이 글은 누구를 위한 것인가
- 데이터 기반 의사결정 문화를 만들고 싶은 이커머스 PM과 그로스팀
- LaunchDarkly 같은 외부 도구 없이 자체 A/B 테스트 시스템을 구축하려는 개발자
- 실험을 빠르게 돌리되 잘못된 결론을 내리지 않으려는 팀
들어가며
아마존은 매년 수만 건의 A/B 테스트를 돌린다. 넷플릭스의 제품 변경 중 90%는 A/B 테스트를 거친다. 이들이 빠르게 성장할 수 있었던 이유 중 하나는 "데이터 없이는 출시하지 않는다"는 실험 문화다.
문제는 실험 인프라가 없으면 A/B 테스트가 어렵다는 것이다. LaunchDarkly나 Optimizely 같은 SaaS 도구는 월 수백만 원에 달하고, 직접 구축하면 개발 공수가 크다. 하지만 이커머스 규모가 커질수록 실험 플랫폼 투자는 반드시 회수된다.
이 글에서는 자체 A/B 테스트·Feature Flag 시스템을 최소 비용으로 구축하는 방법을 설명한다.
이 글은 bluefoxdev.kr의 그로스 해킹 실험 설계 를 참고하고, 이커머스 플랫폼 설계 관점에서 확장하여 작성했습니다.
1. Feature Flag 시스템 설계
1.1 핵심 데이터 모델
interface FeatureFlag {
id: string;
key: string; // 코드에서 사용하는 키 (예: 'checkout_v2')
type: 'boolean' | 'multivariate';
status: 'active' | 'inactive' | 'archived';
rollout: RolloutConfig;
targeting: TargetingRule[];
createdBy: string;
experiment?: ExperimentConfig;
}
interface RolloutConfig {
type: 'percentage' | 'full' | 'off';
percentage?: number; // 0~100
hashKey?: string; // 일관된 버킷팅을 위한 해시 키 (보통 userId)
}
interface TargetingRule {
attribute: string; // 'userId', 'email', 'segment', 'region'
operator: 'in' | 'not_in' | 'matches';
values: string[];
}
interface ExperimentConfig {
name: string;
hypothesis: string;
primaryMetric: string;
secondaryMetrics: string[];
startDate: Date;
endDate?: Date;
minimumSampleSize: number;
}
1.2 일관된 버킷팅 구현
같은 사용자는 항상 같은 버전을 보아야 한다. Murmur hash를 사용하면 일관된 버킷팅이 가능하다.
import { createHash } from 'crypto';
function getBucket(userId: string, flagKey: string): number {
const hash = createHash('md5')
.update(`${userId}:${flagKey}`)
.digest('hex');
// 상위 8자리 16진수를 0~99 범위로 변환
const numericHash = parseInt(hash.substring(0, 8), 16);
return numericHash % 100;
}
function isFeatureEnabled(
flag: FeatureFlag,
userId: string,
userAttributes: Record<string, string>
): boolean {
if (flag.status === 'inactive') return false;
// 타게팅 규칙 우선 확인
for (const rule of flag.targeting) {
if (matchesRule(rule, userAttributes)) {
return true; // 타게팅 규칙 매칭 시 무조건 활성화
}
}
// 퍼센트 롤아웃
if (flag.rollout.type === 'percentage') {
const bucket = getBucket(userId, flag.key);
return bucket < (flag.rollout.percentage ?? 0);
}
return flag.rollout.type === 'full';
}
2. A/B 테스트 실험 설계
2.1 실험 생명주기
가설 수립 → 샘플 크기 계산 → 실험 설계 → 코드 구현
↓ ↓
결론 도출 ← 통계 분석 ← 데이터 수집 ← 실험 실행
2.2 필요 샘플 크기 계산
from scipy import stats
import numpy as np
def calculate_sample_size(
baseline_rate: float, # 현재 전환율 (예: 0.03 = 3%)
minimum_detectable_effect: float, # 최소 감지 효과 (예: 0.005 = 0.5%p)
alpha: float = 0.05, # 유의 수준 (1종 오류)
power: float = 0.80 # 검정력 (1 - 2종 오류)
) -> int:
"""
각 그룹(A, B)별 필요 샘플 수 반환
"""
p1 = baseline_rate
p2 = baseline_rate + minimum_detectable_effect
z_alpha = stats.norm.ppf(1 - alpha / 2) # 양측 검정
z_beta = stats.norm.ppf(power)
p_bar = (p1 + p2) / 2
n = (z_alpha * np.sqrt(2 * p_bar * (1 - p_bar)) +
z_beta * np.sqrt(p1 * (1 - p1) + p2 * (1 - p2))) ** 2 / \
(p2 - p1) ** 2
return int(np.ceil(n))
# 예시: 현재 전환율 3%, 0.5%p 개선 감지
n = calculate_sample_size(0.03, 0.005)
print(f"그룹당 필요 샘플: {n:,}명") # 약 4,700명
2.3 통계적 유의성 검정
def analyze_experiment(
control_conversions: int,
control_visitors: int,
treatment_conversions: int,
treatment_visitors: int,
alpha: float = 0.05
) -> dict:
p_control = control_conversions / control_visitors
p_treatment = treatment_conversions / treatment_visitors
# Z-test (대규모 샘플)
p_pool = (control_conversions + treatment_conversions) / \
(control_visitors + treatment_visitors)
se = np.sqrt(p_pool * (1 - p_pool) *
(1/control_visitors + 1/treatment_visitors))
z_score = (p_treatment - p_control) / se
p_value = 2 * (1 - stats.norm.cdf(abs(z_score)))
relative_lift = (p_treatment - p_control) / p_control * 100
return {
'control_rate': f"{p_control:.2%}",
'treatment_rate': f"{p_treatment:.2%}",
'relative_lift': f"{relative_lift:+.1f}%",
'p_value': round(p_value, 4),
'is_significant': p_value < alpha,
'confidence': f"{(1 - p_value) * 100:.1f}%"
}
3. 이커머스 실험 설계 베스트 프랙티스
3.1 동시 실험 충돌 방지
여러 실험이 동시에 돌아갈 때 상호작용 효과(interaction effect)가 발생할 수 있다. 독립적인 실험은 **직교 분할(orthogonal split)**로 충돌을 방지한다.
3.2 이커머스에서 주의할 편향
| 편향 유형 | 설명 | 방지 방법 |
|---|---|---|
| 노벨티 효과 | 새 버전 초반에 참여율 높음 | 2주 이상 실험 |
| 계절성 편향 | 성수기/비성수기 차이 | 동일 기간 비교 |
| 프라이밍 효과 | 이전 실험 경험 영향 | 쿨다운 기간 |
| SRM (표본 비율 불일치) | 버킷팅 오류 | SRM 테스트 선행 |
마무리: 핵심 정리
자체 실험 플랫폼 구축의 핵심은 세 가지다.
- 일관된 버킷팅: 같은 사용자는 항상 같은 변형을 보도록 해시 기반 버킷팅
- 통계적 엄밀함: 샘플 크기 사전 계산, p-value < 0.05 기준 준수
- 실험 문화: 개발자와 PM이 쉽게 실험을 만들고 결과를 볼 수 있는 UI
실험 플랫폼은 한 번 구축하면 수년간 팀의 의사결정 품질을 높인다.