SLO multi-window burn rate, 우리 팀이 세 번 갈아엎은 이야기

SLO 알림 한 번 손봤다가 두 달을 끌었다. 이게 뭐 그리 복잡하다고. 처음엔 그렇게 생각했다.
우리 팀은 작년 가을부터 핵심 API 다섯 개에 대해 SLO 기반 알림을 운영하고 있다. 가용성 99.9%, 레이턴시 P99 300ms 이하. 알림은 Prometheus + Alertmanager 조합. Google SRE Workbook에 나온 multi-window multi-burn-rate(MWMBR)를 그대로 베껴 쓰고 있었다. 처음엔 만족스러웠다. 그런데 올해 초부터 슬슬 문제가 보이기 시작했다.
1차 시도: Workbook 그대로 베끼기
처음 셋업할 때는 SRE Workbook 표를 그대로 옮겼다. 4개 티어, 각 티어마다 short/long 두 윈도우.
- alert: HighErrorBudgetBurn_Fast
expr: |
(
job:slo_errors_per_request:ratio_rate1h{job="api"} > (14.4 * 0.001)
and
job:slo_errors_per_request:ratio_rate5m{job="api"} > (14.4 * 0.001)
)
for: 2m
labels:
severity: page
burn rate 14.4, 6, 3, 1로 내려가는 4단계. 우리도 똑같이 했다.
처음 두 달은 평화로웠다. 의미 있는 알림만 떴고, 페이지가 떠서 깨면 실제로 문제가 있었다. 그러다 1월쯤부터 새벽에 한 번씩 페이지가 뜨는데, 도착해서 보면 이미 회복돼 있는 상황이 잦아졌다. 한 주에 두세 번. 처음엔 "그래도 실제 burn이 있었으니 정상이지"라고 넘겼다. 근데 한 달이 지나니까 팀원 두 명이 "이거 페이지 의미 없는 거 같다"고 말을 꺼냈다.
진짜 문제: 우리 트래픽 패턴이 Google이 아니다
원인을 파보니까 결국 우리가 Workbook을 너무 곧이곧대로 받았다는 거였다.
Workbook의 burn rate 계산은 트래픽이 균일하다는 가정에 가깝다. 그런데 우리 서비스는 새벽 2시에서 4시 사이 트래픽이 평소의 1/30 수준으로 떨어진다. 이 시간대에 에러가 다섯 개만 나도 5분 윈도우 에러율은 순식간에 튄다. 1시간 윈도우는 좀 더 안정적이지만, 새벽에는 어차피 분모가 작으니 그것도 흔들린다.
요약하면 트래픽이 적은 시간대에는 SLI 자체가 노이즈가 심하다. 14.4 burn rate라는 수치가 "주간 budget의 2%를 1시간에 태우는 속도"인데, 새벽에는 요청 100개 중 2개만 실패해도 그 속도가 나온다. 사실 사용자 영향은 거의 없는데도.
2차 시도: low-traffic 가드 추가
그래서 일단 트래픽이 적을 때는 알림을 죽이는 가드를 붙였다.
- alert: HighErrorBudgetBurn_Fast
expr: |
(
job:slo_errors_per_request:ratio_rate1h{job="api"} > (14.4 * 0.001)
and
job:slo_errors_per_request:ratio_rate5m{job="api"} > (14.4 * 0.001)
and
sum(rate(http_requests_total{job="api"}[5m])) > 10
)
for: 2m
5분 평균 RPS가 10 미만이면 알림을 안 보낸다. 임시방편이었다. 팀 내부 논의에서 "이거 결국 SLO 신뢰도를 깎는 거 아니냐" 이야기가 나왔다. 맞는 말이다. 새벽에 진짜 장애가 나도 트래픽이 적으면 묻혀버린다.
그리고 또 한 가지 깨달은 게 있다. multi-window가 false positive를 줄이는 건 맞는데, 그게 통하는 전제가 "두 윈도우 모두 sustain된 burn"이라는 것이다. 우리처럼 트래픽이 들쭉날쭉하면, 5분 윈도우는 노이즈로 잘 안 잡혀 있어도 1시간 윈도우는 한참 후에 떨어지는 식의 시차가 생긴다. 그 시차 때문에 알림 타이밍이 묘하게 어긋났다.
3차 시도: 에러 카운트 기반 + 다중 SLI
세 번 갈아엎고 도달한 지점은 이렇다.
첫째, ratio 알림 옆에 절대 카운트 알림을 같이 둔다. "5분 동안 에러가 N개 이상" 같은 보조 조건을 넣었다. 트래픽이 적을 때는 이 조건이 안 맞아서 자연스럽게 false positive가 줄었다. 트래픽이 많을 때는 ratio가 의미 있게 동작하니까 이중으로 걸러진다.
- alert: HighErrorBudgetBurn_Fast
expr: |
(
job:slo_errors_per_request:ratio_rate1h{job="api"} > (14.4 * 0.001)
and
job:slo_errors_per_request:ratio_rate5m{job="api"} > (14.4 * 0.001)
and
sum(increase(http_requests_total{job="api",status=~"5.."}[5m])) > 20
)
for: 2m
둘째, fast 티어(burn 14.4)는 page로, slow 티어(burn 1)는 ticket으로 분리했다. Workbook은 4티어를 다 권장하지만 우리는 6배 burn 티어를 빼버렸다. 알림 분기점이 너무 많으면 운영자가 어느 게 진짜인지 헷갈린다. 두 티어로 줄이니까 페이지가 뜨면 "지금 당장 봐야 할 일", 티켓이 뜨면 "내일 아침에 봐도 될 일"로 명확해졌다.
셋째, SLO 윈도우를 28일에서 7일로 줄였다. 이건 좀 논쟁이 있었는데, 우리 서비스가 배포 사이클이 빠르다 보니 28일 budget으로 보면 한 번의 큰 사고가 한 달 내내 영향을 준다. 7일로 줄이고 burn rate 임계값도 그에 맞춰 다시 계산했다. 의도치 않게 좋았던 점은, budget이 짧으니까 회고가 빨라졌다.
결국 배운 것
SLO 알림은 베스트 프랙티스를 그대로 가져다 쓰면 안 된다는 게 가장 큰 교훈이다. Google이 제시한 표는 출발점이지, 정답이 아니다. 우리 트래픽 패턴, 우리 사용자가 느끼는 영향, 우리 팀이 새벽에 깰 의지 — 이 셋이 다 다르면 임계값도 윈도우도 다 달라진다.
올해 초 SRE 관련 글에서 multi-window 알림 튜닝 얘기가 자주 나오던데, 거기서 강조하는 것도 같은 결이다. "for duration을 늘려서 false positive를 줄여라"는 조언이 흔한데, 우리는 for를 건드리는 대신 보조 조건을 붙이는 쪽으로 갔다. 이건 팀마다 취향이 갈릴 것 같다.
여전히 완벽하지는 않다. 지난주에도 한 번 fast 페이지가 떴는데 알고 보니 외부 의존 서비스 한 곳이 잠깐 흔들린 거였다. 우리 서비스 입장에서는 5xx로 잡히지만 사용자가 실제 영향을 받았는지는 더 봐야 한다. 이런 케이스는 SLI 자체를 다시 정의해야 하는 영역이라 또 한 번 갈아엎을 일이 생길 것 같다.
혹시 비슷한 고민 하시는 분 있으면 어떻게 푸셨는지 댓글로 좀 알려주시라. 우리 팀도 아직 헤매는 중이다.