왜 "눈으로 보는 카나리"는 실패하는가

카나리 배포는 새 버전을 소수 트래픽에만 노출한 뒤 지표를 보고 확산 여부를 결정한다. 문제는 판정 단계다. 대시보드를 사람이 보고 "괜찮아 보인다"고 승인하는 방식은 세 가지로 무너진다. 첫째, 관측 창이 짧으면 노이즈를 신호로 오인한다. 둘째, 트래픽이 적어 표본이 부족한데도 평균값만 비교한다. 셋째, 판정 기준이 사람마다 달라 재현되지 않는다. 자동화의 핵심은 배포 도구가 아니라 통계적 판정 규칙을 코드로 고정하는 것이다.

기준선(baseline)을 언제 것으로 잡을 것인가

흔한 실수가 카나리(신버전)를 프로덕션 전체(구버전, 오래 살아있어 캐시가 따뜻한 인스턴스)와 비교하는 것이다. 인스턴스 나이·캐시 상태·JIT 워밍업 차이가 버전 차이로 오인된다. 올바른 비교는 동시에 새로 띄운 baseline군과 canary군이다. 둘 다 같은 시점에 시작하고, 트래픽도 동일 비율로 받는다. 이렇게 하면 "환경 차이"가 아니라 "코드 차이"만 남는다.

단일 평균 비교의 함정과 Mann-Whitney

지연 시간은 정규분포가 아니라 오른쪽으로 긴 꼬리를 가진다. 평균 t-검정은 이 분포에서 취약하다. 실무에서는 분포 가정이 없는 순위 기반 검정(Mann-Whitney U)이나 분위수(p95, p99) 비교를 쓴다. 아래는 canary와 baseline의 지연 표본을 비검정으로 판정하는 예다.

import numpy as np
from scipy import stats

def judge_latency(baseline, canary, alpha=0.01, tol=1.05):
    # 분포 차이 검정 (비모수)
    u, p = stats.mannwhitneyu(canary, baseline, alternative="greater")
    canary_p95 = np.percentile(canary, 95)
    base_p95   = np.percentile(baseline, 95)

    regressed = (p < alpha) and (canary_p95 > base_p95 * tol)
    return {
        "p_value": round(p, 4),
        "base_p95": round(base_p95, 1),
        "canary_p95": round(canary_p95, 1),
        "verdict": "FAIL" if regressed else "PASS",
    }

# 통계적으로 유의(p<alpha)하면서, 실질 차이(p95 5% 초과)일 때만 FAIL

여기서 두 조건을 모두 요구하는 것이 핵심이다. p-value만 보면 표본이 커질수록 사소한 차이도 유의해지고, 분위수 차이만 보면 노이즈에 흔들린다.

지표별 가중치와 종합 점수

지표 하나로 판정하지 않는다. 에러율·지연·자원 사용량을 각각 채점하고 가중합으로 종합한다. Kayenta(Spinnaker) 계열이 쓰는 방식과 유사하다.

metrics:
  - name: error_rate
    query: 'sum(rate(http_requests_total{status=~"5..",job="$scope"}[5m]))'
    direction: increase   # 증가하면 나쁨
    weight: 50
    fail_threshold: 1.10  # baseline 대비 10% 초과 시 실패
  - name: latency_p99
    query: 'histogram_quantile(0.99, ...{job="$scope"})'
    direction: increase
    weight: 35
    fail_threshold: 1.20
  - name: cpu_usage
    query: 'avg(rate(container_cpu_usage_seconds_total{job="$scope"}[5m]))'
    direction: increase
    weight: 15
    fail_threshold: 1.30
decision:
  pass_score: 75   # 100점 만점, 75 미만이면 롤백
  min_samples: 500 # 표본 부족 시 판정 보류(연장)

판정 방식 비교

방식장점약점
평균 임계값구현 단순꼬리 분포·표본 크기에 취약
분위수(p95/p99)사용자 체감 반영표본 적으면 불안정
비모수 검정분포 가정 없음실질 차이 판단은 별도 필요
가중 종합 점수다지표 균형가중치 튜닝 부담

주의점: 표본, 다중검정, 자동 롤백

  • 최소 표본 게이트: 트래픽이 적으면 판정하지 말고 관측 창을 연장하라. 표본 없는 PASS가 가장 위험하다.
  • 다중 비교 보정: 지표가 10개면 우연히 하나가 유의해질 확률이 커진다. 유의수준(alpha)을 지표 수로 나누거나(Bonferroni) 종합 점수로 흡수하라.
  • 양방향 감시: "지연이 갑자기 0"처럼 좋아 보이는 이상치도 장애(요청 유실)일 수 있다. increase만 보지 말고 급격한 감소도 경보 대상에 넣어라.
  • 판정과 롤백 분리: 통계 판정은 신호일 뿐이다. 롤백은 자동으로 하되, 승격(promotion)은 창을 늘려가며 단계적으로 확대하는 것이 안전하다.

결국 카나리 자동화의 품질은 배포 파이프라인이 아니라 판정 규칙의 통계적 엄밀함에서 갈린다. 비교 대상을 동시에 띄우고, 유의성과 실질 차이를 함께 요구하고, 표본이 부족하면 판정을 미루는 세 가지만 지켜도 오탐과 미탐이 크게 줄어든다.