새벽에 burn rate 알람이 안 울렸다 — multiwindow SLO 알람 삽질 노트

새벽에 burn rate 알람이 안 울렸다 — multiwindow SLO 알람 삽질 노트
지난주 금요일, 정확히는 토요일 새벽 4시쯤에 한 통의 메시지로 잠에서 깼다. 새벽이라 페이저는 아니었다. 결제 팀 PL이 슬랙 DM으로 "혹시 새벽 2시 ~ 3시 사이에 결제 실패 폭주한 거 알고 있냐"고 물어본 게 시작이었다. 모니터링 알람은 한 통도 받지 못한 상태였다.
대시보드를 켜보니 새벽 2시 12분부터 약 32분간 결제 API의 5xx 비율이 8%까지 튀었다가 자연 복구됐다. 우리 SLO는 가용성 99.9%, 즉 한 달에 약 43분의 에러 예산이 전부였다. 사실상 한 번의 사고로 한 달치를 다 태운 거다. 그런데 왜 burn rate 알람이 안 울렸지? 이날 알게 된 multiwindow burn rate 알람의 함정을 정리해둔다.
우리가 쓰던 알람 설정
올해 초 SRE 워크북에 나오는 multiwindow multi-burn-rate 패턴을 그대로 가져다 썼다. 4가지 조합인데, 일단 critical 두 개만 보자.
# Critical: 1시간 안에 한 달 에러 예산의 2% 소진
- alert: PaymentErrorBudgetBurnFast
expr: |
(
payment_error_ratio_rate1h > (14.4 * 0.001)
and
payment_error_ratio_rate5m > (14.4 * 0.001)
)
for: 2m
labels:
severity: critical
# Critical: 6시간 안에 한 달 에러 예산의 5% 소진
- alert: PaymentErrorBudgetBurnMedium
expr: |
(
payment_error_ratio_rate6h > (6 * 0.001)
and
payment_error_ratio_rate30m > (6 * 0.001)
)
for: 15m
labels:
severity: critical
겉으로 보면 멀쩡한 설정이다. 구글 SRE 책 워크북에도 비슷하게 나와 있고, 작년 KubeCon에서도 이 패턴이 표준처럼 통용됐다. 그런데 이게 새벽 사고에서는 한 번도 트리거되지 않았다.
왜 안 울렸나
원인을 추적해보니 두 가지가 겹쳤다.
첫 번째. 사고는 32분간 8% 에러로 지속됐는데, 알람 조건의 rate1h는 1시간 평균이라 사고 직후엔 약 4.3% 정도로 희석됐다. burn rate로 환산하면 43배. 14.4배 임계값은 넘는다.
근데 같이 걸린 for: 2m에 함정이 있었다. 사고 시작 후 짧은 윈도우(rate5m)는 5분 만에 임계값을 넘었는데, 1시간 윈도우는 사고가 18분쯤 진행됐을 때 비로소 14.4 × 0.001 = 0.0144(=1.44%)를 넘기 시작했다. 그러니까 두 조건이 동시에 참인 시점은 사고 시작 후 약 18분쯤부터다. 거기서 for: 2m을 더 기다린다.
그런데 슬프게도 우리 결제 API는 그 무렵 자연 복구가 시작됐다. 32분짜리 사고에서 18+2 = 20분이 지난 시점부터 알람 평가가 시작됐고, 그때부터는 에러율이 다시 떨어지고 있어서 rate5m이 임계값 밑으로 내려갔다. 결국 두 조건이 동시에 참인 채로 2분을 못 버텨서 알람이 안 울린 거다.
두 번째. medium 알람은 rate6h가 0.006(=0.6%)을 넘어야 하는데, 32분짜리 사고가 6시간 평균에 끼치는 영향은 8% × (32/360) ≈ 0.71%. 임계값을 살짝 넘는다. 근데 for: 15m을 기다리면서 시간이 흐를수록 분모는 늘고 분자는 안 늘어서 임계값 아래로 다시 내려간다. 결국 이쪽도 못 울렸다.
요약하면 "짧고 격렬한 사고"에 대해 multiwindow burn rate 패턴이 의외로 무뎠다. 이게 이번에 우리 팀이 배운 가장 비싼 교훈이다.
임시 조치와 본격 수정
토요일 오후에 임시로 다음 알람을 추가했다. 짧은 윈도우 하나만 보고 critical을 띄우는 것.
- alert: PaymentErrorSpike
expr: payment_error_ratio_rate5m > 0.02
for: 3m
labels:
severity: critical
annotations:
summary: "결제 API 5분 에러율 2% 초과 (burn rate 무관 안전망)"
이건 SLO 기반 알람이라기보다 그냥 "급격한 스파이크 잡는 안전망"이다. burn rate 패턴이 잡지 못하는 짧은 사고를 잡으라는 의도다. 한 달 운영해보니 false positive가 좀 있긴 한데, 새벽에 못 자는 것보단 낫다.
그리고 본격 수정으로 burn rate 알람 자체를 손봤다. 구체적으로 두 가지를 바꿨다.
1. 짧은 윈도우의 임계값을 별도로 낮췄다.
원래 multiwindow는 short/long 둘 다 같은 burn rate를 봤는데, short는 더 민감하게, long은 확증용으로 쓰는 게 맞다는 결론이 났다. 14.4배 burn rate 대신 short만 30배로 올리고 long은 그대로 두는 식이다.
- alert: PaymentErrorBudgetBurnFast
expr: |
(
payment_error_ratio_rate1h > (14.4 * 0.001)
and
payment_error_ratio_rate5m > (30 * 0.001)
)
for: 0m
for: 0m도 같이 빼버렸다. burn rate 패턴 자체가 이미 노이즈 필터링 역할을 하기 때문에 for를 추가로 두면 짧은 사고를 놓치게 된다는 걸 이번에 처음 깨달았다.
2. 단독 short window 알람을 정식으로 추가했다.
위 임시 조치를 정식 알람으로 승격. 다만 severity를 warning으로 낮춰서 페이지를 안 깨우고 슬랙만 가게 했다. 이건 burn rate와 별개 트랙으로 운영한다.
한 가지 더 — recording rule 평가 주기
이건 좀 부끄러운데, 우리 Prometheus의 evaluation_interval이 30s였다. recording rule도 30s 주기로 갱신된다. 그러니까 rate5m도 매 30초마다 새로 계산된다. 빠른 알람을 원하면 evaluation_interval을 더 짧게 가져가도 되는데, 우리는 일단 30s 그대로 두기로 했다. 이걸 15s로 줄이면 부하가 꽤 커진다 — 우리 환경에서는 CPU 사용량이 약 1.8배까지 올랐다. 알람 1~2분 빨리 울리자고 인프라 비용을 거의 두 배 쓰는 건 좀 그렇다.
그래서 결론은
multiwindow multi-burn-rate 패턴이 잘못된 건 아니다. 한 시간 이상 지속되는 점진적 에러율 상승에는 여전히 잘 작동한다. 다만 "30분 이내에 시작해서 끝나는 격렬한 사고"는 이 패턴의 사각지대다. 우리는 결국 burn rate 알람과 별개로 short-window 임계 알람을 함께 운영하는 쪽으로 갔다.
아직도 고민은 있다. Google이 워크북에서 multiwindow 패턴을 강력히 권하는 이유는 false positive를 줄이려는 것이고, 별도 short alarm을 추가하면 그 의도와 정확히 반대 방향으로 가는 셈이다. 그래서 short alarm은 페이지 트리거에서 제외하고 슬랙만 가게 둔 거지만, 이 합의가 우리 팀에서 영원히 유효할지는 모르겠다. 결제 팀 PL은 "다음에 또 못 잡으면 페이지로 올려달라"고 하더라.
혹시 비슷한 사례 겪으신 분 있으시면 어떻게 해결하셨는지 댓글로 알려주세요. 특히 short window 단독 알람 없이 burn rate 패턴만으로 짧은 사고를 잡으신 분이 있으면 진짜 궁금합니다.