SLI / SLO / Error Budgets
The Google SRE framework for measuring and managing reliability.
Definitions
| Term | Definition | Example |
|---|---|---|
| SLI (Service Level Indicator) | The actual measurement — a ratio of good events to total events | good_requests / total_requests |
| SLO (Service Level Objective) | The target you commit to — SLI must be ≥ this over a window | 99.9% of requests succeed over 30 days |
| SLA (Service Level Agreement) | A contractual SLO with financial penalty if breached | External customer contract |
| Error Budget | (1 - SLO) × window — how much failure you're allowed |
0.1% × 30 days = 43.2 minutes |
SLI Design
A good SLI is a ratio: valid_good_events / valid_total_events.
Availability SLI (most common)
# SLI: fraction of requests that succeed (non-5xx)
sum(rate(http_requests_total{status!~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
Latency SLI
# SLI: fraction of requests faster than 300ms
sum(rate(http_request_duration_seconds_bucket{le="0.3"}[5m]))
/
sum(rate(http_request_duration_seconds_count[5m]))
Saturation SLI (queue-based)
# SLI: fraction of time queue depth is below threshold
1 - (
sum(rate(job_queue_depth_seconds_total{depth!="0"}[5m]))
/ sum(rate(job_queue_depth_seconds_total[5m]))
)
What makes a bad SLI
| Bad SLI | Why bad | Better |
|---|---|---|
| CPU utilization | Doesn't measure user experience | Request latency |
| Internal error count (absolute) | Not normalized, can't set a stable target | Error rate (ratio) |
| "Service is up" (binary) | Not granular, all-or-nothing | % requests succeeding |
| Probe from one region | Misses regional failures | Multi-region probe OR real user traffic |
Error Budget Math
Error Budget = (1 - SLO_target) × window_seconds
Example: SLO = 99.9%, window = 30 days
Error Budget = 0.001 × (30 × 24 × 3600)
= 0.001 × 2,592,000
= 2,592 seconds
= 43.2 minutes
Remaining error budget:
# Error budget consumed (last 30 days)
1 - (
sum(rate(http_requests_total{status!~"5.."}[30d]))
/
sum(rate(http_requests_total[30d]))
)
# Budget remaining % (if SLO = 0.999)
(
(1 - 0.999) - (1 - avg_over_time(sli_availability[30d]))
)
/ (1 - 0.999)
Multi-Window Multi-Burn-Rate Alerts
Simple threshold alerts on SLI are noisy. The Google SRE Workbook recommends alerting on burn rate — how fast you're consuming the error budget — across two time windows to reduce false positives.
Burn Rate
Burn rate = current_error_rate / (1 - SLO)
SLO = 99.9% → (1 - SLO) = 0.001
If current error rate = 0.01 (1%) → burn rate = 0.01 / 0.001 = 10x
Burn rate 1x = consuming budget at exactly the SLO pace (budget exhausted at end of window). Burn rate 14.4x = entire 30-day budget consumed in 2 hours.
The Four Alert Tiers
| Tier | Window | Burn Rate | Budget consumed if burns whole window | Urgency | Action |
|---|---|---|---|---|---|
| P1 critical | 1h + 5m | > 14.4x | ~2% (2h alert) | Page immediately | Incident |
| P2 critical | 6h + 30m | > 6x | ~5% (6h alert) | Page | Investigate now |
| P3 warning | 1d + 2h | > 3x | ~10% (1d alert) | Ticket | Next business day |
| P4 info | 3d + 6h | > 1x | >10% | Log | Sprint backlog |
Why two windows per tier? Short window = fast detection, high false positive rate. Long window = slow detection, low false positive rate. Both must fire to page.
# Example: P1 critical alert (Prometheus alerting rule)
groups:
- name: slo_alerts
rules:
- alert: HighErrorBudgetBurn
expr: |
(
job:http_errors:rate1h{job="api"} > (14.4 * 0.001)
and
job:http_errors:rate5m{job="api"} > (14.4 * 0.001)
)
for: 2m
labels:
severity: critical
annotations:
summary: "High error budget burn rate (14.4x)"
description: "Error rate {{ $value | humanizePercentage }} burning budget at 14.4x"
runbook: "https://wiki/runbooks/api-high-error-rate"
- alert: MediumErrorBudgetBurn
expr: |
(
job:http_errors:rate6h{job="api"} > (6 * 0.001)
and
job:http_errors:rate30m{job="api"} > (6 * 0.001)
)
for: 15m
labels:
severity: critical
annotations:
summary: "Medium error budget burn rate (6x)"
Recording Rules for Burn Rate
Pre-compute error rates at required windows (Prometheus evaluates these on interval, not at query time):
groups:
- name: slo_recording_rules
interval: 30s
rules:
# Error rate over each window
- record: job:http_errors:rate5m
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m])) by (job)
/
sum(rate(http_requests_total[5m])) by (job)
- record: job:http_errors:rate30m
expr: |
sum(rate(http_requests_total{status=~"5.."}[30m])) by (job)
/
sum(rate(http_requests_total[30m])) by (job)
- record: job:http_errors:rate1h
expr: |
sum(rate(http_requests_total{status=~"5.."}[1h])) by (job)
/
sum(rate(http_requests_total[1h])) by (job)
- record: job:http_errors:rate6h
expr: |
sum(rate(http_requests_total{status=~"5.."}[6h])) by (job)
/
sum(rate(http_requests_total[6h])) by (job)
SLO Dashboard (Grafana)
Key panels for an SLO dashboard:
Row 1: Current SLI value | Budget remaining % | Budget burn rate
Row 2: Error rate over time (with SLO line)
Row 3: Latency p50/p95/p99 (with SLO threshold line)
Row 4: Request rate (traffic signal — is low SLI low traffic or real errors?)
Row 5: Error budget burn rate over time (alert threshold lines at 1x, 6x, 14.4x)
# Budget remaining (0-1 range, panel threshold: red < 0.1)
(
(1 - 0.999) - (
1 - (
sum(rate(http_requests_total{status!~"5.."}[30d]))
/ sum(rate(http_requests_total[30d]))
)
)
) / (1 - 0.999)
# Current burn rate (panel threshold: red > 14.4, orange > 6)
(
1 - sum(rate(http_requests_total{status!~"5.."}[1h]))
/ sum(rate(http_requests_total[1h]))
) / 0.001
SLO Decision Framework
flowchart TD
NEW["Defining a new SLO"] --> USER
USER["What does the user experience?<br/>Latency? Availability? Correctness?"] --> MEASURE
MEASURE["Can we measure it from real traffic?<br/>(not synthetic probes)"] --> RATIO
RATIO["Express as a ratio:<br/>good events / total events"] --> TARGET
TARGET["Set target conservatively:<br/>start at current p30-day baseline − 0.5%"] --> BUDGET
BUDGET["Calculate error budget<br/>and burn rate tiers"] --> ALERT
ALERT["Write multi-window burn rate alerts<br/>link to runbooks"] --> REVIEW
REVIEW["Review SLO monthly:<br/>too tight --> eng velocity hurt<br/>too loose --> reliability suffering"]
Common mistake: Setting SLO at 99.99% before measuring baseline. If your actual baseline is 99.5%, a 99.99% SLO means your error budget is always exhausted and every deploy is blocked.
SLO per Service Type
| Service type | Primary SLI | SLO starting point |
|---|---|---|
| User-facing API | Availability + p99 latency | 99.9% / < 500ms |
| Background job | Completion rate + job duration | 99.5% complete within 1h |
| Data pipeline | Freshness (data age) + completeness | Data < 15min old, 99.9% records |
| Storage | Durability + availability | 99.99% durability, 99.9% availability |
| Internal service | Availability | 99.5% (lower than user-facing) |