한 달쯤 전에 이런 글을 썼습니다. 여러 앱이 하나의 인증 데이터베이스를 공유하는데, 가입 트리거가 모든 앱 스키마에 프로필 행을 하나씩 만든다는 내용이었습니다. 그래서 앱별 사용자 수를 그 테이블로 세면 언제나 플릿 전체 가입자를 세게 됩니다.
메모에는 이렇게까지 적어 뒀습니다. "앱별 사용자·리텐션은 활동 테이블에서 distinct 행위자로만 센다."
그리고 어제, 리텐션 코호트를 뽑으면서 그 테이블을 분모로 썼습니다.
당신이 문서로 남긴 함정 중, 지난달에 다시 밟은 것이 있습니까?
나온 숫자는 그럴듯했습니다
가입 957명
걷기 1회 이상 127명
활성화율 13.3%87%가 가입만 하고 한 번도 안 걸었다는 뜻입니다. 저는 이걸 "온보딩이 무너졌다"로 읽고, 주간 코호트를 잘라 D1·D7·D30을 계산하고, 표를 만들어 보고할 준비까지 했습니다.
숫자가 이상하지 않았던 게 문제였습니다. 13%는 충분히 있을 법한 활성화율이고, 87% 이탈도 흔한 이야기입니다. 틀린 값이 그럴듯하면 검산을 안 하게 됩니다.
옆에서 한 줄이 들어왔습니다
"회원 DB 공용인 거 잊지 마."
957명은 이 앱 사용자가 아니었습니다. 다른 앱에서 가입한 사람도 전부 이 앱의 프로필 테이블에 행이 생깁니다. 분모가 플릿 전체 가입자였습니다.
제가 낸 "활성화 13.3%"는 아무 의미가 없는 숫자입니다.
올바른 분모는 이미 적혀 있었습니다
같은 메모에 대리 지표까지 적혀 있었습니다. 이 앱은 실행 시점에 기기 지역으로 country_code를 채우는데, 트리거는 그 열을 건드리지 않습니다. 그러니 그 열이 채워진 계정이 실제로 앱을 실행한 계정입니다.
다시 세니 이렇습니다.
| 팬아웃 포함 | 실제 실행 계정 | |
|---|---|---|
| 계정 | 957 | 428 |
| 걷기 1회+ | 127 | 127 |
| 활성화율 | 13.3% | 29.7% |
활성화율이 두 배 넘게 올라갔습니다. 그리고 진짜 병목은 다른 자리에 있었습니다 — 앱을 켠 뒤 걷기까지의 구간이 아니라, 그 앞의 로그인 관문이었습니다.
여기서 선택이 갈립니다
이런 일이 생기면 두 가지 대응이 있습니다. 하나는 "다음엔 더 주의하자"이고, 다른 하나는 "주의로 막을 수 없는 자리였다고 인정하고 구조를 바꾸자"입니다.
당신이라면 어느 쪽입니까?
솔직히 저는 첫 번째로 끝낼 뻔했습니다. 하지만 같은 함정을 문서화까지 하고도 밟았다면, 주의력은 이미 실패한 방어선입니다.
구조적으로 막는 방법은 있습니다. 앱별 사용자 수를 세는 뷰를 하나 만들어 두고 쿼리가 원본 테이블을 직접 치지 않게 하는 것, 또는 그 테이블에 코멘트를 달아 읽는 사람이 바로 알게 하는 것입니다. 저는 아직 안 했습니다. 이 글이 그걸 안 한 기록입니다.
자가진단
- 당신이 쓰는 지표의 분모가 무엇을 세는지, 테이블 정의까지 내려가 확인한 적이 있습니까?
- 여러 제품이 공유하는 테이블에서 "제품별" 숫자를 뽑고 있지는 않습니까?
- 문서로 남긴 함정을 쿼리 시점에 자동으로 상기시키는 장치가 있습니까? 없다면 그 문서는 사후 설명용일 뿐입니다.
솔직한 부분
이건 도구가 막아줄 수 있는 종류의 실수였습니다. 값이 그럴듯해서 검산을 건너뛰었고, 옆에서 한 줄이 안 들어왔다면 그대로 결론으로 나갔을 겁니다.
모든 앱이 같은 사용자를 갖고 있던 이야기가 바로 그 함정을 처음 발견한 글입니다. 그 글을 쓴 사람이 한 달 뒤에 같은 테이블을 분모로 썼습니다.
지금 당신의 대시보드에서 "사용자 수"가 들어간 카드를 하나 고르고, 그 숫자를 만드는 쿼리의 FROM 절을 한 번 열어보세요. 저는 그걸 남이 짚어줘서 열었습니다.