Flagger Webhook에 Alpha Spending Sequential Testing 구현하기 — 통계적 조기 탈출로 Canary 롤백을 최대 66% 단축하는 방법
Canary 배포를 운영해본 개발자라면 이런 상황을 겪어봤을 것이다. 새 버전을 10% 트래픽에 올려두고 5분 뒤 메트릭을 슬쩍 들여다봤더니 에러율이 올라가 있다. "아직 샘플이 충분하지 않으니 좀 더 보자"는 직관과 "이미 나쁜 신호가 보이는데 왜 기다려야 하지?"라는 직관이 충돌한다. 결국 애매한 상태로 대시보드를 반복 새로고침하다가, 통계적 근거 없이 수동으로 롤백 버튼을 누르게 된다. 혹은 반대로 자동 임계치가 뒤늦게 발동되어 그 사이 더 많은 사용자가 피해를 입는다.
이 두 시나리오의 공통 원인은 고정 표본 A/B 테스트의 "peeking problem"이다. 데이터를 반복적으로 들여다볼수록 Type I 오류율(거짓 양성)이 인플레이션되어 통계적 신뢰도가 무너진다. 전통적인 임계값 비교는 이 문제를 해결하지 못한다. Sequential Testing과 Alpha Spending 함수를 Flagger의 웹훅 게이팅 레이어에 결합하면, 매 중간 분석 시점을 통계적으로 올바르게 검정하면서 문제가 확실해지는 즉시 자동으로 탈출할 수 있고, 이를 통해 평균 실험 기간을 최대 66% 단축할 수 있다. 이 글에서는 Alpha Spending 함수의 수학적 원리부터, Flagger 웹훅 서비스를 직접 구현해 연동하는 방법까지 단계별로 다룬다.
핵심 개념
Peeking Problem: 왜 반복 확인이 위험한가
고정 표본 검정(fixed-sample test)은 사전에 정한 N개의 샘플이 모두 모인 뒤 단 한 번 검정을 수행한다는 전제 위에 성립한다. 하지만 Canary 배포에서는 메트릭을 매 분마다 확인한다. k번 반복 확인하면 유의 수준 α=0.05라도 실제 Type I 오류율은 훨씬 높아진다.
import numpy as np
def peeking_inflation(alpha: float, num_peeks: int) -> float:
"""반복 검정 시 실제 Type I 오류율 추정 (Bonferroni 상한 — 실제보다 보수적)"""
# 각 검정이 독립적이라 가정한 보수적 상한값
# 실제 상관된 관측값에서는 이보다 낮게 나옴 (12회 peek 시 약 0.30~0.35 수준)
return 1 - (1 - alpha) ** num_peeks
# 5분 간격으로 60분 동안 12번 들여다보면
print(peeking_inflation(0.05, 12)) # ≈ 0.46 (Bonferroni 상한)
# 실제 시뮬레이션 기반 추정치는 약 0.30~0.35 수준이지만,
# 어느 쪽이든 목표 오류율 0.05를 대폭 초과한다는 사실은 변하지 않는다Sequential Testing은 이 문제를 해결하기 위해 중간 분석(interim analysis)을 통계적으로 올바르게 처리한다. 핵심 아이디어는 전체 유의 수준 α를 각 중간 분석 시점에 조금씩 "소비"하되, 총합이 α를 넘지 않도록 배분하는 것이다.
정보 분율(Information Fraction) τ ∈ [0, 1]: 현재까지 수집된 샘플 수를 계획된 전체 샘플 수로 나눈 비율. τ = 0.3이면 계획의 30%가 진행된 시점임을 의미한다. 시간 기반이 아닌 샘플 수 기반으로 계산해야 트래픽이 불균등한 서비스에서도 통계적 의미를 유지할 수 있다.
Alpha Spending 함수: 세 가지 전략
Lan과 DeMets(1983)가 제안한 Alpha Spending 함수는 정보 분율 τ에 따라 누적 소비 α(τ)를 정의한다.
배경을 먼저 이해하는 것이 중요하다. O'Brien-Fleming(1979)과 Pocock 방법은 원래 등간격 중간 분석(equal-spaced looks) 을 전제로 개발되었다. Canary 배포에서는 트래픽 양에 따라 분석 타이밍이 불규칙해질 수 있는데, 이런 경우를 위해 Lan-DeMets의 Alpha Spending 확장이 필요하다. 세 방법을 단순히 "다른 스타일"로 보기보다, O'Brien-Fleming/Pocock은 분석 스케줄이 고정된 경우, Lan-DeMets는 스케줄이 유동적인 경우에 적합하다는 맥락으로 이해해야 한다.
| 방법 | 초기 보수성 | 조기 탈출 가능성 | 최종 명목 유의 수준 | 권장 사용 케이스 |
|---|---|---|---|---|
| O'Brien-Fleming | 매우 높음 | 낮음(강한 증거 필요) | 단일 검정과 거의 동일 | 회귀 감지, 안전 중단, 고정 분석 스케줄 |
| Pocock | 낮음 | 높음(균등 배분) | 보수적(낮아짐) | 빠른 우월성 확인, 고정 분석 스케줄 |
| Lan-DeMets | 유연 | 유연 | 유연 | 분석 횟수·타이밍이 실험 중 달라질 때 |
import numpy as np
from scipy import stats
def obrien_fleming(tau: float, alpha: float = 0.05) -> float:
"""O'Brien-Fleming 누적 Alpha Spending"""
if tau <= 0:
return 0.0
z_alpha = stats.norm.ppf(1 - alpha / 2)
return 2 * (1 - stats.norm.cdf(z_alpha / np.sqrt(tau)))
def pocock(tau: float, alpha: float = 0.05) -> float:
"""Pocock 누적 Alpha Spending"""
return alpha * np.log(1 + (np.e - 1) * tau)
# τ=0.1 (10% 진행) 시점에서 각 방법이 소비하는 누적 alpha
print(f"O'Brien-Fleming @ τ=0.1: {obrien_fleming(0.1):.6f}") # ≈ 0.000016
print(f"Pocock @ τ=0.1: {pocock(0.1):.6f}") # ≈ 0.017O'Brien-Fleming은 초반에 극히 적은 alpha만 소비한다. τ=0.1 시점에서 단 0.0016%만 소비하므로, 초기 10%에서 조기 탈출하려면 Z-통계량이 약 4.5σ를 넘어야 한다. 이는 진짜 심각한 회귀만 잡아내는 설계다. 반면 Pocock은 동일 시점에 이미 전체 α의 34%를 소비해 Z≈2.1 수준에서도 조기 탈출이 가능하다.
incremental_alpha(증분 소비량)는 이 접근법의 핵심 메커니즘이다. 전체 α 예산(예: 0.05)을 마치 "포인트"처럼 다루어, 각 중간 분석 시점마다 그 시점에서 소비할 수 있는 양을 계산한다. 한 번 소비한 α는 다음 분석에서 쓸 수 없으며, 모든 분석을 거쳐도 총 소비량이 0.05를 넘지 않는다는 것이 보장된다.
Flagger 게이팅 레이어 아키텍처
Flagger는 analysis.interval마다 메트릭을 체크하고 웹훅을 호출한다. 이 구조가 Sequential Testing의 중간 분석 루프와 정확히 대응된다.
[Flagger Canary Controller]
│
│ 매 interval마다 webhook POST
▼
[Sequential Test Service] ←─── [Prometheus / Metrics Store]
│ (현재 canary 메트릭)
│
│ 1. 현재 τ 계산 (수집 샘플 / 목표 샘플)
│ 2. incremental_alpha 계산 (이번 분석의 α 예산)
│ 3. 임계 Z-값 산출
│ 4. 실제 Z-통계량과 비교
▼
|Z| > boundary?
├── YES → HTTP 400 → Flagger 실패 카운트 증가 → threshold 초과 시 자동 롤백
└── NO → HTTP 200 → Canary 계속 진행실전 적용
Flagger Canary 분석 설정
먼저 Flagger의 Canary 리소스에 Sequential Testing 웹훅을 등록한다. 각 interval이 하나의 중간 분석 시점이 된다.
# canary.yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: my-service
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: my-service
progressDeadlineSeconds: 600
service:
port: 80
analysis:
interval: 1m # 매 1분 = 한 번의 중간 분석
threshold: 3 # 3회 연속 실패 시 롤백
maxWeight: 50 # 최대 50% 트래픽
stepWeight: 10 # 매 iteration 10% 증가
metrics:
- name: request-success-rate
thresholdRange:
min: 99
interval: 1m
webhooks:
- name: sequential-test-gate
type: pre-rollout # 트래픽 증가 전 게이팅
url: http://seq-test-service.monitoring/check
timeout: 30s
metadata:
alpha: "0.05"
method: "obrien_fleming"
# Flagger metadata 필드는 문자열만 허용하므로 숫자도 문자열로 전달
target_samples: "10000"
metric: "error_rate"
canary_name: "my-service"
type: pre-rollout웹훅은 각 트래픽 증가 단계 전에 호출된다. Sequential Test가 실패를 반환하면 트래픽이 증가하지 않고 실패 카운트만 올라간다.threshold에 도달하면 Flagger가 자동 롤백을 실행한다.
웹훅 서비스 구현: AlphaSpendingTest 핵심 로직
아래는 Flagger 웹훅으로 호출되는 FastAPI 서비스다. 먼저 통계 검정 핵심 클래스를 살펴보자.
Z-통계량이 낯선 독자를 위한 배경: Z-통계량은 "관찰된 차이가 우연에 의한 것인지"를 표준편차 단위로 나타낸 값이다. scipy.stats.norm.ppf는 정규분포의 분위수 함수로, "이 alpha 값 아래 있는 Z-값이 얼마인가"를 반환한다. Z > 1.96이면 α=0.05 단일 검정 수준에서 유의하다는 의미다.
# seq_test_service.py — 핵심 통계 로직
import numpy as np
from scipy import stats
class AlphaSpendingTest:
def __init__(self, alpha: float = 0.05, method: str = "obrien_fleming"):
self.alpha = alpha
self.method = method
def spending_function(self, tau: float) -> float:
"""정보 분율 tau까지 누적 소비할 alpha 계산"""
tau = max(tau, 1e-10) # 0 나눗셈 방지
if self.method == "obrien_fleming":
z_alpha = stats.norm.ppf(1 - self.alpha / 2)
return 2 * (1 - stats.norm.cdf(z_alpha / np.sqrt(tau)))
elif self.method == "pocock":
return self.alpha * np.log(1 + (np.e - 1) * tau)
else:
raise ValueError(f"Unknown method: {self.method}")
def get_critical_z(self, tau: float, prev_tau: float = 0.0) -> float:
"""
현재 중간 분석 시점의 임계 Z-값 계산.
prev_tau: 직전 분석 시점의 τ. 호출자가 이전 소비 이력을 관리해
전달해야 한다. 기본값 0은 이번이 첫 번째 분석임을 의미한다.
여러 번 호출할 때 매번 0으로 두면 누적 소비가 정확하게 추적되지
않으므로, 상태 저장소(Redis 등)나 요청 파라미터로 prev_tau를 전달해야 한다.
"""
# incremental_alpha: "이번 중간 분석에서 소비할 수 있는 남은 alpha 예산"
incremental_alpha = (
self.spending_function(tau) - self.spending_function(prev_tau)
)
# 수치 안정성: 경계값이 극단적으로 커지는 것 방지
incremental_alpha = max(incremental_alpha, 1e-10)
return stats.norm.ppf(1 - incremental_alpha / 2)
def evaluate(self, z_stat: float, tau: float, prev_tau: float = 0.0) -> dict:
boundary = self.get_critical_z(tau, prev_tau)
return {
"stop": abs(z_stat) > boundary,
"z_stat": round(z_stat, 4),
"boundary": round(boundary, 4),
"tau": round(tau, 4),
"incremental_alpha": round(
self.spending_function(tau) - self.spending_function(prev_tau), 6
),
}
def compute_z_stat(
canary_error_rate: float,
baseline_error_rate: float,
n_canary: int,
n_baseline: int,
) -> float:
"""
이표본 비율 차이에 대한 Z-통계량.
풀링된 표준오차(pooled SE)를 사용하는 이유: 귀무가설(두 비율이 같다) 아래서는
pooled SE가 통계적으로 더 효율적이다. 대립가설이 사실일 때는 비풀링 SE가
더 적절할 수 있지만, 회귀 감지가 목적인 Canary 배포에서는 귀무가설 기준
검정이 표준 관행이다.
"""
p_pool = (
canary_error_rate * n_canary + baseline_error_rate * n_baseline
) / (n_canary + n_baseline)
se = np.sqrt(p_pool * (1 - p_pool) * (1 / n_canary + 1 / n_baseline))
if se < 1e-10:
return 0.0
return (canary_error_rate - baseline_error_rate) / se각 함수의 역할을 정리하면 다음과 같다.
| 함수 | 역할 |
|---|---|
spending_function(tau) |
τ까지 소비해야 할 누적 alpha 계산 |
get_critical_z(tau, prev_tau) |
이번 중간 분석의 임계 Z-값 계산. prev_tau 상태 관리는 호출자 책임 |
compute_z_stat() |
풀링된 SE 기반 이표본 비율 검정 Z-통계량 계산 |
웹훅 서비스 구현: Prometheus 연동 및 엔드포인트
# seq_test_service.py — FastAPI 엔드포인트 및 메트릭 수집
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import httpx
import os
app = FastAPI()
PROMETHEUS_URL = os.getenv("PROMETHEUS_URL", "http://prometheus:9090")
async def fetch_canary_metrics(canary_name: str) -> dict:
"""
Prometheus에서 canary 메트릭 수집.
주의: 아래 쿼리의 레이블(app="{canary_name}")은 예시다.
실제 서비스의 레이블 구조(app, deployment, service 등)에 맞게 수정해야 한다.
primary deployment는 "{canary_name}-primary" 레이블을 쓴다고 가정하지만
실제 Flagger 환경에서는 다를 수 있다.
"""
async with httpx.AsyncClient() as client:
error_rate_query = (
f'sum(rate(http_requests_total{{app="{canary_name}",'
f'status=~"5.."}}[1m])) / '
f'sum(rate(http_requests_total{{app="{canary_name}"}}[1m]))'
)
sample_count_query = (
f'sum(increase(http_requests_total{{app="{canary_name}"}}[1h]))'
)
resp_error = await client.get(
f"{PROMETHEUS_URL}/api/v1/query",
params={"query": error_rate_query},
)
resp_count = await client.get(
f"{PROMETHEUS_URL}/api/v1/query",
params={"query": sample_count_query},
)
error_data = resp_error.json()["data"]["result"]
count_data = resp_count.json()["data"]["result"]
# 쿼리 결과가 비어있으면 메트릭 미수집 상태로 처리
if not error_data or not count_data:
raise ValueError(f"No metrics found for '{canary_name}'. "
"레이블 구조를 확인하세요.")
error_rate = float(error_data[0]["value"][1])
sample_count = float(count_data[0]["value"][1])
return {"error_rate": error_rate, "sample_count": sample_count}
class WebhookPayload(BaseModel):
metadata: dict = {}
@app.post("/check")
async def sequential_test_gate(payload: WebhookPayload):
meta = payload.metadata
alpha = float(meta.get("alpha", "0.05"))
method = meta.get("method", "obrien_fleming")
# Flagger metadata는 문자열만 전달되므로 int 변환 필요
target_samples = int(meta.get("target_samples", "10000"))
canary_name = meta.get("canary_name", "unknown")
min_tau = float(meta.get("min_tau", "0.05")) # 최소 웜업 5%
# prev_tau 상태 관리: 현재 구현은 요청 파라미터로 받는 구조.
# 실제 운영에서는 Redis 등 외부 상태 저장소에서 실험별 prev_tau를 관리해야
# 매 분석마다 증분 소비가 정확하게 누적된다.
prev_tau = float(meta.get("prev_tau", "0.0"))
try:
canary_metrics = await fetch_canary_metrics(canary_name)
baseline_metrics = await fetch_canary_metrics(f"{canary_name}-primary")
except Exception as e:
# 메트릭 수집 실패 시 통과 처리 (fail-open 정책)
# 서비스 특성에 따라 fail-closed로 변경할 수 있음
return {"status": "metrics_unavailable", "reason": str(e)}
n_canary = int(canary_metrics["sample_count"])
tau = n_canary / target_samples
# 최소 웜업 기간 미충족 시 통과 (τ가 너무 작으면 경계값이 극단적으로 높아짐)
if tau < min_tau:
return {"status": "warming_up", "tau": tau, "min_tau": min_tau}
z_stat = compute_z_stat(
canary_metrics["error_rate"],
baseline_metrics["error_rate"],
n_canary,
max(int(baseline_metrics["sample_count"]), 1),
)
tester = AlphaSpendingTest(alpha=alpha, method=method)
result = tester.evaluate(z_stat, min(tau, 1.0), prev_tau)
if result["stop"]:
raise HTTPException(
status_code=400,
detail={
"reason": "sequential_test_boundary_exceeded",
**result,
},
)
return {"status": "pass", **result}경계 시각화 (로컬 분석용 독립 스크립트)
아래 코드는 서비스 코드(seq_test_service.py)와 무관한 독립 분석 스크립트다. 배포 전에 어떤 경계가 적용되는지 로컬에서 미리 확인할 때 사용한다.
# visualize_boundaries.py — 로컬 분석 전용, 서비스에 포함하지 않는다
import matplotlib.pyplot as plt
import numpy as np
from scipy import stats
def obf_boundary(taus, alpha=0.05):
z_alpha = stats.norm.ppf(1 - alpha / 2)
boundaries, prev_spend = [], 0
for tau in taus:
curr_spend = 2 * (1 - stats.norm.cdf(z_alpha / np.sqrt(tau)))
inc = max(curr_spend - prev_spend, 1e-10)
boundaries.append(stats.norm.ppf(1 - inc / 2))
prev_spend = curr_spend
return boundaries
def pocock_boundary(taus, alpha=0.05):
boundaries, prev_spend = [], 0
for tau in taus:
curr_spend = alpha * np.log(1 + (np.e - 1) * tau)
inc = max(curr_spend - prev_spend, 1e-10)
boundaries.append(stats.norm.ppf(1 - inc / 2))
prev_spend = curr_spend
return boundaries
taus = np.linspace(0.01, 1.0, 100)
obf = obf_boundary(taus)
poc = pocock_boundary(taus)
plt.figure(figsize=(10, 5))
plt.plot(taus, obf, label="O'Brien-Fleming", linewidth=2)
plt.plot(taus, poc, label="Pocock", linewidth=2, linestyle="--")
plt.axhline(y=1.96, color="gray", linestyle=":", label="단일 검정 Z=1.96")
plt.xlabel("정보 분율 τ")
plt.ylabel("임계 Z-값")
plt.title("Alpha Spending 경계 비교 (target_samples=10000 기준)")
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig("boundary_comparison.png", dpi=150, bbox_inches="tight")O'Brien-Fleming의 초기 경계는 Z≈4.5 수준이다. τ=0.1 시점에서 4
5σ의 매우 강한 신호만 조기 탈출을 허용한다는 의미로, 심각한 회귀(오류율 급등, 지연 폭등)에 대한 안전 중단에 이상적이다. Pocock은 전반적으로 Z≈2.12.3 수준을 유지하므로 더 빠른 우월성 확인에 적합하다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 빠른 롤백 | 통계적으로 유의미한 문제 확인 즉시 자동 탈출. 고정 샘플 대비 평균 실험 기간 최대 66% 단축 |
| 사용자 피해 최소화 | 소수 Canary 트래픽에서 문제를 포착하여 전체 사용자 노출 전에 차단 |
| Type I 오류 통제 | Alpha Spending으로 peeking problem 해결. 전체 오류율이 사전 정의 α를 초과하지 않음 |
| 유연한 스케줄 | Lan-DeMets 방법은 중간 분석 횟수와 타이밍을 실험 중 변경 가능 |
| 무용성 중단 확장 가능 | Beta-Spending을 추가하면 효과 없는 실험도 조기 종료하여 자원 낭비 방지 |
| 기존 인프라 활용 | Flagger + Prometheus 스택 위에 웹훅 서비스만 추가하면 도입 가능 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 초기 보수성 | O'Brien-Fleming 초반 경계가 너무 높아 명백한 문제도 못 잡을 수 있음 | 심각도 높은 메트릭(오류율)은 O'Brien-Fleming, 성능 메트릭은 Pocock 혼용 |
| 샘플 크기 소폭 증가 | 동일 검정력 달성을 위한 총 샘플이 단일 검정 대비 약간 증가 | 목표 샘플 수를 10~15% 여유 있게 설정 |
| 구현 복잡도 | 외부 통계 서비스 연동 및 prev_tau 상태 관리 필요. 서비스 장애 시 정책 결정 필요 |
fail-open(서비스 다운 시 통과) 또는 fail-closed 정책 명시적 결정 |
| 메트릭 지연 | Prometheus scrape interval(기본 15s~1m)이 τ 계산 정확도에 영향 | interval을 scrape interval의 3배 이상으로 설정 |
| 다중 메트릭 문제 | 여러 메트릭에 독립적 Alpha Spending 적용 시 전체 FWER 증가 | Bonferroni 보정(α/k) 또는 계층적 검정 설계 |
| Cold Start | τ가 매우 작을 때 경계가 극단적으로 높아 사실상 무의미 | 최소 웜업 기간(min_tau ≥ 0.05) 설정 필수 |
FWER(Family-Wise Error Rate): 다중 비교에서 하나 이상의 거짓 양성이 발생할 확률. 메트릭이 k개라면 각 검정의 유의 수준을 α/k로 낮추는 Bonferroni 보정이 가장 간단한 대응이다. 예를 들어 메트릭 5개에 α=0.05를 적용하려면 각 검정에 α=0.01을 쓴다.
실무에서 가장 흔한 실수
threshold와 Sequential Test를 이중으로 설계하지 않음: Flagger의threshold(누적 실패 허용 횟수)와 Sequential Test 경계가 충돌하면 의도치 않은 조기 롤백이 발생한다. Sequential Test가 경계를 초과하지 않아도 단순 메트릭 임계치 위반이threshold를 채울 수 있다. 두 메커니즘의 역할을 명확히 분리하고, 가능하면 Sequential Test 결과만으로 롤백 여부를 결정하도록 설계한다.- 정보 분율을 시간 기반으로 계산함: τ = 경과 시간 / 총 계획 시간으로 계산하면 트래픽이 적은 새벽 시간대에는 샘플 수가 부족해 통계적 의미가 없는 결과가 나온다. 트래픽이 불균등한 서비스는 반드시 샘플 수 기반으로 τ를 계산해야 한다.
prev_tau상태 관리 없이 매번 0으로 처리함: Sequential Test의 정확한 증분 소비 계산은 이전 분석의 τ를 알아야 한다. 매 호출을prev_tau=0으로 처리하면 실질적으로 각 분석이 독립적인 단일 검정처럼 작동해 Alpha Spending의 오류율 보장이 깨진다. Redis 등 외부 상태 저장소에서 실험별prev_tau를 관리하거나, 최소한 요청 파라미터로 전달하는 구조를 갖춰야 한다.
마치며
Sequential Testing의 진짜 가치는 "더 빨리 판단"이 아니라 "언제 판단해도 오류율이 보장된다"는 통계적 확신에 있다. 임계값 비교 방식은 "지금 나쁜 수치가 나왔으니 롤백하자"는 직관적 판단에 의존하지만, Alpha Spending 기반 Sequential Test는 그 판단이 장기적으로 몇 %의 확률로 틀렸는지를 정량화하고 통제한다. 이 차이가 팀의 신뢰도와 운영 자신감으로 이어진다.
다만 이 글에서 소개한 구현은 단일 메트릭, 단순 prev_tau 전달 구조로 시작점을 제공한다. 실제 운영에서는 다중 메트릭 FWER 보정, Redis 기반 상태 관리, 그리고 Alpha Spending과 쌍을 이루는 Beta-Spending(무용성 중단)이 필요해지는 시점이 온다. 그 시점이 이 접근법의 한계가 아니라, 다음 단계로 성장하는 신호다.
지금 바로 시작할 수 있는 3단계:
- Alpha Spending 경계 시뮬레이션:
visualize_boundaries.py를 로컬에서 실행해 O'Brien-Fleming과 Pocock 경계가 실제 서비스의 목표 샘플 수(target_samples)에서 어떤 Z-값을 요구하는지 먼저 확인한다. 이 숫자를 보면 어떤 방법이 서비스에 맞는지 직관적으로 판단된다. - 최소 MVP 웹훅 서비스 배포:
fetch_canary_metrics의 Prometheus 연동 부분을 하드코딩 더미값으로 교체한 버전을 먼저 배포하고,canary.yaml의webhooks.url을 실제 서비스 주소로 교체한 뒤kubectl apply -f canary.yaml을 실행한다. HTTP 200/400 게이팅 흐름이 의도대로 동작하는지 먼저 검증한다. - 실제 메트릭 연동 및 min_tau 튜닝: 서비스의 실제 Prometheus 레이블 구조에 맞게 쿼리를 수정하고,
min_tau를 서비스 평균 요청 처리량에 맞게 조정한다. 목표는 첫 번째 유의미한 검정이 실제 샘플 수 기반 τ ≥ 0.05 이후에 이뤄지도록 보장하는 것이다.
다음 글: Beta-Spending으로 무용성 중단(futility stopping)을 추가해 효과 없는 Canary를 조기에 종료하고, 다중 메트릭 환경에서 FWER을 통제하는 계층적 검정 설계 방법
참고 자료
- Flagger 공식 문서 — How it works
- Flagger 공식 문서 — Webhooks
- Flagger 공식 문서 — Metrics Analysis
- GitHub — fluxcd/flagger
- O'Brien-Fleming boundary | Wikipedia
- Alpha Spending Function approach | Penn State STAT 509
- Alpha-Spending Function 정의 | Analytics ToolKit
- Error Spending in Sequential Testing Explained | Analytics-Toolkit.com
- Sequential Testing for Early Stopping of Online Experiments | ACM SIGIR 2015
- Understanding Group Sequential Testing | Towards Data Science
- Sequential Testing | Amplitude Explore
- Lan-DeMets Method Programs | University of Wisconsin
- Mastering Progressive Delivery with Istio and Flagger | Medium