첫 번째 이유: 30개
Firebase는 프로젝트당 앱 30개다. 프로젝트와 GA4 속성은 1:1. 앱을 계속 만드는 한 언젠가 쪼개야 하고, 쪼개는 순간 같은 제품의 iOS와 Android가 다른 속성으로 갈라진다. 계측이 있는 이유가 그 둘을 나란히 보는 건데, 그게 깨진다.
쿼터 증설을 요청하는 대신 계측을 직접 갖기로 했다. 이식 앱들은 Firebase에서 아예 뺐다.
두 번째 이유: 몇 달째 틀린 숫자
7월에 화면별 체류 시간이 이상하다는 걸 알았다. "LogoForm은 페이월에 78%", "Pro2Camera는 69%", "Artoon은 58%". 사용자가 페이월을 그렇게 오래 볼 리가 없다.
원인은 GA4 설정이 아니라 앱 코드였다. 화면 추적이 onAppear에서 한 줄 호출하는 게 전부였고 onDisappear가 없었다. 페이월은 모든 앱에서 .sheet로 뜬다. 시트를 닫아도 SwiftUI는 부모 뷰의 onAppear를 다시 부르지 않는다. Firebase는 마지막 screen_view를 현재 화면으로 들고 있다가 이후 engagement를 거기에 붙인다. 페이월을 닫고 에디터에서 쓴 시간이 전부 페이월로 적립된 것이다.
모달을 닫을 때 이전 화면을 복원하도록 고쳤지만, 그 전까지의 화면 체류 분석은 전부 과대계상이었다. 숫자 하나를 몇 달 믿었다.
만든 것
| 조각 | 무엇 |
|---|---|
| 수집 엣지 | Cloudflare Worker + D1. 앱이 이벤트를 보내는 곳 |
| 집계·리포트 | 기존 SaaS 안의 Go 패키지 |
| 클라이언트 | iOS 패키지 하나, Flutter 패키지 하나 |
설계에서 정한 것들.
app_id는 스토어 리스팅이 아니라 제품 하나에 문자열 하나. iOS와 Android가 같은 값을 보내야 합쳐진다.- 국가는 엣지가 연결에서 읽는다. 기기는 보내지 않는다.
- IP, 광고 ID, 정밀 위치, 개인 식별정보는 구조적으로 못 받는다. 컬럼이 없고, 속성 키에 그런 단어가 들어오면 막는다.
- 수집 URL이 설정되지 않으면 클라이언트는 조용히 꺼진다. 없는 주소로 큐를 쌓으면 정작 배포된 날 보낼 이벤트가 용량 초과로 밀려난다.
- 화면 체류는 enter/exit 짝으로 잰다. 그 실수는 다시 하지 않는다.
첫 데이터의 대부분은 사람이 아니었다
수집이 돌기 시작한 첫 주, 목록을 액면대로 읽으면 "심사도 안 끝난 앱에 유저가 있다"였다. 지문을 나눠 보니 셋이었다.
| 정체 | 지문 |
|---|---|
| Play 사전 출시 보고서 로봇 | 같은 빌드 ID에 install_id 여러 개, 국가 US, 로케일 전부 en-US, 하루에 몰림, D1 리텐션 0, 세션당 화면 진입 수십 회 |
| Apple 심사 리뷰어 | 앱당 정확히 1세션·1유저, US, 체류 3~4분, 버전이 아직 미출시인 심사 제출 버전 |
| 나 | KR, ko_KR, 실기기 OS 버전, 세션 한두 개 |
LogoForm 30일. iOS는 1세션 1유저 KR. Android는 12세션 8유저 전원 en-US, 빌드 ID 2종, 페이월 진입 182회. 둘이 합쳐져 "13세션에 페이월 165회"라는 말이 안 되는 행이 됐다.
지금은 이 셋을 나눠서 본다. 계측의 첫 일은 사람을 세는 게 아니라 사람이 아닌 걸 빼는 거였다.