무용성 중단(Futility Stopping)과 계층적 검정으로 카나리를 통계적으로 자동 종료하는 법: Beta-Spending 설계 실전 가이드
이 글은 A/B 테스트 기초(p-value, 귀무가설)와 카나리 배포 경험을 전제합니다. 카나리 파이프라인을 운영하는 백엔드·데이터 엔지니어에게 적합합니다.
카나리 배포를 운영하다 보면 두 가지 고통스러운 상황이 반복된다. 첫째, 명백히 효과 없는 버전을 최종 배포 시점까지 억지로 끌고 가야 하는 낭비. 구매 전환율이 첫 번째 중간 분석에서 이미 기준점 아래였지만, "데이터가 더 필요하다"는 이유로 불필요한 사용자 노출이 계속된다. 둘째, 구매 전환율·응답 지연·에러율 등 10개 메트릭을 동시에 검정하다가 "사실 아무것도 변하지 않았는데도" 위양성이 터지는 혼란. 각 메트릭을 독립적으로 α=0.05로 검정하면 적어도 하나가 위양성일 확률은 1-(0.95)^10 ≈ 40%다.
이 글에서는 Beta-Spending으로 무용성 경계를 설정해 실패할 카나리를 조기 종료하고, 계층적 검정(Hierarchical Testing)으로 다중 메트릭 환경의 FWER을 α 이하로 통제하는 전체 설계 방법을 다룬다. Statsig·Eppo 같은 실험 플랫폼의 실제 구현, R의 gsDesign 패키지를 활용한 경계 계산, Python 파이프라인 통합, Kubernetes Argo Rollouts 연동까지 단계별로 살펴본다. 이 글 끝에서 여러분의 카나리 파이프라인에 즉시 붙일 수 있는 Python 코드와 R 스크립트를 얻어 갈 수 있다.
핵심 개념
이 섹션에서 다루는 세 가지 도구—Alpha-Spending, Beta-Spending, 계층적 검정—는 독립적으로 사용할 수도 있지만, 이 세 가지를 조합하면 "중간에 여러 번 들여다보아도 통계적으로 유효하고, 효과 없는 실험은 자동으로 멈추고, 다중 메트릭에서 위양성이 누적되지 않는" 카나리 파이프라인이 완성된다. 각 개념을 차례로 쌓아 올려보자.
Alpha-Spending과 Beta-Spending: 오류 예산의 두 축
순차 검정(Sequential Testing)은 데이터를 모두 모으기 전에 중간에 여러 번 들여다보는 설계다. 문제는, 중간에 볼 때마다 위양성이 누적된다는 것이다. 예를 들어 동일한 데이터를 매주 들여다보면서 p < 0.05가 나오면 "유의미하다"고 판정하면 실제 오류율이 α를 훨씬 초과한다. 이를 해결하는 핵심 아이디어가 오류 예산(error budget) 분산 배분이다.
| 구분 | 제어 대상 | 경계 방향 | 경계 초과/미달 시 |
|---|---|---|---|
| Alpha-Spending | 타입 I 오류 (위양성, α) | 상한(upper bound) | 초과 → 효과 탐지, 배포 진행 |
| Beta-Spending | 타입 II 오류 (위음성, β) | 하한(lower bound) | 미달 → 무용성 판정, 카나리 종료 |
Alpha-Spending의 사실상 표준인 Lan-DeMets(1983) 방법은 중간 분석 횟수를 사전에 고정하지 않아도 된다는 장점이 있다. O'Brien-Fleming 유형 함수는 초반 중간 분석에서 α를 거의 소비하지 않아, 마지막 분석이 고정 표본 검정과 거의 동일한 기준을 유지한다.
용어 정리 — 무용성 중단(Futility Stopping): 현재까지 수집된 데이터로 볼 때 실험을 끝까지 진행해도 유의미한 결과가 나올 가능성이 낮다고 판단해 조기에 실험을 종료하는 행위. 자원 낭비와 불필요한 사용자 노출을 줄인다.
조건부 검정력(Conditional Power)과 동적 무용성 경계
2024년 _Biometrical Journal_에 Ni et al.이 발표한 CP 기반 β-spending 함수는 기존의 고정된 무용성 경계를 한 단계 진화시켰다.
**조건부 검정력(Conditional Power, CP)**은 현재까지 누적된 데이터를 기준으로 "최종 분석에서 귀무가설을 기각할 확률"을 실시간으로 추정한다. 고전적 β-spending이 분석 시점(정보 분율, information fraction)만으로 경계를 정했다면, CP 기반 방법은 실제 관측된 효과 크기의 추세를 반영해 무용성 경계를 동적으로 조정한다.
# Python 3.9+
import numpy as np
from scipy import stats
def conditional_power(
z_current: float, # 현재 시점의 검정 통계량
t: float, # 정보 분율 (현재 수집된 정보 / 계획된 총 정보)
delta: float, # 사전 가정 효과 크기 (표준화된 드리프트 파라미터)
alpha: float = 0.025 # 단측 유의 수준
) -> float:
"""
고정 효과 크기 가정 하 조건부 검정력 계산.
수식 해설: 정보 분율 t 시점에서 관측된 Z 통계량 z_current를 기반으로
최종 시점(t=1)의 검정 통계량 기댓값을 계산한다.
여기서 delta는 n_total=1로 표준화된 드리프트 파라미터다.
실제 샘플 수 기반 계산이 필요하면 delta에 sqrt(n_total)을 곱한 값을 전달하라.
CP < 0.2이면 무용성 판정 후보.
"""
z_alpha = stats.norm.ppf(1 - alpha)
# 현재까지 수집된 정보(t)와 남은 정보(1-t)의 가중 합산
z_final_mean = z_current * np.sqrt(t) + delta * np.sqrt(1 - t)
cp = 1 - stats.norm.cdf(z_alpha - z_final_mean)
return cp
# 예: 50% 데이터 수집 시점, 효과가 절반 수준만 관측됨
cp = conditional_power(z_current=0.8, t=0.5, delta=2.0)
print(f"조건부 검정력: {cp:.3f}") # → 0.183 → 무용성 경계 검토주의 — 카나리 환경에서 트래픽 비율과 정보 분율(information fraction)은 같지 않을 수 있다. 카나리 그룹과 베이스라인 그룹의 분산이 다르거나 응답률이 변하면, 실제 수집된 통계적 정보량이 트래픽 비율과 달라진다. 이 점을 무시하면 무용성 경계가 예상보다 일찍 또는 늦게 트리거된다. 실무에서는 분산을 실시간으로 추적해 정보 분율을 별도 계산하는 것을 권장한다.
계층적 검정(Hierarchical Testing)과 FWER 제어
10개 메트릭을 각각 α=0.05로 검정하면 최소 하나가 위양성일 확률은 1-(0.95)^10 ≈ 40%에 달한다. **Family-Wise Error Rate(FWER)**가 폭발하는 이유다.
단순 Bonferroni 보정(α/k)은 FWER을 통제하지만 검정력을 과도하게 소모한다. k=10이면 각 메트릭의 α는 0.005까지 낮아져 실제 효과도 탐지하기 어려워진다. 또한 Bonferroni는 메트릭 간 독립성을 가정하지만 실제 메트릭들(구매 전환율·세션 시간·클릭률)은 서로 양의 상관관계를 갖는 경우가 많아 결과가 과보수적으로 나온다. 이 과보수성을 완화하려면 Permutation 기반 다중 검정이나 Šidák 보정을 검토할 수 있다.
**계층적 검정(Hierarchical Testing / Gatekeeping)**은 메트릭 간의 논리적 우선순위를 활용해 이 손실을 최소화한다.
[1차 메트릭: 구매 전환율] ← α 전체 배분
│
│ 유의 (p < α)
▼
[2차 메트릭 그룹: 세션 시간, 클릭률] ← 각각 α/2로 Bonferroni
│
│ 그룹 내 하나 이상 유의 (disjunctive)
▼
[3차 메트릭 그룹: 페이지 로드, API 에러율] ← 각각 α/2로 Bonferroni수학적 보장: 1차 메트릭 검정에 α-level 절차를 적용하고, 2차 이하 메트릭은 상위 계층 기각을 조건으로만 검정하면 전체 FWER은 α 이하로 통제된다. 이는 폐합 검정 원리(Closed Testing Principle)에 의해 증명된다.
위 구조에서 계층 내 "하나라도 유의하면 다음 게이트 열림"은 **분리적 게이트키핑(disjunctive gatekeeping)**이다. 이는 "모두 유의해야 다음 게이트 열림"인 연언적(conjunctive) 방식보다 검정력이 높지만 다소 덜 보수적이다. 어느 쪽이든 FWER ≤ α 보장은 성립하되, 선택한 기준은 실험 시작 전 사전 등록해야 한다.
용어 정리 — 게이트키핑(Gatekeeping): 상위 계층 검정을 통과해야만 하위 계층 검정의 문이 열리는 다중 검정 설계. 허가 없이 문을 열면 FWER이 제어되지 않는다.
그룹 순차 설계의 전체 구조
Alpha-Spending, Beta-Spending, 계층적 검정을 결합한 전체 프레임워크는 다음과 같다.
Z-통계량
4 ┤
│ ━━┓ ← 상한(efficacy): O'Brien-Fleming은 초반에 매우 높고
3 ┤ ┗━━┓ 분석이 거듭될수록 점차 낮아짐
│ ┗━━━┓
2 ┤ ┗━━━━━━━━━━━ (최종: ≈ 고정 표본 기준과 유사)
1 ┤
0 ┼────────────────────────────────► 정보 분율 t
-1 ┤ ─ ─ ─ ─┐ ← 하한(futility): 초반엔 음수(거의 무조건 통과)
│ └ ─ ─┐ 분석이 거듭될수록 점차 높아짐
1 ┤ └ ─ ─ ─ ─ ─ (최종: 상한과 수렴)
│
t=0.10 t=0.25 t=0.50 t=1.00
통계량 > 상한 → 효과 탐지 → 배포 진행 또는 즉시 롤백
통계량 < 하한 → 무용성 → 카나리 종료
두 경계 사이 → 계속 진행O'Brien-Fleming 경계의 핵심 특성: 초반(t=0.10)의 상한은 매우 엄격해서(예: Z > 3.47) 섣부른 배포 결정을 막고, 초반의 하한은 매우 느슨해서(예: Z < -0.48) 노이즈로 인한 조기 종료를 방지한다. 후반(t=1.0)에서는 양쪽 경계가 수렴해 최종 결정을 내린다.
실전 적용
아래 네 가지 예시는 하나의 가상 e-commerce API 서버 카나리 배포를 공통 시나리오로 사용한다.
- 1차 메트릭: 구매 전환율(
purchase_rate) - 2차 메트릭: 세션 시간(
session_time), 클릭률(click_rate) - 3차 메트릭: 페이지 로드 시간(
page_load_ms), API 에러율(error_rate) - 체크포인트: 트래픽 10% → 25% → 50% → 100%
예시 1: R gsDesign으로 카나리 배포 경계 설계
# install.packages("gsDesign")
library(gsDesign)
# 4회 분석(중간 3회 + 최종 1회), 단측 α=0.025, β=0.20(검정력 80%)
# delta1 = 0.3: Cohen's d 기준 "소-중간 효과 크기"
# 구매 전환율 맥락에서 약 0.5%p 차이에 해당.
# 과거 A/B 테스트 히스토리에서 "탐지할 가치 있는 최소 효과"로 결정하라.
design <- gsDesign(
k = 4, # 중간 분석 3회 + 최종 1회
test.type = 2, # 양측 검정 (상한/하한 모두)
alpha = 0.025, # 단측 α
beta = 0.20, # 타입 II 오류 (검정력 80%)
sfu = sfLDOF, # Alpha-Spending: O'Brien-Fleming 유사
sfl = sfLDOF, # Beta-Spending: 무용성 경계
delta1 = 0.3, # 탐지 목표 최소 효과 크기 (Cohen's d)
n.fix = 1000 # 고정 표본 기준 샘플 수
)
print(design)
# 출력 예시:
# Analysis N Z (upper) Z (lower/futility)
# 1 310 3.47 -0.48
# 2 620 2.78 0.94
# 3 930 2.29 1.71
# 4 1051 2.02 2.02| 컬럼 | 의미 |
|---|---|
N |
해당 분석 시점까지 필요한 누적 샘플 수 (총 1051로 고정 표본 1000 대비 약 5% 인플레이션) |
Z (upper) |
이 값 초과 시 효과 탐지 → 배포 진행 |
Z (lower) |
이 값 미달 시 무용성 판정 → 카나리 종료 |
1차 중간 분석의 하한이 -0.48인 이유: 아직 데이터가 10%만 쌓인 초반에는 노이즈가 커 검정 통계량이 약간 음수로 나와도 효과가 없다고 단정할 수 없다. -0.48 미만이면 효과가 반대 방향으로 뚜렷하게 나타나는 경우—명백한 회귀(regression)—에만 종료한다는 의미다. 후반으로 갈수록 하한이 올라가(0.94 → 1.71 → 2.02) 무용성 판단이 점점 엄격해진다.
주목: O'Brien-Fleming은 초반에 α를 아껴(상한 3.47) 마지막 분석이 고정 표본 기준(2.02)과 유사하게 유지된다. 반면 무용성 하한은 시간이 지날수록 엄격해진다.
예시 2: Python으로 카나리 배포 파이프라인에 통합
gsDesign에서 계산한 경계값을 Python 파이프라인에 적용한다. 체크포인트 선택 시 "아직 도달하지 않은 미래 시점"이 아닌 "가장 최근에 통과한 시점" 을 기준으로 삼는 것이 중요하다.
# Python 3.9+
from dataclasses import dataclass
from typing import Literal
@dataclass
class SequentialBoundary:
upper: float # efficacy (효과 탐지) 경계
lower: float # futility (무용성) 경계
# 예시 1의 gsDesign 출력에서 가져온 경계값
BOUNDARIES: dict[float, SequentialBoundary] = {
0.10: SequentialBoundary(upper=3.47, lower=-0.48),
0.25: SequentialBoundary(upper=2.78, lower=0.94),
0.50: SequentialBoundary(upper=2.29, lower=1.71),
1.00: SequentialBoundary(upper=2.02, lower=2.02),
}
def evaluate_canary(
z_stat: float,
traffic_fraction: float
) -> Literal["continue", "deploy", "stop_futility"]:
"""
현재 검정 통계량과 트래픽 비율로 카나리 배포 결정.
가장 최근에 통과한 체크포인트 기준을 사용한다.
"""
checkpoints = sorted(BOUNDARIES.keys())
passed = [c for c in checkpoints if c <= traffic_fraction]
if not passed:
return "continue" # 아직 첫 체크포인트 미도달
checkpoint = passed[-1] # 가장 최근에 통과한 시점
boundary = BOUNDARIES[checkpoint]
if z_stat >= boundary.upper:
return "deploy" # 효과 탐지 → 전체 배포
elif z_stat <= boundary.lower:
return "stop_futility" # 무용성 → 카나리 종료
else:
return "continue" # 다음 분석까지 유지
# 사용 예시: 25% 체크포인트에서 z=0.72
result = evaluate_canary(z_stat=0.72, traffic_fraction=0.25)
print(f"카나리 판정: {result}") # → stop_futility (0.72 < 0.94 하한)예시 3: 다중 메트릭 계층적 검정 구현
게이트키핑 로직은 p-value 계산과 분리하는 것이 실용적이다. p-value는 메트릭 특성(비율, 연속형, 카운트 등)에 따라 다른 방법을 쓰고, 게이트키핑 로직만 공통으로 재사용한다.
# Python 3.9+
import numpy as np
from scipy import stats as scipy_stats
def compute_proportion_pvalue(
obs_count: int, obs_total: int,
ctrl_count: int, ctrl_total: int
) -> float:
"""비율 차이에 대한 양측 z-검정 p-value."""
p_obs = obs_count / obs_total
p_ctrl = ctrl_count / ctrl_total
p_pool = (obs_count + ctrl_count) / (obs_total + ctrl_total)
se = np.sqrt(p_pool * (1 - p_pool) * (1 / obs_total + 1 / ctrl_total))
if se == 0:
return 1.0
z = (p_obs - p_ctrl) / se
return float(2 * (1 - scipy_stats.norm.cdf(abs(z))))
def hierarchical_gate(
p_values: dict[str, float],
hierarchy: list[list[str]],
alpha: float = 0.05
) -> dict[str, dict]:
"""
계층적 게이트키핑: 상위 계층이 유의해야 하위 계층 검정 진행.
각 계층 내에서 Bonferroni 보정 후 "하나라도 유의"(disjunctive)하면
다음 계층 게이트를 연다. FWER <= alpha 보장.
더 보수적인 "모두 유의"(conjunctive) 방식으로 바꾸려면
gate_open = all(sig_list) 로 변경하라.
"""
results: dict[str, dict] = {}
gate_open = True
for metric_group in hierarchy:
if not gate_open:
for name in metric_group:
results[name] = {"tested": False, "reason": "gate_closed"}
continue
alpha_adj = alpha / len(metric_group) # 계층 내 Bonferroni
level_any_significant = False
for name in metric_group:
p = p_values.get(name, 1.0)
significant = p < alpha_adj
if significant:
level_any_significant = True
results[name] = {
"tested": True,
"p_value": p,
"alpha_used": alpha_adj,
"significant": significant,
}
gate_open = level_any_significant # disjunctive gatekeeping
return results
# ─────────────────────────────────────────────────────────────
# 사용 예시: e-commerce 카나리, 25% 체크포인트 데이터 (각 500명)
# ─────────────────────────────────────────────────────────────
raw_p_values = {
# 1차: 구매 전환율 (카나리 27/500 vs 베이스라인 25/500)
"purchase_rate": compute_proportion_pvalue(27, 500, 25, 500),
# 2차: 세션 시간, 클릭률 (실제로는 t-test 등 적합한 방법 사용)
"session_time": 0.08,
"click_rate": 0.03,
# 3차: 페이지 로드, API 에러율
"page_load_ms": 0.12,
"error_rate": 0.04,
}
hierarchy = [
["purchase_rate"], # 1차: 가장 중요
["session_time", "click_rate"], # 2차: 1차 통과 후
["page_load_ms", "error_rate"], # 3차: 2차 통과 후
]
gate_results = hierarchical_gate(raw_p_values, hierarchy, alpha=0.05)
for name, r in gate_results.items():
if r["tested"]:
sig = "★ 유의" if r["significant"] else " 비유의"
print(f"{sig} | {name}: p={r['p_value']:.4f} (α_adj={r['alpha_used']:.4f})")
else:
print(f" 미검정 | {name}: {r['reason']}")예시 4: Kubernetes + Argo Rollouts에서 실제 적용
Prometheus에서 z-통계량을 recording rule로 미리 계산한 뒤, Argo Rollouts의 AnalysisTemplate에 경계값을 연결한다. 아래 futility_lower 값은 예시 1의 gsDesign 출력에서 직접 가져온 것이다.
# argo-rollout.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: api-server-canary
spec:
strategy:
canary:
steps:
- setWeight: 10 # 1차 체크포인트: 10% 트래픽
- pause: {duration: 5m}
- analysis:
templates:
- templateName: sequential-boundary-check
args:
- name: checkpoint
value: "0.10"
- name: futility_lower # 예시 1 참조: 10% 하한 = -0.48
value: "-0.48"
- setWeight: 25 # 2차 체크포인트: 25% 트래픽
- pause: {duration: 10m}
- analysis:
templates:
- templateName: sequential-boundary-check
args:
- name: checkpoint
value: "0.25"
- name: futility_lower # 예시 1 참조: 25% 하한 = 0.94
value: "0.94"
- setWeight: 50
- pause: {duration: 20m}
---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: sequential-boundary-check
spec:
args:
- name: checkpoint
- name: futility_lower
metrics:
- name: z-stat-purchase-rate
provider:
prometheus:
address: http://prometheus:9090
# Prometheus recording rule에서 카나리 vs 베이스라인 z-통계량을 미리 계산
query: |
canary_z_statistic{
metric="purchase_rate",
checkpoint="{{args.checkpoint}}"
}
# 무용성 하한(예시 1 gsDesign 출력값)을 초과해야 "계속 진행" 성공
successCondition: "result[0] > {{args.futility_lower}}"
failureCondition: "result[0] <= {{args.futility_lower}}"장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 빠른 실패 탐지 | 효과 없는 카나리를 전체 사용자 노출 전 조기 종료해 피해 최소화 |
| 자원 절약 | 불필요한 실험 지속 비용(컴퓨팅, 엔지니어링 시간) 절감 |
| 통계적 엄밀성 | FWER 제어로 다중 메트릭에서 위양성 누적 방지 |
| 유연한 모니터링 | Lan-DeMets 방식은 중간 분석 횟수와 시점을 사전 고정 불필요 |
| 설계 유연성 | Non-binding futility boundary로 사업적 판단 여지 확보 |
| 검정력 효율 | Bonferroni 대비 계층적 검정이 2차 메트릭에서 더 높은 검정력 유지 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 거짓 무용성 위험 | 실제 효과가 있어도 조기 중단 가능 | Non-binding 경계 사용, CP 임계값을 보수적으로(0.1~0.15) 설정 |
| 사전 계획 의존성 | α/β-spending 함수, 효과 크기를 실험 전에 사전 등록해야 유효 | Experiment Design Doc 프로세스 의무화 |
| 샘플 인플레이션 | 동등 검정력을 위해 고정 표본보다 더 많은 총 샘플 필요 | gsDesign n.I 필드로 인플레이션 인수 사전 계산 |
| 계층 설계 복잡성 | 메트릭 간 논리적 우선순위 정의 실수 시 중요 지표 탐지 기회 소실 | 제품·통계 팀 공동 워크숍으로 계층 구조 사전 합의 |
| 상관 메트릭 과보수 | 실제 메트릭은 서로 상관되어 있어 Bonferroni 보정이 과보수적으로 동작할 수 있음 | Permutation 기반 다중 검정 또는 Šidák 보정 검토 |
용어 보충 — Binding vs. Non-binding Futility Boundary: Binding(구속형)은 하한 경계를 하향 통과하면 반드시 중단해야 하고 이를 α 계산에 반영한다. Non-binding은 통계적으로 중단을 권고하되 사업적 판단으로 계속 진행할 수 있다. Non-binding을 선택하면 FWER 보장이 다소 약해지지만 운영 유연성이 높아진다.
실무에서 가장 흔한 실수
- 분석 후 경계를 소급 조정하기: "이번 한 번만" 경계를 조정하는 순간 FWER 보장이 무너진다. 경계는 실험 시작 전 코드/문서에 잠금(lock)해야 한다.
- 1차 메트릭 없이 모든 메트릭을 동등하게 취급하기: 계층 구조 없이 10개를 Bonferroni로 나누면 실제로 중요한 지표의 검정력이 과도하게 낮아진다. 반드시 1~2개 Primary 메트릭을 먼저 정의하라.
- Beta-Spending만 설정하고 Alpha-Spending을 생략하기: 무용성 경계만 있고 효능 경계가 없으면 중간 분석에서 효과를 조기 탐지하는 기준이 사라진다. 두 경계를 반드시 함께 설계해야 한다.
마치며
Beta-Spending으로 무용성 경계를 설정하고 계층적 검정으로 FWER을 통제하면, 카나리 배포는 "느린 관찰"에서 "자동화된 통계적 의사결정"으로 격상된다. Statsig·Eppo 같은 실험 플랫폼이 이 방식을 기본 옵션으로 채택한 것은 그 실용성을 이미 검증했기 때문이다.
지금 바로 시작할 수 있는 3단계:
- R에서 경계 계산 실행:
install.packages("gsDesign")후 아래 코드로 현재 카나리 샘플 규모에 맞는 α/β 경계값을 출력해 팀 슬랙에 공유해보자. 첫 숫자를 팀과 공유하는 것만으로도 설계 논의가 시작된다. gsDesign(k=4, test.type=2, alpha=0.025, beta=0.20, sfu=sfLDOF, sfl=sfLDOF, delta1=0.3, n.fix=1000)- 메트릭 계층 구조 문서화: 현재 실험에서 사용하는 메트릭 목록을 꺼내 "이것이 유의하지 않으면 나머지를 볼 필요가 없다"는 1차 메트릭 1~2개를 명시적으로 지정하고 Experiment Design Doc에 기록한다.
- Argo Rollouts 또는 Flagger AnalysisTemplate에 z-통계량 연동: Prometheus recording rule로 카나리 vs 베이스라인 z-통계량을 미리 계산하고, 1단계에서 계산한 하한 경계값을
successCondition에 입력해 자동 무용성 판정 파이프라인을 구성한다.
마지막으로 한 가지 한계를 알아두자. 이 글의 방법은 중간 분석 횟수와 체크포인트를 사전에 정해야 한다는 제약이 있다. 이 제약을 해제—즉, 언제든 멈추거나 계속 들여다볼 수 있는 Anytime-Valid 검정—를 가능하게 하는 것이 다음 글의 주제인 mSPRT다.
다음 글: mSPRT(Mixture Sequential Probability Ratio Test)가 Anytime-Valid p-value를 어떻게 보장하는지, 그리고 Netflix·Optimizely·Spotify의 구현이 어떻게 다른지 비교 분석
참고 자료
- What is Beta-Spending? | Analytics ToolKit Glossary
- Beta spending function based on conditional power in group sequential design (2024) | PubMed
- Sequential A/B Testing Keeps the World Streaming — Part 1 | Netflix TechBlog
- Sequential A/B Testing Keeps the World Streaming — Part 2 | Netflix TechBlog
- Beyond Bonferroni: Hierarchical Multiple Testing in Empirical Research | NBER Working Paper (2025)
- Hierarchical Testing of Multiple Endpoints in Group-Sequential Trials | Statistics in Medicine
- A gatekeeping procedure to test a primary and a secondary endpoint in a group sequential design | PubMed
- Futility Monitoring in Clinical Trials | PMC/NIH
- gsDesign: Spending Function Overview | R Documentation
- Defining Group Sequential Boundaries | rpact
- Choosing a Sequential Testing Framework | Spotify Engineering (2023)
- Bonferroni Correction for Multiple Comparisons | Statsig Docs
- Sequential Testing | Eppo Docs
- Introducing Kayenta: Automated Canary Analysis Tool | Google Cloud Blog
- A Flexible Futility Monitoring Method with Time-Varying Conditional Power Boundary | PMC
- A Gentle Introduction to Group Sequential Design | CRAN gsDesign