제품 실험을 주 단위로 돌려보려다 첫 주에 막혔다. 판정할 지표가 없었다.
정확히는 있는데 쓸 수가 없었다. 우리가 궁극적으로 올리고 싶은 건 결제인데, 결제는 주 단위로 보면 건수가 너무 적어서 어떤 변화를 줘도 통계적으로 구분이 안 된다.
이 문제를 어떻게 우회했는지, 그리고 그 과정에서 밟은 함정들을 정리해둔다. 회사마다 숫자는 다르겠지만 구조는 비슷할 것 같다.
왜 최종 전환으로는 판정할 수 없나
필요한 표본 수는 대략 이 식을 따른다. 유의수준 5%(양측), 검정력 80% 기준이다.
n (한 그룹당) ≈ 16 × p(1−p) / δ²
p = 기저 전환율
δ = 검출하고 싶은 절대 차이
여기서 중요한 건 분모가 제곱이라는 점이다. 검출하려는 차이가 절반이 되면 필요한 표본은 네 배가 된다.
예를 들어보자. 기저 전환율이 2%이고 0.5%p의 개선을 검출하고 싶다면,
n ≈ 16 × 0.02 × 0.98 / 0.005² ≈ 12,500 (한 그룹당)
한 그룹당 1만 2천 명이 필요하다는 뜻이다. 주간 신규 유입이 그 수십 분의 일이라면 판정까지 몇 달이 걸린다. 그동안 제품은 여러 번 바뀌고, 실험은 이미 무의미해진다.
B2B처럼 모수 자체가 작고 전환 주기가 긴 서비스에서는 이 계산이 거의 항상 이렇게 나온다. 그래서 결론은 단순하다.
최종 전환은 실험의 판정 지표가 될 수 없다. 이건 실험 설계를 잘하고 못하고의 문제가 아니라 산술의 문제다.
대신 선행 지표로 판정한다
그러면 무엇으로 판정하나. 최종 전환과의 관계가 이미 검증된 행동을 쓴다.
여기서 “검증된"이 핵심이다. 그럴듯해 보이는 행동을 감으로 고르면 안 된다. 우리는 후보 행동들에 대해 먼저 이걸 확인했다.
- 그 행동을 한 사용자군과 안 한 사용자군의 최종 전환율 격차가 충분히 큰가
- 그 행동이 전환보다 시간적으로 앞서는가
- 실험 기간 안에 충분한 발생량이 있는가
이 세 가지를 통과한 행동만 1차 지표 후보가 된다. 실제로 어떤 행동은 격차가 자릿수 단위로 벌어졌고, 그런 건 선행 지표로 쓸 만하다. 반대로 발생량은 많은데 격차가 작은 행동은 후보에서 뺐다. 흔들기는 쉬운데 흔들어봐야 의미가 없기 때문이다.
주의할 점이 하나 있다. 선행 지표를 올렸다고 최종 전환이 오른다는 보장은 없다. 상관이 인과를 보장하지 않는다. 그래서 최종 전환은 판정에서 빼되 가드레일로 계속 누적하고, 분기 단위로 그룹별 차이를 되돌아본다. 판정 주기와 검증 주기를 분리하는 셈이다.
배정 — 로그를 남기지 않는 방법
배정 방식도 정리했다. 사용자 ID의 특정 비트를 그대로 그룹으로 쓴다.
실험 n번 → (user_id >> n) & 1 0 = 대조군, 1 = 실험군
장점이 두 가지다.
배정 로그가 필요 없다. 애플리케이션과 분석 쿼리가 같은 식을 쓰면 언제든 같은 결과가 나온다. 배정 테이블을 따로 관리하면 그 테이블이 유실되거나 어긋나는 순간 실험 전체가 무의미해지는데, 그 위험이 없다.
실험끼리 직교한다. 실험마다 다른 비트를 쓰면 동시에 여러 개를 돌려도 서로 교락되지 않는다. 다만 이건 비트를 반드시 다르게 배정한다는 전제에서만 성립한다. 여러 실험이 같은 비트를 공유하면 완전히 교락되어 둘 다 해석 불가능해진다. 그래서 비트 사용 현황을 한 곳에서 관리하고, 실험이 끝나면 반납한다.
여기서 조심할 게 하나 있다. 순차적으로 발급되는 ID를 쓴다면 상위 비트를 쓰면 안 된다. n번째 비트는 2ⁿ개마다 뒤집히므로, n이 커질수록 연속한 사용자 덩어리가 통째로 같은 그룹에 들어간다. 그러면 그룹이 가입 시점과 상관되어 버린다. 낮은 비트를 쓰거나, 해시를 한 번 섞은 값을 쓰는 편이 안전하다.
밟았던 함정들
여기부터가 실제로 시간을 많이 쓴 부분이다.
1. 참여자를 걸러냈더니 없는 차이가 생겼다
실험 조건을 온전히 만족하지 않은 사용자를 분석에서 빼면 더 깨끗한 비교가 될 것 같다. 실제로는 반대였다.
우리 환경에서는 사용자가 여러 서버에 걸쳐 접속할 수 있었고, 재방문이 잦은 사용자일수록 도중에 다른 그룹으로 넘어갈 확률이 높았다. 그래서 “그룹이 섞인 사용자"를 제외하니 재방문이 많은 사용자가 체계적으로 빠졌고, 결과적으로 재방문 지표에 실제로는 없는 큰 차이가 나타났다.
같은 데이터를 배정된 대로 전원 포함해서 다시 보니 그 차이는 사라졌다.
일반화하면 이렇다.
제외 기준이 결과 변수와 상관되어 있으면 제외 자체가 편향을 만든다.
시간에 따라 누적되는 지표(재방문, 누적 사용량, 저장, 결제)는 배정된 대로 전원을 포함해서 봐야 한다. 반대로 특정 시점의 노출 여부만 따지는 지표라면 조건을 만족한 사용자만 보는 것도 괜찮다. 지표 성격에 따라 다르게 가는 게 맞다.
2. 배정이 랜덤이 아니었다
서버 단위로 그룹이 갈리는 구조에서는 기존 사용자의 배정이 완전한 랜덤이 아니었다. 확인해보니 활동량이 많은 사용자가 한쪽 그룹에 유의하게 치우쳐 있었다.
이유는 접속 이력에 따라 특정 서버로 고정되는 성질이 있었기 때문인데, 원인이 무엇이든 결과는 같다. 처음부터 두 그룹이 다른 집단이면 실험 결과는 처치 효과가 아니다.
대응은 두 가지였다. 신규 가입자는 그런 이력이 없으니 진짜 랜덤이라 별도 세그먼트로 따로 보고, 기존 사용자는 사전 활동량을 몇 개 구간으로 나눠 사후 층화한 뒤 비교했다.
교훈은 배정이 랜덤인지 확인하는 절차를 판정 전에 반드시 넣는 것이다. 노출 비율이 대략 반반인지, 사전 특성이 두 그룹에서 비슷한지. 이게 어긋나면 이후 분석은 전부 의미가 없다.
3. 같은 쿼리가 몇 분 뒤 다른 답을 냈다
경계에 있던 결과 하나가 재실행하니 유의하지 않게 나왔다. 코드는 그대로였다.
원인은 집계 기준이 현재 시각이었다는 점이다. 실행할 때마다 데이터가 조금씩 늘어나고, p값이 경계 근처면 그 정도로도 판정이 뒤집힌다.
그래서 규칙을 만들었다. 모든 측정 쿼리는 구간을 고정한다. 시작과 끝 시각을 상수로 박고, 로그 원장은 시각이 아니라 고정된 식별자 범위로 자른다. 그러면 몇 달 뒤에 돌려도 같은 값이 나오고, 판정 근거를 감사할 수 있다.
경계에 걸린 결과는 아예 “경계, 스냅샷에 민감함"이라고 따로 기록해두기로 했다. 그런 건 이겼다고 롤아웃하면 안 된다.
4. 표본이 작은 칸에 z-검정을 썼다
세그먼트를 잘게 나누다 보면 몇 건짜리 칸이 나온다. 여기에 정규 근사를 쓰면 p값이 실제보다 작게 나온다. 한 번은 유의하다고 나온 결과가 정확검정으로 다시 보니 유의하지 않았다.
칸의 기대 빈도가 작으면 Fisher 정확검정을 쓰는 게 맞다. 세그먼트를 나눌수록 이 상황이 자주 생기므로 아예 규칙으로 박아뒀다.
5. 측정 원장을 잘못 골랐다
가장 허무했던 건 이거다. 어떤 기능의 사용률을 접속 로그로 쟀는데 대조군이 100%로 나왔다.
원인은 특정 화면이 진입 시 그 기능의 API를 자동으로 호출하고 있었다는 것이다. 사용자가 아무것도 안 해도 로그에는 사용한 것처럼 남는다.
여기서 얻은 규칙은 이렇다.
경로를 재는 원장과 소비를 재는 원장은 다르다.
접속 로그는 “어디를 거쳤나"를 재는 데는 좋지만 “실제로 썼나"를 재는 데는 못 쓴다. 후자는 그 행동이 일어났을 때만 기록되는 도메인 원장으로 재야 한다. 자동 호출, 프리페치, 리다이렉트는 전부 접속 로그를 오염시킨다.
지금 쓰는 규칙
정리하면 이렇게 굳었다.
- 최소 2주 러닝 후 판정. 요일 계절성 한 사이클의 두 배. 중간에 결과를 보고 중단하지 않는다
- 1차 지표 하나로 판정하고, 나머지는 가드레일로 본다
- 배포 직후 노출 비율부터 확인. 어긋나면 배정 버그를 먼저 의심한다
- 누적형 지표는 전원 포함, 시점형 지표만 조건부 분석 허용
- 작은 칸은 정확검정, 경계 결과는 승리로 치지 않는다
- 모든 측정은 구간 고정, 재현되지 않으면 근거로 쓰지 않는다
- 애매하면 1주만 연장하고 무조건 종료. 계속 끌면 실험 사이클 자체가 무너진다
남는 생각
시작할 때는 통계 기법이 어려울 줄 알았는데, 실제로 시간을 잡아먹은 건 전부 측정 쪽이었다. 어떤 원장을 믿을 것인가, 무엇이 자동으로 발생하는가, 누구를 빼면 안 되는가.
검정 자체는 마지막 한 줄이다. 그 앞의 데이터가 틀려 있으면 아무리 정확한 검정을 써도 정확하게 틀린 답이 나온다. 그래서 실험을 늘리는 것보다 한 실험의 측정을 믿을 수 있게 만드는 데 대부분의 노력이 들어갔다.
자주 묻는 질문
Q. 전환이 적은 서비스에서 A/B 테스트를 어떻게 판정하나요?
최종 전환 대신 그 전환과의 관계가 이미 검증된 선행 행동을 1차 지표로 씁니다. 필요한 표본 수는 기저 전환율이 낮을수록 급격히 커지므로, 전환율이 몇 퍼센트 수준이면 판정에 수개월이 걸려 실험 사이클이 성립하지 않습니다. 최종 전환은 판정에서 빼고 가드레일로만 누적 관찰한 뒤 별도 주기로 되돌아보는 편이 현실적입니다.
Q. 실험 참여자를 걸러내면 왜 결과가 왜곡되나요?
제외 기준이 결과 변수와 상관되어 있으면 편향이 생기기 때문입니다. 예를 들어 조건을 온전히 만족하지 않은 사용자를 빼는 방식은, 활동이 많은 사용자일수록 제외될 확률이 높은 구조에서 재방문이나 누적 사용량 같은 지표를 체계적으로 왜곡합니다. 시간에 따라 누적되는 지표는 배정된 대로 전원을 분석에 포함하는 편이 안전합니다.
Q. A/B 테스트 결과가 재실행할 때마다 달라지는 이유는 무엇인가요?
관측 구간이 고정되지 않았기 때문입니다. 현재 시각을 기준으로 집계하면 실행할 때마다 데이터가 조금씩 늘어나고, 특히 통계적 유의성이 경계에 있는 경우 판정이 뒤집힐 수 있습니다. 집계 구간의 시작과 끝, 그리고 원장의 상한을 고정된 값으로 못 박아야 결과를 재현하고 감사할 수 있습니다.