콘텐츠로 이동

클라이언트 성능 모니터링의 slow/frozen frame 판정과 알림 정책

클라이언트 모니터링 시스템을 배포한 뒤, 성능 알림 이벤트가 과도하게 수집됐다. 기존 이벤트명(alert_low_fps)으로 쌓인 데이터는 이미 판단에 쓸 수 없는 상태였다.

  • FPS: 매 프레임 순간 FPS를 계산해 임계값 이하이면 바로 로깅했다. GC 스파이크 같은 일시적 프레임 드롭에도 알림이 발생했다.
  • 메모리: 쿨다운마다 반복 로깅해서, 메모리 부족 상태가 계속되는 기기에서 이벤트가 폭주했다.

FPS 측정과 알림을 Android Vitals와 비슷한 기준인 프레임 시간 기반 slow/frozen frame 비율로 바꾼다. 메모리를 포함한 전체 모니터링은 씬 단위 단계별 최초 1회 로깅으로 통일한다.

Android Vitals 기준은 다음과 같다.

  • Slow frame(Janky): 프레임 렌더링 시간이 목표 프레임 시간을 넘음(60fps 기준 16.67ms 초과)
  • Frozen frame: 프레임 렌더링 시간이 700ms 초과(고정값)
  • 측정 단위: FPS가 아니라 프레임 시간(ms)
  • 판단 방식: 세션 안의 slow/frozen frame 비율
  • 매 프레임 순간 FPS 계산(1f / Time.unscaledDeltaTime)
  • targetFrameRate 대비 비율로 판정(50% 미만 → severe, 33% 이하 → critical)
  • 쿨다운(기본 30초)으로 중복 로깅 방지
  • 이벤트명: alert_low_fps / alert_game_low_fps
  • 프레임 시간(ms) 기반으로 바꾼다.
  • 매 프레임 slow/frozen 여부를 판정해 카운터에 누적한다.
  • 씬 단위(로비, 게임별)로 독립 측정한다. Android Vitals의 Activity 단위와 비슷하다.
  • 세션 누적 비율이 단계별 임계값에 처음 도달할 때 1회만 알림을 보낸다.
  • 단계별 1회 로깅이라 쿨다운(fpsLogCooldown)은 필요 없어 없앴다.
  • 기존 이벤트명 데이터가 오염되어 새 이벤트명을 쓴다.
  • 판정: 프레임 시간 > 1 / targetFrameRate(60fps 기준 16.67ms)
  • 세션 누적 비율로 단계별 알림
    • 비율 >= 20% → warning(1회)
    • 비율 >= 50% → severe(1회)
  • frozen frame도 slow frame으로 센다(부분집합).
  • 비율 임계값은 원격 설정으로 조정할 수 있다.
  • 이벤트명: alert_slow_frame / alert_game_slow_frame
  • 판정: 프레임 시간 > 700ms(원격 설정으로 조정 가능)
  • 700ms는 약 1.4 FPS로, 사용자가 보기에 화면이 완전히 멈춘 수준이다.
  • 발생 즉시 로깅한다(세션 내 최초 1회).
  • 비율을 누적하지 않고 단일 프레임으로 판정한다. 한 번만 발생해도 그 자체로 심각한 사용자 경험 문제이기 때문이다.
  • 이벤트명: alert_frozen_frame / alert_game_frozen_frame
  • warning/critical: 쿨다운(60초)마다 반복 로깅
  • system_critical(OS lowMemory): 쿨다운(10초)마다 반복 로깅
  • 메모리 부족 상태가 이어지면 이벤트 폭주
  • FPS와 같은 씬 단위 단계별 최초 1회 로깅으로 바꾼다.
  • 쿨다운(memoryLogCooldown)을 없앤다.
  • 씬이 바뀌면 플래그를 리셋한다.
severity 트리거 씬당 최대
warning reserved >= 시스템 메모리의 30% 1회
critical reserved >= 시스템 메모리의 50% 1회
system_critical OS lowMemory 콜백 1회
  • current_scene 공통 파라미터를 추가했다.
  • 게임 씬을 판별하는 방식을 하나로 통일했다.
구분 키 기본값 비고
제거 monitor.fps_log_cooldown - 단계별 1회 로깅으로 불필요
제거 monitor.memory_log_cooldown - 단계별 1회 로깅으로 불필요
추가 monitor.slow_frame_warning_ratio 0.2 slow frame warning 비율 (20%)
추가 monitor.slow_frame_severe_ratio 0.5 slow frame severe 비율 (50%)
추가 monitor.frozen_frame_threshold_ms 700 frozen frame 임계값 (ms)
유지 monitor.enable true 모니터링 전체 ON/OFF
유지 monitor.sampling_rate 1.0 샘플링 비율
유지 monitor.memory_warning_ratio 0.3 메모리 warning 비율
유지 monitor.memory_critical_ratio 0.5 메모리 critical 비율
유지 monitor.fps_scene_grace_period 5 씬 로드 후 측정 제외 (초)
유지 monitor.fps_fg_grace_period 3 포그라운드 복귀 후 측정 제외 (초)
유지 monitor.warmstart_enable false 웜 스타트 측정

쿨다운 반복 방식에서는 같은 씬에 머무는 시간에 비례해 알림이 계속 쌓였다. 바뀐 방식에서는 씬당 알림 수에 아래 표의 상한이 생긴다.

이벤트 severity 씬당 최대
alert_slow_frame warning 1회
alert_slow_frame severe 1회
alert_frozen_frame critical 1회
alert_memory_warning warning 1회
alert_memory_warning critical 1회
alert_memory_warning system_critical 1회
합계 최대 6회

alert_low_fps와 alert_game_low_fps는 더 이상 발생하지 않는다.

AppsFlyer 전환 데이터를 이벤트 수집 SDK로 전달

섹션 제목: “AppsFlyer 전환 데이터를 이벤트 수집 SDK로 전달”

같은 작업에서 마케팅 성과 분석용 데이터 경로도 정리했다.

마케팅 성과 분석을 위해 AppsFlyer의 전환 데이터(media_source 등)를 이벤트 수집 SDK로 넘겨야 한다. 기존 AppEventLogger는 URL·딥링크에서 파라미터를 하나씩 파싱해 보냈고, 일부 경우에 값이 누락됐다.

AppsFlyer SDK 콜백 데이터를 AppsFlyerSystem에 저장해 두고, 로그인이 끝난 시점에 이벤트 수집 SDK로 한 번에 보낸다.

수집 경로(SDK 콜백 → 저장):

  • OnRequestResponse: SDK 요청 응답(statusCode)
  • OnDeepLinkReceived: 딥링크 수신(DeepLinkEventsArgs)
  • AppsFlyerConversionHandler.onConversionDataSuccess/Fail → SetConversionData(): 전환 데이터

전송 시점은 로그인 성공 후 로그인 처리 클래스가 호출하는 OnLogin()이다.

이벤트명 조건 파라미터
af_request_success SDK 요청 성공 (status 200) af_id
af_request_fail SDK 요청 실패 (status ≠ 200) status, desc
af_deep_link_found 딥링크 수신 성공 af_id, is_deferred, deep_link
af_deep_link_error 딥링크 수신 실패 af_id, error
af_conversion_data_success 전환 데이터 수신 성공 af_id, media_source, campaign, adset, af_channel, af_ad, af_status, data (JSON 원본)
af_conversion_data_fail 전환 데이터 수신 실패 af_id, error

전환 데이터 원본(JSON)을 직접 보내므로, AppEventLogger에서 URL·딥링크를 개별 파싱하던 media_source 로직은 중복이 되어 지웠다.

  • CollectMediaSourceFromUrl(), StoreMediaSourceFromDeepLink(), TryFlushMediaSourceLog()
  • MediaSourceParamKeys, BuildMediaSourceParams(), GetMediaSourceParamKeys()
  • hasLoggedMediaSource, pendingMediaSourceParams 필드

호출부는 한 줄만 바뀐다.

// Before
AppEventLogger.TryFlushMediaSourceLog();
// After
AppsFlyerSystem.OnLogin(msg);