에러 예산(Error Budget) 정리: 실패해도 되는 양을 예산처럼 쓰기

SLI, SLO, SLA 정리에서 가용성 SLO를 30일 동안 99.9%로 잡으면 “0.1%까지는 실패해도 된다”는 허용량이 생긴다고 했는데요. 이 허용량을 어떻게 부르느냐가 생각보다 중요합니다. “허용 오차”라고 부르면 넘지 말아야 할 선처럼 들리지만 “예산”이라고 부르는 순간 질문이 바뀌거든요. 남은 예산을 어디에 쓸까? 🤔
이게 에러 예산(error budget)이라는 개념의 전부입니다. 실패해도 되는 양을 쓸 수 있는 자원으로 보는 거예요. 이 글에서는 에러 예산이 무엇인지, 예산이 남았을 때와 바닥났을 때 무엇을 하기로 약속하는지, 예산이 줄어드는 속도를 어떻게 재는지, 그리고 그 속도로 알림을 걸면 알림 규칙 정리에서 시작된 임계값 논쟁이 어떻게 끝나는지를 정리해 보겠습니다.
에러 예산이란
계산은 뺄셈 하나입니다. 에러 예산은 1에서 SLO를 뺀 값이에요. SLO가 99.9%면 에러 예산은 0.1%입니다. 이걸 요청 수로 바꾸면 감이 옵니다. 한 달에 요청이 1,000만 건 들어오는 서비스라면 그중 1만 건은 실패해도 SLO를 지키는 셈이죠. 시간으로 바꾸면 앞 글의 표에서 본 43분이고요.
짚어 둘 건 그 0.1%가 “실패해도 어쩔 수 없는 양”이 아니라 “위험을 감수해도 되는 양”이라는 점입니다. 새 버전을 배포하면 잠깐 오류가 튈 수 있고 실험 기능을 켜면 일부 사용자가 실패를 겪을 수 있어요. 데이터베이스를 옮기면 몇 분 느려지기도 하고요. 이런 일은 전부 예산을 씁니다. 그리고 예산이 있다는 건 이런 일을 해도 된다는 뜻이에요. 예산이 0이면 아무것도 바꿀 수 없습니다.
그래서 에러 예산은 개발과 운영 사이의 줄다리기를 숫자 하나로 바꿉니다. 개발은 빨리 배포하고 싶고 운영은 안정을 원하는데 둘 다 옳아서 회의로는 결론이 안 나죠. 예산이 있으면 “지금 예산이 얼마나 남았는가”라는 사실 확인으로 대화가 바뀝니다. 남아 있으면 배포하고, 없으면 멈추는 겁니다. 누가 이기느냐의 문제가 아니라 숫자가 정하는 문제가 돼요.
예산이 남았을 때와 바닥났을 때
“남으면 배포하고 없으면 멈춘다”를 미리 문서로 적어 둔 게 에러 예산 정책(error budget policy)입니다. 장애가 난 뒤에 정하면 늦어요. 그때는 이미 감정이 섞여 있어서 “이번만 예외로” 하자는 말이 나오거든요. 정책은 예산이 넉넉할 때 개발, 운영, 제품 담당자가 같이 정하고 서명해 둡니다.
flowchart TB
accTitle: 에러 예산 정책의 순환
accDescr: 30일 창의 에러 예산이 남아 있으면 배포와 실험을 계속하고, 바닥나면 기능 개발을 멈추고 신뢰성 작업을 우선한다. 시간이 지나 창 밖으로 나쁜 날이 빠지면 예산이 회복되어 다시 배포할 수 있다.
check{"30일 창의<br/>에러 예산이 남았는가?"}
check -- "예" --> ship["배포와 실험을<br/>계속한다"]
check -- "아니요" --> freeze["기능 배포를 멈추고<br/>신뢰성 작업을 우선한다"]
ship --> check
freeze --> recover["나쁜 날이 창 밖으로 나가<br/>예산이 회복된다"]
recover --> check
정책에 들어가는 내용은 대개 세 가지입니다. 예산이 남아 있을 때는 평소대로 배포하고 실험합니다. 예산이 바닥나면 기능 배포를 멈추고 신뢰성 작업, 그러니까 장애 원인 수정이나 자동 복구 구축 같은 일을 우선합니다. 그리고 예외를 정합니다. 보안 패치처럼 멈출 수 없는 배포는 예산과 무관하게 나가되 그 사실을 기록하는 식이죠.
이 정책이 있으면 온콜 정리에서 이야기한 사후 검토가 실질적인 힘을 갖습니다. “다음에는 자동 복구를 만들자”는 결론이 사후 검토 문서에만 남고 실행되지 않는 일이 흔한데 예산이 바닥나서 기능 배포가 멈춘 상태라면 그 작업이 자연스럽게 다음 할 일이 되니까요.
예산은 회복된다
바닥났다고 영원히 멈추는 건 아닙니다. SLO의 기간이 최근 30일 굴러가는 창이라면 장애가 났던 날이 창 밖으로 빠져나가는 순간 예산은 돌아옵니다. 30일 전 오늘 43분짜리 장애가 있었다면 내일은 그 43분이 계산에서 빠지죠.
회복 구조 때문에 예산 소진은 벌이 아니라 속도 조절이 됩니다. 나쁜 주가 있었으면 몇 주 조심하고 예산이 다시 쌓이면 속도를 올리는 거예요. 반대로 예산이 계속 90% 넘게 남아 있다면 그것도 신호입니다. 너무 조심하고 있거나 SLO를 너무 느슨하게 잡았다는 뜻이라 배포 주기를 당기거나 SLO를 올릴 여지가 있는 거죠. 예산은 상한이지 목표가 아니지만 매달 거의 안 쓰고 남기는 예산은 그만큼의 속도를 버리고 있는 겁니다.
소진율: 예산이 줄어드는 속도
여기까지가 예산의 잔액 이야기였다면 알림에 필요한 건 잔액이 줄어드는 속도입니다. 지금 오류율이 SLO가 허용하는 오류율의 몇 배인지를 소진율(burn rate)이라고 불러요. SLO가 99.9%라 허용 오류율이 0.1%인데 지금 오류율이 0.1%면 소진율은 1입니다. 정확히 30일에 예산을 다 쓰는 속도죠. 오류율이 1%면 소진율은 10이고 예산은 3일 만에 바닥납니다.
| 소진율 | 지금 오류율 (SLO 99.9%) | 예산이 바닥나는 데 걸리는 시간 |
|---|---|---|
| 1 | 0.1% | 30일 |
| 2 | 0.2% | 15일 |
| 6 | 0.6% | 5일 |
| 14.4 | 1.44% | 50시간 |
| 36 | 3.6% | 20시간 |
표가 알림 규칙 글의 “5%냐 1%냐” 논쟁에 답을 줍니다. 오류율 5%는 소진율 50이라 예산이 14시간 만에 사라지는 속도고, 1%는 소진율 10이라 3일이면 끝납니다. 둘 다 사람을 깨워야 하는 속도예요. 논쟁의 진짜 답은 “몇 %에서 알릴까”가 아니라 “예산이 얼마나 빨리 사라질 때 깨울까”였던 겁니다.
소진율로 알림 걸기
그러면 소진율 몇에서 알림을 걸까요? 2018년에 나온 Site Reliability Workbook이 여기에 실용적인 답을 내놨습니다. 하나의 소진율이 아니라 창 길이가 다른 여러 소진율을 함께 보는 방식이에요. 여러 창과 여러 소진율(multi-window, multi-burn-rate)을 짝지은 알림이라고 부릅니다.
발상은 이렇습니다. 소진율이 아주 높으면 짧은 창에서도 금방 확인되고 바로 깨워야 합니다. 소진율이 낮으면 긴 창에서 봐야 의미가 있고 아침에 봐도 됩니다. 그래서 창 길이와 소진율을 짝지어 단계를 나눕니다.
| 창 | 소진율 | 30일 예산 중 소진 | 심각도 |
|---|---|---|---|
| 1시간 | 14.4 | 2% | page |
| 6시간 | 6 | 5% | page |
| 3일 | 1 | 10% | ticket |
1시간 동안 소진율 14.4가 이어지면 예산의 2%가 사라진 겁니다. 이 속도면 이틀 남짓 만에 한 달 예산이 다 없어지니 바로 깨웁니다. 3일 동안 소진율 1이면 예산의 10%를 썼는데 이건 SLO를 딱 맞추는 속도라 급하지는 않지만 계속 두면 안 되니 ticket으로 남깁니다.
여기에 한 가지 장치를 더합니다. 긴 창과 함께 짧은 창도 같이 봅니다. 1시간 창만 보면 장애가 30분 전에 끝났어도 1시간 평균은 아직 높아서 알림이 나가거든요. 그래서 “1시간 창에서 14.4배이고 최근 5분에서도 14.4배”처럼 두 조건을 묶으면 지금도 계속 타고 있는 경우에만 울립니다. 알림 규칙 글에서 지속 시간이 순간적인 튐을 걸러 줬다면, 짧은 창은 이미 끝난 장애를 걸러 줍니다.
Prometheus 규칙으로 옮기면 다음과 같습니다. 오류율을 창별로 미리 계산해 두는 기록 규칙(recording rule)을 만들고 알림 규칙에서 그 둘을 묶습니다.
groups:
- name: payment-slo
rules:
# 5분 창과 1시간 창의 오류율을 미리 계산해 둔다
- record: payment:error_ratio_5m
expr: |
sum(rate(http_requests_total{service="payment", status=~"5.."}[5m]))
/ sum(rate(http_requests_total{service="payment"}[5m]))
- record: payment:error_ratio_1h
expr: |
sum(rate(http_requests_total{service="payment", status=~"5.."}[1h]))
/ sum(rate(http_requests_total{service="payment"}[1h]))
# 1시간 창에서 소진율 14.4배(예산의 2%)이고 지금(5분)도 같은 속도면 깨운다
# 0.001은 1 - 0.999, 즉 SLO가 허용하는 오류율
- alert: PaymentErrorBudgetBurnFast
expr: |
payment:error_ratio_1h > 14.4 * 0.001
and payment:error_ratio_5m > 14.4 * 0.001
labels:
severity: page
annotations:
summary: "결제 API가 1시간 만에 30일 에러 예산의 2%를 쓰고 있습니다"
알림 규칙 글의 예제와 비교해 보면 달라진 건 임계값 하나입니다. > 0.05라는 감으로 정한 숫자가 > 14.4 * 0.001이라는 SLO에서 유도된 숫자로 바뀌었어요. 5%가 맞는지 1%가 맞는지 다툴 일이 없어집니다. SLO가 바뀌면 0.001만 고치면 되고 나머지 소진율 단계는 그대로예요. 6시간 창과 3일 창 규칙도 창 길이와 소진율만 다르고 같은 모양입니다.
예산은 나눠 쓰는 자원이다
마지막으로 하나 짚어 둘 게 있습니다. 예산은 우리 팀만 쓰는 게 아니에요. 결제 서비스가 의존하는 인증 서비스가 10분 죽으면 결제 서비스의 예산도 10분어치 사라집니다. 사용자 입장에서는 결제가 안 된 거니까요. 그래서 SLO를 잡을 때는 의존하는 서비스의 SLO를 봐야 합니다. 인증이 99.9%인데 결제가 99.99%를 약속할 수는 없어요. 예산의 대부분을 남의 장애로 쓰게 되니까요.
반대로 우리 서비스가 다른 팀의 의존성이라면 우리 예산은 그 팀의 예산이기도 합니다. 그 관계가 보이기 시작하면 SLO 문서를 팀 사이에 공유하고 서로의 소진율 알림을 참고하게 되는데 그 지점부터 SLO는 한 팀의 도구가 아니라 조직의 언어가 됩니다.
마치며
에러 예산은 1에서 SLO를 뺀 값이고, 실패해도 되는 양이 아니라 위험을 감수해도 되는 양입니다. 남아 있으면 배포하고 바닥나면 신뢰성 작업을 우선한다는 정책을 미리 적어 두면 개발과 운영의 줄다리기가 규칙이 되고 굴러가는 창 덕에 예산은 시간이 지나면 회복됩니다. 알림은 잔액이 아니라 소진율로 걸고 창 길이가 다른 소진율을 짝지어 급한 것은 page로, 느린 것은 ticket으로 보냅니다.
여기까지 네 편에서 다룬 알림 규칙, 온콜, SLO, 에러 예산은 모두 “미리 정한 질문에 답하는” 도구였습니다. 오류율이 얼마인지, 예산이 얼마나 남았는지는 질문을 미리 알고 있어서 답할 수 있죠. 그런데 장애 중에 나오는 질문은 대개 미리 정해 두지 않은 질문입니다. “왜 특정 지역 사용자만 느리지?” 같은 질문이요. 미리 정하지 않은 질문에 답하려면 무엇이 필요한지는 관측성 정리에서 이어집니다.
여러 창을 짝지은 소진율 알림의 원래 설명은 Site Reliability Workbook의 Alerting on SLOs 챕터를 참고하세요.
This work is licensed under CC BY 4.0