SLO와 오류 예산에 대한 알림
서비스 수준 목표를 정의하고 사용자가 성능 저하를 알아차리기 전에 작동하는 실행 가능한 알림을 연결합니다.
SLO와 오류 예산에 대한 알림은(는) CoddyKit의 무료 FastAPI Backend Development Bootcamp 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 FastAPI Backend Development Bootcamp 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. FastAPI Backend Development Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.
이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.
Why Alert on SLOs, Not Raw Metrics
Traditional alerts fire on raw symptoms like CPU > 90% or error_count > 100. The problem: these page you for things users never notice, and stay silent during slow degradation that hurts customers.
SLO-based alerting inverts this. You first define what "good service" means to a user, then alert only when you are at risk of breaking that promise.
- SLI (Service Level Indicator): a measured ratio, e.g. fraction of fast, successful requests.
- SLO (Service Level Objective): the target for that SLI, e.g. 99.9% over 30 days.
- Error budget: the allowed failure, i.e. 100% minus the SLO.
In this lesson you will define SLOs for a FastAPI service and wire alerts that fire before users notice degradation.
Picking a Good SLI for an API
A good SLI is a ratio of good events to valid events, scaled 0 to 100%. For a FastAPI backend the two workhorse SLIs are:
- Availability: successful responses / all valid responses. Treat 5xx as failures; usually exclude 4xx (client's fault).
- Latency: requests served under a threshold / all requests, e.g. responses faster than 300ms.
Below is a tiny, self-contained calculator that turns raw request logs into these two SLIs.
def compute_slis(requests, latency_threshold_ms=300):
valid = [r for r in requests if r["status"] < 500 or r["status"] >= 500]
total = len(valid)
good_avail = sum(1 for r in valid if r["status"] < 500)
fast = sum(1 for r in valid if r["latency_ms"] <= latency_threshold_ms)
availability = good_avail / total
latency_sli = fast / total
return {"availability": availability, "latency": latency_sli}
sample = [
{"status": 200, "latency_ms": 120},
{"status": 200, "latency_ms": 410},
{"status": 500, "latency_ms": 90},
{"status": 200, "latency_ms": 250},
{"status": 503, "latency_ms": 600},
]
slis = compute_slis(sample)
print(f"availability = {slis['availability']:.2%}")
print(f"latency = {slis['latency']:.2%}")From SLO to Error Budget
Once you commit to an SLO, the error budget is simply the failures you are allowed before breaching it.
- SLO 99.9% over 30 days → budget = 0.1% of all requests may fail.
- If you serve 10,000,000 requests/month, that is 10,000 failed requests you can spend.
- Expressed as time at full outage: 30 days × 0.1% ≈ 43 minutes of downtime per month.
The budget is a currency. Every failure spends it. Alerting is about watching the burn rate of that currency.
def error_budget(slo_percent, total_requests, window_days=30):
allowed_failure_ratio = 1 - (slo_percent / 100)
budget_requests = total_requests * allowed_failure_ratio
budget_minutes = window_days * 24 * 60 * allowed_failure_ratio
return budget_requests, budget_minutes
for slo in (99.0, 99.9, 99.99):
reqs, mins = error_budget(slo, 10_000_000)
print(f"SLO {slo}% -> {reqs:,.0f} req budget, {mins:.1f} min downtime/30d")Burn Rate: The Core Idea
Burn rate is how fast you are consuming the error budget relative to "on pace." A burn rate of 1 means you will exactly exhaust the 30-day budget in 30 days. A burn rate of 14.4 means you would exhaust it in roughly 2 days.
The formula:
burn_rate = observed_error_rate / (1 - SLO)- If SLO = 99.9%, the budget error rate is 0.001. An observed error rate of 1.44% gives a burn rate of
0.0144 / 0.001 = 14.4.
High burn rate over a short window = something is on fire right now. Modest burn over a long window = slow leak. You alert on both.
def burn_rate(observed_error_rate, slo_percent):
budget_rate = 1 - (slo_percent / 100)
return observed_error_rate / budget_rate
for err in (0.001, 0.005, 0.0144, 0.05):
br = burn_rate(err, 99.9)
hours_to_exhaust = (30 * 24) / br if br else float("inf")
print(f"err={err:.3%} -> burn={br:5.1f}x, exhausts budget in {hours_to_exhaust:6.1f}h")Multi-Window, Multi-Burn-Rate Alerts
A single threshold is a trap: alert too eagerly and you page on blips; too late and you miss real outages. The Google SRE-recommended pattern uses multiple burn rates over multiple windows:
- Fast burn: 14.4x over 1 hour (and a 5-min check) → page now, you will burn the whole month's budget in ~2 days.
- Slow burn: 6x over 6 hours → page, sustained leak.
- Trickle: 1x over 3 days → ticket, not a page.
Pairing a long window with a short "is it still happening" window kills false positives: the alert only fires if the burn is both severe and current.
ALERT_TIERS = [
{"name": "page_fast", "burn": 14.4, "long_window_h": 1, "short_window_m": 5},
{"name": "page_slow", "burn": 6.0, "long_window_h": 6, "short_window_m": 30},
{"name": "ticket", "burn": 1.0, "long_window_h": 72, "short_window_m": 360},
]
def evaluate(long_burn, short_burn):
for tier in ALERT_TIERS:
if long_burn >= tier["burn"] and short_burn >= tier["burn"]:
return tier["name"]
return "ok"
print(evaluate(long_burn=16.0, short_burn=15.0)) # spike still active
print(evaluate(long_burn=16.0, short_burn=0.2)) # recovered, suppress
print(evaluate(long_burn=2.0, short_burn=2.0)) # slow leak -> ticketInstrumenting FastAPI to Feed the SLI
Alerts are only as good as the data. Add Prometheus counters and a histogram via middleware so every request contributes to your availability and latency SLIs.
This is framework code (FastAPI + middleware), so it is not standalone-runnable, but it is the canonical instrumentation pattern.
import time
from fastapi import FastAPI, Request
from prometheus_client import Counter, Histogram, make_asgi_app
app = FastAPI()
REQUESTS = Counter(
"http_requests_total", "All requests", ["method", "path", "status"]
)
LATENCY = Histogram(
"http_request_duration_seconds", "Request latency", ["path"],
buckets=(0.05, 0.1, 0.3, 0.5, 1.0, 2.5),
)
@app.middleware("http")
async def record_metrics(request: Request, call_next):
start = time.perf_counter()
response = await call_next(request)
elapsed = time.perf_counter() - start
path = request.url.path
REQUESTS.labels(request.method, path, response.status_code).inc()
LATENCY.labels(path).observe(elapsed)
return response
app.mount("/metrics", make_asgi_app())Writing the SLI as a PromQL Recording Rule
With the counters in place, express the SLIs as PromQL. A recording rule precomputes the error ratio so alert rules stay cheap and readable.
The good-events ratio over the alert window is what you compare against the burn-rate threshold. Note how the threshold 0.0144 equals 14.4 × (1 - 0.999) — burn rate folded into the comparison.
# prometheus rules.yml
groups:
- name: slo_recording
rules:
- record: job:http_error_ratio:rate1h
expr: |
sum(rate(http_requests_total{status=~"5.."}[1h]))
/
sum(rate(http_requests_total[1h]))
- record: job:http_error_ratio:rate5m
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))The Fast-Burn Alert Rule
Now the alert. It fires only when both the 1h and 5m error ratios exceed the 14.4x burn threshold for a 99.9% SLO. The short window guarantees the problem is still happening before paging an on-call engineer.
0.0144 = 14.4 × (1 - 0.999)for: 2mdebounces transient spikes.- Labels route severity; annotations carry the runbook link.
# prometheus alerts.yml
groups:
- name: slo_alerts
rules:
- alert: HighErrorBudgetBurnFast
expr: |
job:http_error_ratio:rate1h > 0.0144
and
job:http_error_ratio:rate5m > 0.0144
for: 2m
labels:
severity: page
slo: availability-99.9
annotations:
summary: "Burning error budget 14.4x (1h+5m)"
description: "At this rate the 30d budget is gone in ~2 days."
runbook: "https://runbooks.internal/slo-fast-burn"Simulating an Alert Decision in Python
Before trusting your rules in production, simulate them. This self-contained evaluator replays a window of requests, computes both short and long error ratios, and decides whether a page should fire — exactly mirroring the multi-window rule.
def error_ratio(window):
if not window:
return 0.0
failures = sum(1 for r in window if r >= 500)
return failures / len(window)
def should_page(long_window, short_window, slo=99.9, multiplier=14.4):
threshold = multiplier * (1 - slo / 100)
long_r = error_ratio(long_window)
short_r = error_ratio(short_window)
return long_r > threshold and short_r > threshold, long_r, short_r
long_w = [200] * 940 + [500] * 60 # 6% errors over the hour
short_w = [200] * 95 + [500] * 5 # 5% still in last 5 min
fire, lr, sr = should_page(long_w, short_w)
print(f"long={lr:.2%} short={sr:.2%} -> page={fire}")Latency SLOs Need Percentiles, Not Averages
Averages hide tail pain. If 99% of requests take 80ms and 1% take 4s, the mean looks fine while real users rage. Define latency SLOs on percentiles (p95, p99) or as the fraction under a threshold.
With a Prometheus histogram, p99 over 5m is:
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
Below, a pure-Python p-quantile helper shows the math your histogram approximates.
def percentile(values, p):
if not values:
return None
s = sorted(values)
k = (len(s) - 1) * (p / 100)
lo = int(k)
hi = min(lo + 1, len(s) - 1)
frac = k - lo
return s[lo] + (s[hi] - s[lo]) * frac
latencies = [70, 75, 80, 82, 90, 95, 110, 130, 300, 4000]
for p in (50, 95, 99):
print(f"p{p} = {percentile(latencies, p):.0f} ms")Actionable Alerts and Budget Policy
An alert that no one can act on is noise. Make every page actionable:
- Symptom-based: alert on the SLO breach (users hurting), not on a single cause.
- Runbook attached: each alert links steps to diagnose and mitigate.
- Routed by severity:
pagewakes someone;ticketwaits for business hours.
Tie it to an error-budget policy: when the budget is healthy, ship features fast. When it is exhausted, freeze risky deploys and prioritize reliability. The budget turns reliability from opinion into a shared, data-driven decision.
def budget_policy(consumed_ratio):
if consumed_ratio < 0.5:
return "GREEN: ship freely"
if consumed_ratio < 0.9:
return "YELLOW: extra review on risky changes"
if consumed_ratio < 1.0:
return "ORANGE: freeze non-critical deploys"
return "RED: budget exhausted, reliability work only"
for used in (0.2, 0.7, 0.95, 1.1):
print(f"{used:.0%} budget used -> {budget_policy(used)}")Quick Check: Choosing the Alert Trigger
You run a FastAPI service with a 99.9% availability SLO. You want to page on-call only for problems that genuinely threaten the monthly error budget, while avoiding false alarms from brief blips. Which alerting strategy best fits?
Recap: SLO-Driven Alerting
You learned to alert on user-facing reliability instead of raw symptoms:
- SLI = good events / valid events; SLO = the target; error budget = 100% minus the SLO.
- Burn rate = observed error rate / budget rate; it tells you how fast the budget is being spent.
- Multi-window, multi-burn-rate alerts (e.g. 14.4x over 1h+5m to page, slower burns to ticket) catch real degradation early without paging on blips.
- Instrument FastAPI with Prometheus counters and histograms, express SLIs as recording rules, and define alerts on the error ratio — using percentiles, never averages, for latency.
- Every alert must be actionable: symptom-based, runbook-linked, severity-routed, and backed by an error-budget policy that governs when to ship versus freeze.
Done well, your pages fire before users notice degradation — and stay quiet when nothing is wrong.
AI 튜터와 함께 FastAPI Backend Development Bootcamp을(를) 배우세요 — 무료
브라우저에서 실제 코드를 작성하고 실행하며, 24/7 AI 튜터로부터 즉각적인 도움을 받고, 웹이나 앱에서 중단한 부분부터 계속 학습하세요.
- 코스
- 21
- 레슨
- 84
자주 묻는 질문
“SLO와 오류 예산에 대한 알림” 강의는 무료인가요?
네 — “SLO와 오류 예산에 대한 알림” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 FastAPI Backend Development Bootcamp 강의 전체를 잠금 해제할 수 있습니다. FastAPI Backend Development Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.
“SLO와 오류 예산에 대한 알림”에서 뭘 배우나요?
서비스 수준 목표를 정의하고 사용자가 성능 저하를 알아차리기 전에 작동하는 실행 가능한 알림을 연결합니다. 브라우저에서 직접 실행하는 실습 코드로 FastAPI Backend Development Bootcamp을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
FastAPI Backend Development Bootcamp을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 FastAPI Backend Development Bootcamp은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.
“SLO와 오류 예산에 대한 알림” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 FastAPI Backend Development Bootcamp 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 FastAPI Backend Development Bootcamp 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.