“The 5% threshold paged us for a 90-second blip — and stayed silent while a 1% error rate ate the whole month's budget in three days.”
case name: Too noisy and too slow at the same time
- Service
- alerting · shoplite checkout
- Impact
- Error budget fully spent without a single page
- Detected by
- The SLO dashboard, days too late
- Time to resolve
- Rules rewritten in an afternoon
Skills you'll use on this case
00 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
- Day 1
FINDING
A deploy causes 1% errors — below the 5% threshold
The static alert never fires. 1% is twice the SLO's allowed 0.5%, which quietly burns budget at 2× speed.
checkout error ratio vs static thresholderror ratio - Day 3
REPORT
The SLO dashboard shows the budget gone
- Day 5
ALERT
Meanwhile: a 90-second spike to 30% errors during a DB failover pages the on-call at 03:12
It resolved itself before anyone opened a laptop, having used about 0.1% of the monthly budget. The static threshold treats a huge but tiny event as urgent and a small but relentless one as fine. Both are backwards.
- Day 6
RESOLVED
Replaced with multi-window burn-rate alerts
Root cause
A fixed error-ratio threshold measures how bad things are RIGHT NOW, not how fast the error budget is being consumed. It pages on short, harmless spikes and misses sustained, low-level failures, which is the opposite of what an SLO-driven team needs.
02 The concepts behind it
Burn rate
Burn rate = current error ratio ÷ allowed error ratio (1 − SLO). A burn rate of 1 spends exactly the whole budget over the SLO window. At 2 the budget is gone in half the window (14 days for 28). At 14.4 it's gone in about 2 days, so one hour of it costs ~2% of the monthly budget. Burn rate makes every alert a statement about the SLO: 'at this pace, we run out in N hours'.
Multi-window, multi-burn-rate
From Google's SRE workbook. PAGE if burn rate > 14.4 over the last 1 h AND over the last 5 min (a fast, significant burn that's still happening). PAGE if > 6 over 6 h AND over 30 min (a medium burn). TICKET if > 1 over 3 days AND over 6 h (a slow burn that will breach the SLO).
The LONG window gives significance: enough budget has actually been spent to matter. The SHORT window makes the alert stop quickly once the problem is fixed, instead of firing for an hour after recovery. The 90-second failover spike never sustains the long window, so it never pages; the 1% error rate trips the slow-burn ticket within hours.
Precision, recall, detection time, reset time
Every alert trades off four things: precision (does firing mean real impact?), recall (does real impact always fire?), detection time, and reset time. Static thresholds are poor on at least two. Burn-rate pairs get good scores on all four, which is why they became the standard for SLO alerting.
03 The fix
01Burn-rate alert rules on top of the recorded SLIs
0.005 is the budget (1 − 0.995). The fast and medium burns page; the slow burn creates a ticket. The 3-day window uses
increase-based math for brevity; in production, record a 3d ratio too.obs-lab/prometheus/slo.rules.ymladd to fileyaml - name: slo-checkout-alerts rules: - alert: CheckoutErrorBudgetFastBurn expr: | slo:checkout_errors:ratio_rate1h > (14.4 * 0.005) and slo:checkout_errors:ratio_rate5m > (14.4 * 0.005) labels: { severity: critical, service: shoplite, slo: checkout-availability } annotations: summary: "Checkout burning error budget at {{ $value | humanizePercentage }} errors (>14.4× budget) — ~2% of monthly budget per hour" - alert: CheckoutErrorBudgetMediumBurn expr: | slo:checkout_errors:ratio_rate6h > (6 * 0.005) and slo:checkout_errors:ratio_rate30m > (6 * 0.005) labels: { severity: critical, service: shoplite, slo: checkout-availability } annotations: summary: "Checkout burning error budget at >6× — budget exhausted in under 5 days at this rate" - alert: CheckoutErrorBudgetSlowBurn expr: | ( sum(increase(http_request_duration_seconds_count{job="shoplite", route="/checkout", status=~"5.."}[3d])) / sum(increase(http_request_duration_seconds_count{job="shoplite", route="/checkout"}[3d])) ) > 0.005 and slo:checkout_errors:ratio_rate6h > 0.005 labels: { severity: warning, service: shoplite, slo: checkout-availability } annotations: summary: "Checkout error rate above SLO for days — on track to breach"terminal$ curl -s -X POST localhost:9090/-/reloadsleep 150 && curl -s localhost:9090/api/v1/alerts | jq -r '.data.alerts[] | .labels.alertname + " " + .state'── expected output ──CheckoutErrorBudgetFastBurn firingCheckoutErrorRatioHigh firing02Retire the static threshold
Delete
CheckoutErrorRatioHighfromalerts.yml. The burn-rate rules cover it with better precision and recall. Then switch the scenario off and watch the fast-burn alert RESOLVE within ~5 minutes, thanks to the short window.terminal$ curl -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
01Apply the same pattern to latency
A latency SLO ('99% of checkouts under 1 s') gets the same treatment, with the SLI from the
le="1"bucket. Once you have the pattern, every SLO gets alerts for free. Tools like Sloth or Pyrra generate these rules from a short SLO spec.
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.
What's the current burn rate of the checkout availability SLO, as a single number?
At a burn rate of 14.4 sustained for one hour, what fraction of a 28-day budget is consumed?
06 Interview questions from this case
What is a burn-rate alert and why is it better than a static error-rate threshold?
Why does each burn-rate alert use two windows?