에러버짓 정책, 이렇게 굴리면 실제로 돌아간다

에러버짓 정책, 이렇게 굴리면 실제로 돌아간다
SLO 문서에 에러버짓 계산식은 그럴싸하게 적어놨는데, 정작 소진되면 뭘 어떻게 할지가 애매한 팀이 많다. 우리도 그랬다. "예산 다 쓰면 릴리스 중단"이라고 위키에 한 줄만 적어놓고 반년을 흘려보냈다.
문제는 어느 서비스가 어느 만큼 태워야 진짜로 손 놓아야 하는지, 그 기준이 팀마다 제각각이었다는 거다. PM은 "이번 스프린트만 넘기자"고 하고, SRE는 "지금 안 멈추면 안 된다"고 하고, 개발자는 "내 코드 아닌데 왜 내가 멈추냐"고 하고. 결국 정책이 없으면 매번 논쟁만 반복된다.
이 글은 최근 몇 달간 우리 팀에서 굴려보고 나름 안정화된 에러버짓 정책의 뼈대를 정리한 것이다. Google SRE Workbook 얘기가 아니라 진짜 로컬 환경에서 어떻게 세팅하고 자동화했는지에 초점을 뒀다.
예산 구간을 4단계로 자른다
일단 정책이라는 게 존재해야 논쟁이 끝난다. 우리는 예산 잔량을 4구간으로 잘랐다.
- 50% 이상 남음 — 평시. 뭐든 배포 가능. 실험도 이 구간에서만 한다.
- 25~50% — 주의. 릴리스 자체는 하되 카나리 비율을 낮추고(5% → 1%), 대규모 마이그레이션은 다음 스프린트로 미룬다.
- 10~25% — 경고. 기능 배포 중단. 신뢰성 관련 티켓만 머지. 데일리 스탠드업에서 예산 잔량을 확인한다.
- 10% 미만 — 동결. 코드 프리즈. 회복 계획을 세우고 인시던트 리뷰까지 마쳐야 해제된다.
숫자는 정답이 없다. 우리는 처음에 25% / 10%로 시작했는데 오탐이 많아서 좀 더 완만하게 조정한 게 위 배치다. 팀별 서비스 특성에 맞춰야 한다. 결제 API랑 리포트 배치 잡의 임계값을 똑같이 두면 결제 쪽이 계속 프리즈에 들어가서 아무도 정책을 안 지킨다.
예산 리셋 정책 — 30일 롤링 윈도우가 최선이었다
캘린더 월(1일 리셋)로 했다가 후회했다. 월말에 대형 장애가 나면 다음 달 1일에 예산이 원상복구되면서 "괜찮아, 새 달이야"라는 착각을 준다. 실제로 서비스 신뢰성은 회복 안 됐는데 정책상 프리즈는 풀리는 웃긴 상황이 나왔다.
30일 롤링 윈도우로 바꾸고 나서는 이게 사라졌다. 어제자 장애가 오늘도 오늘의 예산에 영향을 주고, 정확히 30일 뒤에 자연 소멸한다. Prometheus 쿼리로도 그냥 표현된다.
# 30일 롤링 윈도우 에러버짓 잔량 (99.9% SLO 기준)
(1 - (
sum(rate(http_requests_total{status=~"5.."}[30d]))
/
sum(rate(http_requests_total[30d]))
)) / 0.999
이 값이 1을 넘으면 예산 잔량이 있는 거고, 1 미만이면 이미 소진된 상태다. 대시보드에는 소수점 대신 (값 - 1) * 100 / (1 - 0.999) 같은 식으로 "몇 % 남았는지"를 계산해서 띄운다.
멀티 윈도우 소진율 알림
단일 윈도우 기반 알림은 오탐 아니면 놓침, 둘 중 하나다. 5분 윈도우로 알림 걸면 스파이크에 반응해서 새벽 3시에 페이지를 받고, 24시간 윈도우로 걸면 이미 예산 절반이 날아간 뒤에야 알람이 온다.
Google SRE Workbook에도 나오는 얘기데 실제로 세팅해보면 파라미터 튜닝이 꽤 걸린다. 우리 팀 세팅은 두 개 조합이다.
# 페이지: 1시간 + 5분 창에서 소진율 14.4x 이상
- alert: ErrorBudgetBurnFast
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[1h]))
/
sum(rate(http_requests_total[1h]))
) > (14.4 * 0.001)
and
(
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
) > (14.4 * 0.001)
for: 2m
labels:
severity: page
# 티켓: 6시간 + 30분 창에서 소진율 6x 이상
- alert: ErrorBudgetBurnSlow
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[6h]))
/
sum(rate(http_requests_total[6h]))
) > (6 * 0.001)
and
(
sum(rate(http_requests_total{status=~"5.."}[30m]))
/
sum(rate(http_requests_total[30m]))
) > (6 * 0.001)
for: 15m
labels:
severity: ticket
14.4x는 "이 속도로 계속 가면 1시간에 월 예산의 2%를 태운다"는 의미고, 6x는 6시간에 5%를 태운다는 뜻이다. 이 두 개만 걸어놔도 어지간한 상황은 다 잡힌다. 짧은 창(5분/30분)이 추가로 걸린 이유는 상황이 이미 해소됐는데 긴 창 지연 때문에 뒤늦게 페이지 오는 걸 막기 위해서다.
자동 프리즈 — CI 파이프라인에 예산 체크 게이트
정책 문서만 있어봐야 아무도 안 본다. 실제로 정책을 강제하려면 CI에 심어야 한다. 우리는 GitHub Actions 잡 앞에 예산 조회 스텝을 넣었다.
- name: Check error budget
id: budget
run: |
BUDGET=$(curl -s "http://prom.internal/api/v1/query?query=error_budget_remaining_ratio{service=\"payment-api\"}" \
| jq -r '.data.result[0].value[1]')
echo "remaining=$BUDGET" >> $GITHUB_OUTPUT
if (( $(echo "$BUDGET < 0.10" | bc -l) )); then
echo "::error::Error budget at ${BUDGET}, deploy blocked."
exit 1
fi
if (( $(echo "$BUDGET < 0.25" | bc -l) )); then
echo "::warning::Error budget low: ${BUDGET}"
fi
- name: Deploy
if: steps.budget.outputs.remaining >= '0.10'
run: ./deploy.sh
error_budget_remaining_ratio 는 위에 있던 PromQL을 recording rule로 미리 계산해둔 메트릭이다. 매번 파이프라인에서 30일치를 스캔하면 부담이 크니까 recording rule로 5분마다 계산해서 저장해둔다.
Override 절차도 만들어둬야 한다. 진짜 급한 보안 패치 같은 건 예산 무관하게 나가야 하니까. 우리는 [emergency-override] 라벨을 PR에 붙이면 이 게이트를 우회하도록 했고, 대신 슬랙 채널로 자동 공지가 나가서 누가 왜 우회했는지 남는다. 이 로그가 꽤 유용하다. 회고 때 "지난달에 override 몇 건 있었지?" 하고 돌아볼 수 있다.
실제 도입 순서
정책 문서를 완성해놓고 한 번에 적용하려 하면 반드시 실패한다. 우리 경험상 순서는 이랬다.
첫 주는 그냥 대시보드만 만들었다. 잔량 표시, 소진율 그래프, 알림 없이 눈으로만. 팀원들이 "아 이 서비스 예산 이렇게 태우고 있구나" 감을 잡는 시간이다. 이 시기가 없으면 나중에 알림이 왔을 때 뭘 봐야 할지 모른다.
둘째 주에 티켓 알림을 걸었다. 페이지가 아니라 조회 가능한 티켓만. 오탐 여부를 확인하고 임계값을 조정했다.
셋째 주에 페이지 알림을 붙였다. 여기서부터 새벽에 사람이 깬다.
넷째 주 이후에 CI 게이트를 추가했다. 이 시점에는 팀원들이 이미 예산 개념에 익숙해져 있어서 배포가 막혀도 "왜 막혔지?"가 아니라 "아 이거 태워서 그렇구나"라고 반응한다.
이 순서 없이 곧바로 CI 게이트부터 심으면 팀원들이 정책을 우회하기 시작한다. 라벨 붙여서 override, PR 쪼개서 우회, 심지어 메트릭 자체를 조작. 그러면 정책은 종이 위에만 남는다.
하나 남은 의문
에러버짓 정책이 만닠은 아니다. 특히 트래픽이 적은 서비스는 요청 1000건 중 실패 1건만 나와도 예산이 크게 흔들려서 오탐 지옥이 된다. 우리 팀에서도 내부 admin API는 SLO 기반 에러버짓 대신 절대값 임계치(하루 실패 5건 이상)로 관리한다.
또 하나, 예산이 남는다고 무조건 배포해도 되냐 하면 그것도 아니다. 큰 아키텍처 변경은 예산과 별개로 리스크 리뷰가 따로 필요하다. 예산은 안전벨트일 뿐 조향 장치는 아니다.
정책을 굴려본 지 몇 달 지난 지금도 임계값은 계속 조정 중이다. 완성된 정책이라는 건 없고, 현재 팀 상황에 맞는 정책이 있을 뿐이다. 다른 팀은 어떻게 굴리는지 궁금하다.