mSPRT로 A/B 테스트를 언제든 들여다봐도 되는 이유 | Anytime-Valid p-value 원리와 Netflix·Optimizely·Spotify 구현 차이
A/B 테스트를 운영하다 보면 누구나 한 번쯤 충동을 느낀다. "지금 데이터만 봐도 충분한 거 아닐까?" 이렇게 실험 도중 결과를 들여다보는 행위를 peeking이라 부른다. 전통적인 고정 표본 t-test에서 이것은 심각한 문제다. 실제로 α=0.05 기준에서 20번만 반복해서 들여다봐도, 귀무가설이 참일 때 한 번이라도 "유의미하다"는 결론을 내릴 확률(FWER)이 64%를 넘어간다. 들여다볼수록 1종 오류가 누적적으로 증가하기 때문이다.
mSPRT(Mixture Sequential Probability Ratio Test)는 이 peeking problem을 마팅게일 이론으로 수학적으로 완전히 해결하면서, '언제 멈춰도 안전한' Anytime-Valid p-value를 보장하는 순차 가설 검정 방법론이다. Optimizely·Netflix·Spotify는 각자 다른 방식으로 이 문제를 풀었다. 그 차이를 이해하는 것은 단순한 호기심이 아니다. 프로덕션 A/B 테스트 플랫폼을 설계하거나 선택할 때, 방법론 선택 하나가 실험 검정력 20% 차이로 돌아올 수 있다.
이 글을 읽으려면 p-value와 가설 검정의 기본 개념을 알고 있어야 한다. 통계 수식이 포함되지만, 각 수식에 직관적 설명을 병렬로 제공하므로 수학 전공이 아니어도 따라올 수 있다. mSPRT의 수학적 토대, 세 회사의 구현 차이, 실무 적용 시 주의사항까지 한 번에 정리한다.
핵심 개념
이 글에서 사용하는 주요 용어
용어 정의
- 1종 오류(Type I Error): 귀무가설이 참인데 기각하는 오류. 유의수준 α로 제어한다.
- FWER(Family-Wise Error Rate): 여러 가설을 동시에 검정할 때 하나 이상의 1종 오류가 발생할 확률.
- MDE(Minimum Detectable Effect): 실험에서 실질적으로 의미 있다고 판단하는 최소 효과 크기.
- Anytime-Valid p-value: 아무 시점에 계산해도 1종 오류율이 α를 넘지 않는 p-value.
- e-value: 귀무가설 하에서 기댓값이 1 이하인 비음수 확률 변수. Anytime-valid 추론의 현대적 이론 기반.
고전 SPRT의 한계와 mSPRT의 탄생
Wald(1945)가 제안한 SPRT(Sequential Probability Ratio Test)는 순차 검정의 원조다. 데이터가 들어올 때마다 우도비(likelihood ratio)를 계산해 귀무가설 기각 여부를 판단한다. 그런데 고전 SPRT에는 치명적인 제약이 있다. 처리 효과 크기 θ를 사전에 정확히 알아야 한다. 실험 전에 "이번 버튼 색 변경으로 클릭률이 정확히 2.3% 오른다"는 것을 알 수 없으니, 실제로 쓰기 어렵다.
mSPRT는 단일 θ 대신 혼합(mixture) 사전 분포 H(θ) 를 사용해 이 문제를 해결한다. 가능한 효과 크기 전체에 대해 우도비를 적분해버리는 것이다.
Λ_n = ∫ [ ∏_{i=1}^{n} f_θ(X_i) / f_0(X_i) ] dH(θ)직관적 이해
f_θ(X_i)는 효과 크기가 θ일 때 관측값 X_i의 확률밀도,f_0(X_i)는 귀무가설(효과 없음) 하에서의 확률밀도다. "처리 효과가 있을 때 이 데이터가 나올 가능성" 대 "효과가 없을 때 나올 가능성"의 비율을, 가능한 모든 효과 크기에 걸쳐 평균 낸 값이 Λ_n이다.
정규 데이터에서는 H를 N(0, τ²)로 두는 경우가 가장 흔하며, 이때 폐쇄형(closed-form) 해가 존재해 실시간 연산이 가능하다.
Anytime-Valid p-value 보장: 마팅게일이 핵심이다
mSPRT가 언제 봐도 안전한 이유는 Λ_n이 귀무가설 H₀ 하에서 비음수 마팅게일(nonnegative martingale) 이 되기 때문이다.
마팅게일이란? 카지노에서 손실을 복구하려고 베팅을 두 배로 늘리는 전략을 '마팅게일 전략'이라고 부르는데, 여기서의 마팅게일은 전혀 다른 의미다. 확률 과정에서 현재 이후의 기댓값이 현재 값과 정확히 같은 과정을 말한다. 동전 던지기 누적 합이 대표적인 예다.
귀무가설 하에서 Λ_n은 다음 마팅게일 조건을 만족한다.
E_H₀[Λ_n | Λ_1, ..., Λ_{n-1}] = Λ_{n-1}이 성질에 Ville의 부등식(Ville's Inequality) 을 적용하면 다음이 성립한다.
P(∃n ≥ 1 : Λ_n ≥ 1/α) ≤ α즉, 귀무가설이 참일 때 Λ_n이 1/α를 넘을 확률은 전체 실험 기간을 통틀어 최대 α다. 따라서 p_n = 1/Λ_n을 anytime-valid p-value로 정의하면, 몇 번을 들여다봐도 1종 오류율이 α를 초과하지 않는다는 수학적 보장이 생긴다.
핵심 인사이트 전통 p-value는 "딱 이 시점에 이 표본 크기로 분석했을 때"라는 조건부 해석이 필요하다. Anytime-valid p-value는 그 조건이 없다. 아무 시점에 봐도 해석이 동일하다.
이 개념은 최근 통계학계에서 주목받는 e-value(e-process) 의 순차 버전이기도 하다. mSPRT의 Λ_n은 e-value의 특수 사례로, Grünwald·Ramdas 등의 연구로 이론이 빠르게 일반화되고 있다.
τ²(혼합 분산) 선택: 작은 차이가 검정력을 가른다
mSPRT 설계에서 실무적으로 가장 중요한 파라미터는 혼합 사전 분포의 분산 τ² 다. τ²는 직관적으로 "효과 크기가 얼마나 퍼져 있을지에 대한 우리의 사전 믿음의 넓이"다. 이 값이 너무 좁으면 실제 효과를 놓치고, 너무 넓으면 검정력이 낮아진다.
τ²는 α(유의수준), σ(데이터 표준편차), M(기대 표본 크기)을 기반으로 최적화하며, MDE에서 역산하는 방법을 권장한다.
| τ² 오지정 정도 | 검정력 손실 | 평균 실행 길이 증가 |
|---|---|---|
| 실제 효과 대비 1~2 오더 차이 | 5~10% 이내 | 미미함 |
| 실제 효과 대비 2 오더 초과 | 최대 20% | 최대 40% 증가 |
τ²가 MDE와 크게 동떨어지지 않는 한 mSPRT는 비교적 강건하다. 하지만 아예 엉뚱한 스케일을 쓰면 검정력 손실이 상당하다.
세 회사 구현 비교
예시 1: Optimizely Stats Engine — 상용화의 원조
2015년 1월부터 두 집단(대조/처리)의 관측값 차이 Zn = Yn - Xn ~ N(θ, 2σ²)에 단일 스트림 mSPRT를 적용해왔다. 정규 혼합 사전 H = N(0, τ²)를 쓰면 폐쇄형 해가 나오기 때문에 실시간 연산이 가능하다.
# requires: numpy>=1.21, scipy>=1.7
import numpy as np
def msprt_p_value(n: int, mean_diff: float, tau_sq: float, sigma_sq: float) -> float:
"""
정규 근사 기반 mSPRT anytime-valid p-value 계산
Args:
n: 현재까지 수집된 표본 수 (처리군 또는 대조군 각각)
mean_diff: 처리군과 대조군의 평균 차이 (원래 단위, 표준화 전)
tau_sq: 혼합 사전 분포 분산 τ² — MDE를 기반으로 역산 권장
sigma_sq: 데이터 분산 추정치 σ²
Returns:
anytime-valid p-value (언제 계산해도 1종 오류율 α 이하 보장)
"""
# 혼합 우도비 Λ_n (정규 혼합 분포 폐쇄형 해)
variance_ratio = sigma_sq / (sigma_sq + n * tau_sq)
exponent = (tau_sq * n**2 * mean_diff**2) / (2 * sigma_sq * (sigma_sq + n * tau_sq))
lambda_n = np.sqrt(variance_ratio) * np.exp(exponent)
# p_n = 1 / Λ_n, 1.0을 초과하지 않도록 클리핑
return min(1.0, 1.0 / lambda_n)
# 사용 예시: 10,000명 표본, 관측된 평균 차이 0.05, τ²=0.01, σ²=1.0
p = msprt_p_value(n=10000, mean_diff=0.05, tau_sq=0.01, sigma_sq=1.0)
print(f"Anytime-valid p-value: {p:.4f}")| 코드 요소 | 설명 |
|---|---|
mean_diff |
처리군–대조군 평균 차이 (원래 단위). 표준화된 Z 통계량이 아님에 주의 |
variance_ratio |
Λ_n의 크기 조정 항 — 표본이 쌓일수록 작아짐 |
exponent |
관측된 효과가 클수록 지수가 커져 Λ_n 증가 |
min(1.0, 1.0 / lambda_n) |
p-value는 1을 넘을 수 없음 |
Optimizely는 여기서 한 발 더 나아가 Stats Accelerator를 통해 anytime-valid 특성을 활용한 동적 트래픽 배분도 구현했다. 가장 유망한 변형에 더 많은 트래픽을 실시간으로 몰아주면서도 1종 오류 보장이 유지된다. Simpson's Paradox 방지를 위한 시간 가변 신호 처리 로직도 내장돼 있다.
예시 2: Netflix — 복잡한 메트릭으로의 확장
Netflix의 핵심 문제는 play-delay(재생 지연) 다. 신규 소프트웨어 배포 시 재생 지연이 회귀했는지 빠르게 감지해야 하는데, 재생 지연은 정규 분포를 따르지 않고 분위수(quantile) 변화가 더 의미 있는 경우가 많다.
Netflix는 두 가지 접근법으로 mSPRT를 확장했다.
Part 1 — 연속 데이터: Howard & Ramdas(2021)의 anytime-valid 신뢰 시퀀스(confidence sequence)를 활용해 평균뿐 아니라 분위수 변화도 연속 모니터링한다.
# requires: numpy>=1.21, scipy>=1.7
# 주의: 이 코드는 개념 설명을 위한 단순화된 구현입니다.
# 직접 실행은 가능하지만, 수치는 Howard & Ramdas(2021) 논문의
# empirical Bernstein bound 정확한 구현과 다릅니다.
# 실제 구현은 논문 Algorithm 1을 참조하세요.
import numpy as np
def anytime_valid_confidence_sequence(
data: np.ndarray,
alpha: float = 0.05,
) -> tuple[float, float]:
"""
Howard & Ramdas (2021) 기반 anytime-valid 신뢰 구간 (단순화 버전)
t-test 신뢰 구간과 달리 언제 계산해도 커버리지 보장
"""
n = len(data)
mean = np.mean(data)
var = np.var(data, ddof=1)
# 단순화된 시간 균등 신뢰 시퀀스 반경
# (실제 구현에는 추가 보정항이 필요)
radius = np.sqrt(
2 * var * (np.log(2 / alpha) + np.log(np.log(n) + 1)) / n
)
return (mean - radius, mean + radius)
# 사용 예시
data = np.random.normal(0.1, 1.0, 500)
lower, upper = anytime_valid_confidence_sequence(data)
print(f"Anytime-valid 95% 신뢰 구간: ({lower:.4f}, {upper:.4f})")Part 2 — 카운트 데이터: 처리군과 대조군의 이벤트 발생 수 두 정수만으로 anytime-valid 검정을 구현했다. Netflix는 이 구현을 GitHub Gist로 공개했다.
핵심 인사이트 Netflix의 접근법이 Optimizely와 다른 점은 단일 mSPRT 공식이 아니라 메트릭 유형(연속, 카운트, 분위수, 회귀 조정)마다 이론적으로 최적화된 anytime-valid 방법을 적용한다는 것이다. 이는 더 복잡하지만 각 메트릭에서 더 높은 검정력을 낸다.
예시 3: Spotify — GST로 다른 길을 선택하다
Spotify는 2023년 3월 공식적으로 mSPRT 대신 Group Sequential Test(GST) 를 채택했다. 이유는 명확하다. Spotify의 실험 데이터는 실시간 스트림이 아닌 일별·주별 배치로 집계된다. 이 환경에서는 중간 분석 시점이 사전에 계획 가능하므로 GST의 alpha spending 함수가 더 효율적이다.
GST에서 사용하는 O'Brien-Fleming 함수는 "초기에 엄격하고 후반에 관대한 경계를 설정하는 함수"다. 실험 초반에는 압도적인 증거가 있어야 기각하고, 데이터가 쌓일수록 점점 낮은 기준으로도 기각을 허용한다. Lan-DeMets(1983) 는 이 O'Brien-Fleming을 포함한 다양한 경계 형태를 일반화한 alpha spending 프레임워크다.
# requires: numpy>=1.21, scipy>=1.7
# 주의: 이 코드는 개념 설명을 위한 단순화된 구현입니다.
# 직접 실행은 가능하지만, 수치는 Lan-DeMets(1983)의
# 수치 적분 기반 정확한 구현과 다를 수 있습니다.
import numpy as np
from scipy import stats
def obrien_fleming_alpha_spending(
t: float,
alpha: float = 0.05
) -> float:
"""
O'Brien-Fleming 타입 alpha spending 함수 (Lan-DeMets 프레임워크)
O'Brien-Fleming은 특정 경계 형태(초기 엄격, 후반 관대)이고,
Lan-DeMets는 이를 포함한 일반적 alpha spending 프레임워크다.
Args:
t: 현재 정보 비율 (0~1, 현재 n / 계획된 최대 N)
alpha: 전체 유의수준
Returns:
t 시점까지의 누적 alpha spending
"""
return 2 * (1 - stats.norm.cdf(
stats.norm.ppf(1 - alpha / 2) / np.sqrt(t)
))
# 사용 예시: 5번의 중간 분석 계획
analysis_points = [0.2, 0.4, 0.6, 0.8, 1.0]
prev_spent = 0.0
print(f"{'정보비율':>8} {'허용 α(이번)':>14} {'누적 spent':>12}")
for t in analysis_points:
cumulative = obrien_fleming_alpha_spending(t, alpha=0.05)
incremental = cumulative - prev_spent
print(f"{t:>8.1f} {incremental:>14.5f} {cumulative:>12.5f}")
prev_spent = cumulative| 비교 항목 | mSPRT (Optimizely·Netflix) | GST (Spotify) |
|---|---|---|
| 분석 시점 | 언제든지, 제한 없음 | 사전 계획된 시점에만 |
| 표본 크기 사전 지정 | 불필요 | 필요 |
| 배치 환경 검정력 | GST 대비 낮음 | 더 높음 |
| 표본 크기 오추정 내성 | 강건 | 취약 |
| 실시간 스트리밍 적합성 | 높음 | 낮음 |
세 방법 직접 비교: 동일 데이터로 돌려보기
같은 시뮬레이션 데이터에 mSPRT와 GST를 모두 적용해 검정력과 평균 표본 크기를 비교한다.
# requires: numpy>=1.21, scipy>=1.7
# 주의: GST 구현은 단순화된 개념 코드입니다.
# 수치는 경향성 파악용이며 논문의 정확한 구현과 다릅니다.
import numpy as np
from scipy import stats
np.random.seed(42)
TRUE_EFFECT = 0.05 # 진짜 효과 크기
SIGMA = 1.0
N_SIM = 1_000 # 시뮬레이션 횟수
MAX_N = 5_000 # 최대 표본 크기 (군별)
ALPHA = 0.05
TAU_SQ = 0.01 # mSPRT 혼합 분산 (MDE 0.05 기반 설정)
def msprt_test(ctrl: np.ndarray, trt: np.ndarray) -> tuple[bool, int]:
"""mSPRT: 50개 단위로 데이터 추가하며 연속 모니터링"""
sigma_sq = SIGMA ** 2
for n in range(50, len(ctrl) + 1, 50):
mean_diff = np.mean(trt[:n]) - np.mean(ctrl[:n])
v_ratio = sigma_sq / (sigma_sq + n * TAU_SQ)
exp_term = (TAU_SQ * n**2 * mean_diff**2) / (2 * sigma_sq * (sigma_sq + n * TAU_SQ))
if np.sqrt(v_ratio) * np.exp(exp_term) >= 1 / ALPHA:
return True, n
return False, MAX_N
def gst_test(ctrl: np.ndarray, trt: np.ndarray) -> tuple[bool, int]:
"""GST (O'Brien-Fleming): 사전 계획된 5번 중간 분석"""
analysis_fractions = [0.2, 0.4, 0.6, 0.8, 1.0]
prev_spent = 0.0
for frac in analysis_fractions:
n = int(frac * MAX_N)
cum_spent = 2 * (1 - stats.norm.cdf(
stats.norm.ppf(1 - ALPHA / 2) / np.sqrt(frac)
))
local_alpha = cum_spent - prev_spent
prev_spent = cum_spent
z = (np.mean(trt[:n]) - np.mean(ctrl[:n])) / (SIGMA * np.sqrt(2 / n))
if 2 * (1 - stats.norm.cdf(abs(z))) < local_alpha:
return True, n
return False, MAX_N
results = {"mSPRT": [0, 0], "GST ": [0, 0]}
for _ in range(N_SIM):
ctrl = np.random.normal(0, SIGMA, MAX_N)
trt = np.random.normal(TRUE_EFFECT, SIGMA, MAX_N)
for name, fn in [("mSPRT", msprt_test), ("GST ", gst_test)]:
rejected, n = fn(ctrl, trt)
results[name][0] += rejected
results[name][1] += n
print(f"진짜 효과={TRUE_EFFECT}, σ={SIGMA}, 시뮬레이션={N_SIM}회\n")
print(f"{'방법':<8} {'검정력':>8} {'평균 표본':>12}")
for name, (power, total_n) in results.items():
print(f"{name:<8} {power/N_SIM:>8.3f} {total_n/N_SIM:>12.0f}")이 코드를 직접 실행하면 두 방법의 검정력과 평균 표본 크기의 실제 트레이드오프를 확인할 수 있다. 일반적으로 GST는 사전에 계획된 분석 시점에서 더 높은 검정력을 보이고, mSPRT는 더 유연하지만 같은 표본 크기에서 검정력이 약간 낮다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| Peeking 안전성 | 연속 모니터링 시에도 1종 오류율 α 보장, 기존 t-test의 핵심 약점 해소 |
| 표본 크기 유연성 | 실험 기간을 사전에 확정하지 않아도 됨 |
| 조기 종료 | 충분한 증거 확보 시 즉시 결론 도출 가능, 자원 낭비 방지 |
| τ² 강건성 | 혼합 분산 설정이 1 |
| 이론적 완결성 | e-value 프레임워크와 연결, 복합 귀무가설·회귀 조정으로 확장 가능 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 검정력 손실 | 표본 크기를 알 때 GST 대비 검정력 낮음 | 배치 환경이면 GST 검토 |
| τ² 선택 난이도 | 설정이 성능에 영향, 도메인 지식 필요 | MDE 사전 정의 후 역산 |
| 비정규 메트릭 복잡도 | 비율·카운트 등에서 closed-form 없어 수치 적분 필요 | Netflix 공개 구현 참조 |
| 다중 검정 미보호 | 여러 메트릭 동시 적용 시 FWER 미제어 | Bonferroni 또는 e-value 결합 적용 |
| 양측 검정 주의 | 방향성 있는 대립 가설 설정 시 별도 처리 필요 | 단측/양측 구분 명확히 설계 |
실무에서 가장 흔한 실수
- τ²를 감으로 설정하고 시작한다 — MDE를 먼저 정의하고 α, σ, 기대 표본 크기 M을 기반으로 역산해야 한다. 감으로 설정한 τ²는 검정력을 최대 20%까지 떨어뜨릴 수 있다.
- mSPRT를 여러 메트릭에 동시 적용하면서 α를 보정하지 않는다 — 메트릭이 10개면 Bonferroni 보정으로 각 검정의 α를 0.05/10 = 0.005로 낮추거나, e-value 곱셈 결합을 사용해야 한다.
- 배치 분석 환경에서 무조건 mSPRT를 선택한다 — 데이터가 일별로 집계되고 실험 기간을 사전에 알 수 있다면 GST가 같은 α에서 더 높은 검정력을 제공한다. Spotify의 선택은 근거가 있다.
마치며
mSPRT는 "언제 봐도 안전한" A/B 테스트를 마팅게일 이론으로 수학적으로 보장하지만, Optimizely·Netflix·Spotify의 사례가 보여주듯 데이터 환경이 방법론 선택을 결정한다. 실시간 스트림이냐 일별 배치냐, 메트릭이 단순 정규 분포냐 분위수냐에 따라 최선의 구현이 완전히 달라진다.
지금 바로 시작할 수 있는 3단계 (1→2→3 순서로 진행하면 효과적이다):
- 비교 코드를 자신의 메트릭 수치로 바꿔 실행해본다 — 위 시뮬레이션 코드의
TRUE_EFFECT와TAU_SQ를 자신의 도메인 MDE로 교체하면 mSPRT와 GST의 검정력 트레이드오프를 직접 확인할 수 있다. Python보다 통계 패키지가 편하다면 R의MSPRT패키지(install.packages("MSPRT"))도 같은 시뮬레이션을 지원한다. - Netflix 공개 GitHub Gist를 실행해본다 — 카운트 데이터용 anytime-valid 검정 코드를 실제 실험 데이터에 적용하고, 기존 χ² 검정 결과와 비교해본다.
- 자신의 분석 환경을 진단한다 — 데이터가 실시간 스트림이면 mSPRT, 일별 배치이고 실험 기간이 고정 가능하면 GST를 우선 검토한다.
다음 글: e-value와 SAVI(Safe Anytime-Valid Inference) 프레임워크 — 이 글에서 잠깐 언급한 e-value는 실은 mSPRT보다 훨씬 넓은 문제 해결. 복합 귀무가설, 회귀 조정, 다변량 검정까지 anytime-valid 추론을 확장하는 최신 이론 정리
참고 자료
- Always Valid Inference: Continuous Monitoring of A/B Tests | Johari et al., arXiv 1512.04922 — Optimizely Stats Engine의 이론적 기반, mSPRT anytime-valid 보장의 핵심 논문
- Sequential A/B Testing Keeps the World Streaming Part 1: Continuous Data | Netflix TechBlog — Netflix의 연속 데이터용 anytime-valid 신뢰 시퀀스 구현 상세
- Sequential A/B Testing Keeps the World Streaming Part 2: Counting Processes | Netflix TechBlog — Netflix의 카운트 데이터용 anytime-valid 검정, GitHub Gist 링크 포함
- Choosing a Sequential Testing Framework — Comparisons and Discussions | Spotify Engineering — Spotify가 mSPRT 대신 GST를 선택한 이유와 시뮬레이션 근거
- Bringing Sequential Testing to Experiments with Longitudinal Data (Part 2) | Spotify Engineering — Spotify의 종단 데이터용 순차 검정 확장 프레임워크
- Stats Accelerator Overview | Optimizely Support — Optimizely Stats Accelerator(동적 트래픽 배분) 공식 문서
- Optimizely Stats Engine Whitepaper | Optimizely — Stats Engine 수학적 구현 원본 설명서
- Sequential Test for Practical Significance: Truncated mSPRT | arXiv 2509.07892 — MDE를 명시적으로 통합한 절단형 mSPRT 연구(2025)
- Anytime-Valid Inference For Multinomial Count Data | Netflix Research — Netflix의 다항 카운트 데이터용 anytime-valid 추론(NeurIPS 발표)
- Mastering the mSPRT for A/B Testing | HackerNoon — mSPRT 실무 적용 튜토리얼
- Comparison of Statistical Power: SPRT, AGILE, Always Valid Inference | Analytics-Toolkit.com — 순차 검정 방법론 간 검정력 정량 비교 분석
- Sequential Testing for Statistical Inference | Amplitude Experiment Docs — Amplitude의 mSPRT 기반 구현 참조 문서
- Sequential Tests | Spotify Confidence Documentation — Spotify Confidence 플랫폼의 GST 기반 순차 검정 공식 문서
- E-values | Wikipedia — e-value 개념 및 mSPRT와의 이론적 연결 설명