왜 임계값 기반 알림은 실패하는가

많은 팀이 "CPU 80% 초과", "에러율 1% 초과" 같은 고정 임계값으로 알림을 건다. 문제는 두 가지다. 첫째, 임계값이 사용자 경험과 무관하다. 야간 배치로 CPU가 잠깐 90%가 되어도 사용자는 아무 영향이 없는데 온콜은 깨어난다. 둘째, 짧은 스파이크마다 알림이 울려 알림 피로(alert fatigue)가 쌓이고, 정작 중요한 장애를 놓친다. 필요한 건 "지금 얼마나 심각한가"가 아니라 "이 속도로 가면 우리가 약속한 신뢰성을 지킬 수 있는가"라는 관점이다.

SLO와 에러버짓의 기본 개념

SLI(서비스 수준 지표)는 측정값이다. 예를 들어 "성공 응답 비율"이나 "300ms 이하 응답 비율"이다. SLO는 그 목표치다. "30일 동안 성공률 99.9%"처럼 기간과 함께 정의한다. 에러버짓은 1 - SLO, 즉 허용되는 실패량이다. 99.9% SLO라면 0.1%가 예산이다. 30일 기준으로 환산하면 약 43분의 다운타임에 해당한다. 이 예산은 배포 위험을 감수할 수 있는 여유이자, 소진되면 안정화에 집중하라는 신호다.

# 30일, 99.9% SLO 기준 에러버짓 계산
총_요청 = 100_000_000
SLO = 0.999
허용_실패 = 총_요청 * (1 - SLO)   # 100,000건
관측_실패 = 42_000
소진율 = 관측_실패 / 허용_실패     # 0.42 -> 예산 42% 소진
남은_예산_비율 = 1 - 소진율        # 0.58

번 레이트(Burn Rate)로 사고하기

번 레이트는 에러버짓을 소비하는 속도다. 번 레이트 1은 "예산을 기간 내내 정확히 다 쓰는 속도"다. 번 레이트 10이면 예산을 10배 빠르게 태우고 있다는 뜻이다. 30일 예산을 3일 만에 소진한다. 알림을 절대 에러율이 아니라 번 레이트에 걸면, 사용자 영향의 심각도와 속도를 동시에 반영할 수 있다.

burn_rate = (실제_에러율) / (1 - SLO)
# 예: SLO 99.9%, 현재 5분간 에러율 1%
# burn_rate = 0.01 / 0.001 = 10 -> 예산을 10배 속도로 소진

멀티 윈도우, 멀티 번레이트 알림

단일 윈도우 알림은 딜레마가 있다. 윈도우를 짧게 하면 스파이크에 예민하고, 길게 하면 반응이 느리다. 구글 SRE 워크북이 제안하는 방식은 긴 윈도우와 짧은 윈도우를 AND로 묶는 것이다. 긴 윈도우는 "실제로 문제가 지속되는가"를, 짧은 윈도우는 "지금도 진행 중인가"를 확인해 오탐과 미탐을 동시에 줄인다.

심각도번 레이트긴 윈도우짧은 윈도우대응
Page14.41시간5분즉시 호출
Page66시간30분즉시 호출
Ticket13일6시간업무시간 처리

Prometheus 알림 규칙 예시

다음은 성공률 SLI에 대해 빠른 소진(2% 예산을 1시간에)과 중간 소진을 각각 페이지/티켓으로 구분한 규칙이다. 짧은/긴 윈도우를 and로 결합한 점에 주목한다.

groups:
- name: slo-burn-rate
  rules:
  - alert: ErrorBudgetBurnFast
    expr: |
      (
        job:slo_errors:ratio_rate1h{job="api"} > (14.4 * 0.001)
        and
        job:slo_errors:ratio_rate5m{job="api"} > (14.4 * 0.001)
      )
    labels:
      severity: page
    annotations:
      summary: "API 에러버짓 빠른 소진 (burn rate > 14.4)"
  - alert: ErrorBudgetBurnSlow
    expr: |
      (
        job:slo_errors:ratio_rate6h{job="api"} > (6 * 0.001)
        and
        job:slo_errors:ratio_rate30m{job="api"} > (6 * 0.001)
      )
    labels:
      severity: ticket
    annotations:
      summary: "API 에러버짓 중간 속도 소진 (burn rate > 6)"

여기서 0.001은 1 - 0.999, 즉 허용 에러율이다. job:slo_errors:ratio_rate1h 같은 지표는 recording rule로 미리 집계해 두면 알림 평가가 가볍고 일관된다.

SLI를 올바르게 정의하기

알림 품질은 결국 SLI 정의에서 갈린다. 서버가 반환한 5xx만 세면 로드밸런서 타임아웃이나 연결 실패를 놓친다. 가능하면 사용자 경로에 가까운 지점, 예를 들어 인그레스나 로드밸런서 로그에서 측정한다. 헬스체크, 봇 트래픽, 예상된 4xx(인증 실패 등)는 분모에서 제외할지 신중히 결정한다. 아래는 로그 테이블에서 SLI를 뽑는 예시다.

-- 5분 버킷별 성공률 SLI
SELECT
  date_trunc('minute', ts) AS bucket,
  COUNT(*) FILTER (WHERE status < 500 AND path != '/healthz') AS good,
  COUNT(*) FILTER (WHERE path != '/healthz') AS total,
  1.0 * COUNT(*) FILTER (WHERE status < 500 AND path != '/healthz')
      / NULLIF(COUNT(*) FILTER (WHERE path != '/healthz'), 0) AS sli
FROM request_logs
WHERE ts > now() - interval '1 hour'
GROUP BY 1
ORDER BY 1;

운영상의 주의점

첫째, 에러버짓 정책을 문서로 합의하라. "예산이 소진되면 신규 기능 배포를 멈추고 안정화한다" 같은 규칙이 없으면 SLO는 대시보드 장식으로 끝난다. 둘째, 낮은 트래픽 서비스는 요청 수가 적어 번 레이트가 요동친다. 이때는 최소 요청 수 조건을 함께 걸거나 윈도우를 늘린다. 셋째, SLO는 100%가 목표가 아니다. 지나치게 엄격한 SLO는 예산이 0에 수렴해 모든 배포를 막고, 지나치게 느슨하면 알림이 침묵한다. 실제 사용자 불만이 시작되는 지점을 기준으로 잡고 분기마다 재조정하라. 마지막으로 알림에는 항상 대응 런북 링크를 붙여, 호출받은 사람이 무엇을 확인할지 즉시 알 수 있게 한다.