사이트의 루트 레이아웃에 있는 공유 함수가 A/B 테스트 노출 카운터를 페이지 뷰 카운터로 바꿔버렸고, 이로 인해 샘플 크기가 부풀려져 전환율이 무의미해졌습니다. 홈페이지를 50/50으로 나눈 이 테스트에서는 한 변형(variant)에 178회, 다른 변형에 57회의 히트가 기록되어 예상했던 균등한 분할과는 거리가 멀었습니다.
오류가 랜덤라이저를 통과해버린 이유
개발자는 먼저 방문자를 변형에 할당하는 랜덤라이저(randomizer)를 검증했습니다. 미들웨어를 읽고, 쿠키 로직을 점검하고, 할당 함수를 10,000번 호출하는 스크립트를 실행해 보았으나 결과는 완벽한 50/50 분할이었습니다. 랜덤라이저 자체는 정상적으로 작동했습니다. 문제는 노출이 기록되는 방식이었습니다.
하나의 함수가 두 가지 작업을 수행하고 있었습니다:
- 변형 설정(Set the variant) – 방문자의 경험을 일관되게 유지하기 위해 모든 페이지 로드 시 실행됩니다.
- 노출 이벤트 기록(Record the exposure event) – 변형이 처음 나타나는 순간, 방문자당 단 한 번만 실행되어야 합니다.
이 함수는 모든 내비게이션 시 렌더링되는 컴포넌트인 루트 레이아웃에 있었습니다. 노출 기록 코드가 레이아웃이 렌더링될 때마다 실행되었기 때문에, 모든 페이지 뷰가 새로운 노출로 집계되었습니다. 두 홈페이지 변형은 약간 다른 레이아웃 트리를 사용했기 때문에 페이지 뷰 비율이 서로 달라졌고, 이로 인해 랜덤라이저가 고장 난 것 같은 착각을 불러일으켰습니다.
잘못된 카운트가 중요한 이유
클릭률, 가입, 구매와 같은 전환 수치는 정확하게 기록되었습니다. 하지만 분모(노출 수)가 잘못되었습니다. 계산된 전환율은 실제보다 훨씬 낮게 나타났고, 해당 전환율을 바탕으로 내린 모든 결정은 신뢰할 수 없게 되었습니다.
불일치가 드러나기 전까지 테스트는 2주 동안 진행되었고, 결국 팀은 전체 데이터 세트를 폐기해야 했습니다.
해결 방법
해결책은 간단했습니다. 책임을 별도의 함수로 분리하는 것이었습니다. 이제 노출 기록 코드는 방문자가 이미 집계되었는지 확인하여 사용자당 한 번만 실행됩니다. 변형 설정 코드는 기존 위치에 그대로 두어 모든 내비게이션 시 계속 실행되도록 했습니다.
실험을 운영하는 사람을 위한 세 가지 교훈
- 상태 설정과 일회성 이벤트를 분리하십시오. 변형을 할당하는 동시에 노출을 기록하는 함수는 충돌을 일으킵니다. 전자는 반복되어야 하지만 후자는 반복되어서는 안 되기 때문입니다.
- 루트 레이아웃에 일회성 로직을 두지 마십시오. 모든 페이지 로드 시 렌더링되는 컴포넌트에 배치된 모든 것은 반복적으로 실행되어, "방문자당 한 번"이 "페이지 뷰당 한 번"으로 변질됩니다.
- 관찰된 분할 비율이 랜덤라이저와 일치하지 않는다면, 카운터부터 점검하십시오. 개발자들은 흔히 랜덤라이저의 공정성은 테스트하지만, 카운팅 메커니즘이 정확한지는 거의 확인하지 않습니다.
향후 주의할 점
노출을 위해 단일 카운터에 의존하는 모든 실험은 해당 카운터가 컴포넌트 트리 어디에 위치하는지 점검해야 합니다. 카운터가 글로벌 레이아웃에 있다면, 쿠키나 로컬 스토리지 플래그와 같은 지속적인 식별자에 이벤트를 연결하는 확인 절차를 추가하십시오. 또한 팀은 대시보드에 무결성 검사(sanity check) 기능을 구축해야 합니다. 관찰된 변형 분포가 작은 통계적 오차 범위를 벗어난다면, 랜덤라이저가 고장 났다고 가정하기 전에 카운터 점검을 위해 해당 테스트에 플래그를 표시하십시오.
요약하자면, 신뢰할 수 있는 노출 카운트 없이는 잘 작동하는 랜덤라이저도 무용지물입니다. 책임을 분리하고 일회성 이벤트를 항상 렌더링되는 컴포넌트 외부에 배치하면, A/B 테스트의 정확성을 유지하고 몇 주간의 낭비되는 분석 시간을 아낄 수 있습니다.
