“Product says reliability is fine at 99.4%. Engineering says we're on fire. Both are looking at the same graph.”
case name: '99.4% is fine' — or is it?
- Service
- shoplite checkout
- Impact
- A month of arguments about whether to ship features or fix reliability
- Detected by
- A tense planning meeting
- Time to resolve
- One SLO document and a set of recording rules
Skills you'll use on this case
increase over long windows00 It starts
Reproduce this incident in your lab, then work the case alongside the timeline below. Try each query yourself before reading its result.
01 The investigation
- Mon
REPORT
'Checkout success is 99.4% this month. That's basically 100%.'
At 180,000 checkouts a month, 0.6% is 1,080 failed payments. Whether that's 'fine' isn't a technical question; it's a product decision that nobody had actually made.
- Tue
ACTION
Write the SLO down: SLI, target, window
SLI: the proportion of checkout requests that succeed (not 5xx). SLO: 99.5% over a rolling 28 days. ERROR BUDGET: the 0.5% allowed to fail, about 900 checkouts at current traffic. Now '99.4%' has a meaning: we've spent 120% of our budget.
- Tue
QUERY
Encode the SLI as a recording rule
Recording rules compute an expression on every evaluation and store it as a new series. SLO math re-uses the same ratio over several windows, so recording it once at 5m keeps queries fast and consistent.
PromQL· Prometheusslo:checkout_errors:ratio_rate5mresult{service="shoplite", slo="checkout-availability"} 0.199 (errors scenario on) - Wed
QUERY
Budget remaining over the SLO window
Error ratio over the window divided by the allowed ratio (0.005) gives the fraction of budget spent. One minus that is what's left. Negative means the SLO is breached.
PromQL· Prometheus1 - ( sum(increase(http_request_duration_seconds_count{job="shoplite", route="/checkout", status=~"5.."}[28d])) / sum(increase(http_request_duration_seconds_count{job="shoplite", route="/checkout"}[28d])) ) / 0.005result{} -0.21 → budget exhausted: 121% spenterror budget remaining (28d window)remaining - Thu
RESOLVED
Error budget policy agreed: while budget is negative, reliability work takes priority over features
The argument ends because the decision was made in advance, by product and engineering together, and now just gets applied.
Root cause
There was no agreed definition of 'reliable enough'. Without a target, every percentage was open to interpretation, so reliability work and feature work competed by opinion rather than by data.
02 The concepts behind it
SLI, SLO, SLA
An SLI (indicator) is a measured ratio of good events to valid events: successful checkouts / all checkouts, requests under 300 ms / all requests. An SLO (objective) is the target for that SLI over a window: 99.5% over 28 days. An SLA (agreement) is a contract with consequences, usually looser than the internal SLO so you have warning before you owe customers anything.
Pick SLIs that users would recognise: the checkout succeeded; the page loaded fast. Pick targets from what users actually need and what the business will fund. 100% is never the right target, because it makes every change impossible.
Error budgets turn reliability into a resource
A 99.5% SLO means 0.5% of events MAY fail. That allowance is the budget. It gets spent on incidents, risky deploys, and experiments. Budget left means the team can ship faster; budget exhausted means slow down and fix things. The budget is what makes the SLO actionable: it gives product and engineering a shared, numeric rule instead of a recurring argument.
Recording rules and naming
Recording rules precompute expensive or repeated expressions. The Prometheus convention for names is level:metric:operations: slo:checkout_errors:ratio_rate5m reads as 'SLO level, checkout errors, a ratio of 5-minute rates'. Consistent names make SLO dashboards and alert rules readable and reusable.
03 The fix
01SLI recording rules for several windows
Burn-rate alerts (Case 4.3) need the error ratio over 5m, 30m, 1h, and 6h. Record each once. The
slolabel names the objective; theservicelabel keeps Alertmanager routing working.obs-lab/prometheus/slo.rules.ymlwhole fileyaml # add "slo.rules.yml" to rule_files in prometheus.yml groups: - name: slo-checkout rules: - record: slo:checkout_errors:ratio_rate5m labels: { service: shoplite, slo: checkout-availability } expr: | sum(rate(http_request_duration_seconds_count{job="shoplite", route="/checkout", status=~"5.."}[5m])) / sum(rate(http_request_duration_seconds_count{job="shoplite", route="/checkout"}[5m])) - record: slo:checkout_errors:ratio_rate30m labels: { service: shoplite, slo: checkout-availability } expr: | sum(rate(http_request_duration_seconds_count{job="shoplite", route="/checkout", status=~"5.."}[30m])) / sum(rate(http_request_duration_seconds_count{job="shoplite", route="/checkout"}[30m])) - record: slo:checkout_errors:ratio_rate1h labels: { service: shoplite, slo: checkout-availability } expr: | sum(rate(http_request_duration_seconds_count{job="shoplite", route="/checkout", status=~"5.."}[1h])) / sum(rate(http_request_duration_seconds_count{job="shoplite", route="/checkout"}[1h])) - record: slo:checkout_errors:ratio_rate6h labels: { service: shoplite, slo: checkout-availability } expr: | sum(rate(http_request_duration_seconds_count{job="shoplite", route="/checkout", status=~"5.."}[6h])) / sum(rate(http_request_duration_seconds_count{job="shoplite", route="/checkout"}[6h]))terminal$ curl -s -X POST localhost:9090/-/reloadcurl -s -X POST localhost:8080/admin/chaos -H 'content-type: application/json' -d '{"scenario":"errors","enabled":false}'── expected output ──{"active":[]}
04 Make sure it never surprises you again
01Write the SLO document, not just the rules
One page per SLO: the SLI definition (exact query), the target and window, who owns it, the error budget policy (what happens when it's spent), and when it will be reviewed. Rules without an agreed policy are just another graph.
docs/slo/checkout-availability.mdwhole filemd # SLO: Checkout availability SLI: successful /checkout responses (non-5xx) / all /checkout responses (recording rule: slo:checkout_errors:ratio_rate5m) Target: 99.5% over a rolling 28 days Budget: 0.5% of checkouts (~900 at current traffic) Owner: ShopLite team · reviewed quarterly with Product Policy: - Budget > 50%: normal release cadence. - Budget < 25%: every release needs a rollback plan and a canary. - Budget exhausted: feature releases pause; the top reliability items from the last 28 days' incidents are prioritised until budget recovers.
05 Your turn: write the query
Run each one against your lab before revealing the answer. Share your own version in the comments; there's usually more than one correct query.
Write an SLI for LATENCY: the proportion of checkouts completing within 1 second, over the last hour.
With a 99.9% SLO over 30 days, how many minutes of complete outage does the budget allow?
06 Interview questions from this case
Explain SLI, SLO, SLA, and error budget.
Why not target 100% reliability?