Everything for SRE Metrics in one place — pick a section below. 25 reviewed items across 4 content types.
Quick Answer
Burn rate measures how fast a service is consuming its error budget relative to a sustainable pace that would exactly exhaust the budget at the end of the SLO window — a burn rate of 1x means you're on track to use exactly 100% of the budget by window end, while 10x means you'd exhaust it ten times faster than sustainable. The same 1% error rate is catastrophic viewed over a 1-hour window (implying total budget exhaustion in hours) but comparatively mild viewed over a 6-hour window, which is exactly why mature alerting evaluates burn rate across multiple time windows simultaneously rather than firing on a single fixed error-rate threshold.
Detailed Answer
Burn rate is like judging whether a car is going to run out of gas before reaching its destination — knowing the fuel gauge reads 'half full' tells you almost nothing on its own; what actually matters is the rate of consumption relative to the remaining distance. A car burning fuel at a rate that would empty the tank in 500 miles is perfectly fine for a 200-mile trip and alarming for a 1,000-mile one. An SLO's error budget is the fuel tank, the 30-day (or whatever) SLO window is the trip distance, and burn rate is precisely that consumption-rate-relative-to-remaining-distance calculation, expressed as a multiple of the 'sustainable' rate that would use exactly 100% of the budget by the end of the window and no more.
This multiple-of-sustainable-rate framing was designed specifically to solve the problem of a single fixed error-rate threshold being either too noisy or too slow depending on how long the bad behavior persists. A fixed alert like 'page if error rate exceeds 1%' either fires on every brief, self-resolving blip (paging for something that would have consumed a negligible fraction of the actual monthly budget) or, if you raise the threshold to avoid that noise, fails to catch a sustained, lower-level degradation that quietly exhausts the entire monthly budget over several days without ever crossing the naive fixed threshold. Burn rate solves this by making the alert's severity a function of both how bad the error rate is and how long it's projected to last, expressed relative to the budget itself rather than an arbitrary absolute number.
Step by step, the calculation multiplies together two dimensions on purpose: burn rate equals the observed error rate divided by the budget's allowed error rate (1 minus the SLO target), evaluated over a specific lookback window. A 99.9% SLO allows a 0.1% error rate as its sustainable baseline; observing a 1.44% error rate over a short window is a burn rate of 14.4x — at that pace, the entire 30-day budget would be gone in about 30/14.4 ≈ 2 days, which is genuinely urgent and worth an immediate page. The same 1.44% error rate observed as an average over a much longer 6-hour window, if it's actually a brief spike that's already resolved, produces a much smaller *sustained* burn-rate reading because the long window dilutes a short spike down toward the noise floor — this is precisely why Google's SRE workbook recommends alerting on *multiple* window-and-threshold pairs simultaneously (commonly a fast 1-hour/5-minute pair for immediate paging and a slower 6-hour/30-minute pair for ticket-level investigation) rather than picking just one window size, since a single window size is always a trade-off between catching short severe spikes and catching long slow leaks.
At production scale, teams typically implement a two-tier (sometimes four-tier) burn-rate alerting scheme: a high-multiplier, short-window alert (e.g., 14.4x sustained for 2 minutes) pages immediately because it implies the budget could be fully gone within hours if unaddressed, while a lower-multiplier, longer-window alert (e.g., 3x sustained for an hour) creates a ticket for investigation during business hours because at that pace, the budget still has days of runway left even if the trend continues unaddressed. The engineering value of this design is that it makes the page-versus-ticket decision an automatic, calculated output rather than something an on-call engineer has to mentally estimate under pressure during an actual incident.
The gotcha: teams that implement burn-rate alerting with only a single window size inevitably end up either paging on harmless short blips (window too short) or missing a real, slow-building budget-exhausting trend until it's too late to fix gracefully (window too long) — the fix genuinely requires at least two window-and-multiplier pairs evaluated in parallel, and skipping this dual-window design is one of the most common mistakes teams make when they implement error budgets for the first time, often discovering the gap only after either alert fatigue or a missed slow-burn incident forces them to revisit the design.
Code Example
# Burn rate formula: observed error rate / SLO's allowed error rate, over a given window
# For a 99.9% SLO, allowed error rate = 1 - 0.999 = 0.001
# Fast-burn alert: short window, high multiplier — pages immediately
- alert: PaymentsAPIFastBurnRate
expr: |
(
sum(rate(http_requests_total{job="payments-api",status=~"5.."}[5m]))
/ sum(rate(http_requests_total{job="payments-api"}[5m]))
) > (14.4 * 0.001)
for: 2m
labels: { severity: page }
annotations: { summary: "Budget exhausted in ~2 days at this rate" }
# Slow-burn alert: long window, low multiplier — creates a ticket, not a page
- alert: PaymentsAPISlowBurnRate
expr: |
(
sum(rate(http_requests_total{job="payments-api",status=~"5.."}[6h]))
/ sum(rate(http_requests_total{job="payments-api"}[6h]))
) > (3 * 0.001)
for: 1h
labels: { severity: ticket }
annotations: { summary: "Budget exhausted in ~10 days at this rate" }Interview Tip
A junior engineer typically explains burn rate as 'how fast you're using the error budget' without going further, but for a senior role the interviewer is actually looking for the dual-window design — why a single fixed error-rate threshold is inherently a trade-off between noise and missed slow leaks, and why evaluating both a short high-multiplier window and a longer low-multiplier window simultaneously is the actual fix. Being able to walk through the arithmetic (14.4x sustained implies budget exhaustion in about 2 days for a 30-day window) shows real fluency with the calculation rather than memorized terminology. The strongest candidates connect this directly to reducing on-call cognitive load — the page/ticket decision becomes a calculated output of the alerting rule rather than something a human has to judge under pressure mid-incident.
◈ Architecture Diagram
sustainable rate (1x): ●●●●●●●● budget lasts 30d current rate (14.4x): ●●●●●●●● budget lasts ~2d → PAGE current rate (3x): ●●●●●●●● budget lasts ~10d → ticket
💬 Comments
Quick Answer
Start by correlating the exact burn window against your deploy timeline and dependency health dashboards, since a sudden budget cliff almost always maps to a specific change event rather than gradual organic drift, then narrow using error breakdown by endpoint, status code, and upstream dependency to find the specific failure signature. Whether to trigger a freeze depends on whether the root cause is fixed or still ongoing — a resolved one-time incident that already burned the budget is a retrospective conversation, while an unresolved, still-active burn is exactly the scenario the freeze policy exists for.
Detailed Answer
A sudden 60-percentage-point budget drop in six hours is like a household's bank balance dropping sharply in a single afternoon — the sensible first move isn't reviewing every transaction from the past month, it's pulling up exactly what happened in that afternoon's window, because a change that dramatic almost never comes from gradual, ordinary spending, it comes from one identifiable event: a large purchase, a fraud charge, a mistaken transfer. Error budgets behave the same way — organic, gradual drift rarely produces a cliff this steep this fast, so the investigation should start by asking 'what changed in this exact six-hour window,' not by re-examining broad service health from scratch.
This correlation-first approach exists because budget burn, by construction, is just an aggregate of a huge number of individual failed or slow requests, and that aggregate number alone tells you nothing about causation — it's a symptom-level metric, not a diagnostic one. The entire reason to track deploy history, dependency health, and configuration changes alongside SLO dashboards is so that when the aggregate number moves sharply, you have an immediate, pre-correlated list of candidate causes to check, rather than starting a root-cause investigation completely from scratch under time pressure.
Step by step: first, pull the deploy timeline for checkout-worker itself and every service it directly depends on, and overlay it against the exact start time of the burn — if a deploy landed in or near that six-hour window, that's the leading suspect and should be checked first, since deploys are statistically the single most common trigger of sudden reliability regressions. Second, if no relevant deploy lines up, check the health and latency of checkout-worker's direct dependencies (databases, downstream APIs, message queues) over that same window, since an upstream degradation can burn a *downstream* service's budget even when the downstream service's own code never changed at all. Third, break down the failing requests themselves by endpoint and status code — a burn concentrated on one specific endpoint or a single error code (say, every failure is a 503 from one particular downstream call) narrows the search dramatically faster than treating the whole service as uniformly degraded. Fourth, check whether the burn is still actively ongoing at the moment of investigation or whether it was a discrete, already-resolved incident (a bad deploy that was already rolled back, a transient dependency blip that's already recovered) — this distinction is the actual determinant of what happens next.
At the organizational level, this is exactly where the pre-agreed freeze policy gets tested for real: if the burn is over and resolved, the appropriate response is a postmortem and, if the remaining budget for the window is now thin, heightened caution on further risky changes — but a full feature freeze over an already-resolved incident is usually disproportionate. If the burn is still active and ongoing, that's precisely the scenario the freeze policy was designed for: continuing to ship new changes into an environment that's already actively burning budget compounds risk on top of an unresolved problem, and the freeze exists to stop adding new variables until the existing one is under control.
The gotcha: teams that treat 'trigger the freeze' as an automatic, mechanical response the instant budget drops below a threshold — without first distinguishing an already-resolved historical burn from an actively ongoing one — end up freezing feature work over incidents that are already fixed, which trains the organization to see the freeze policy as arbitrary and punitive rather than a genuinely protective mechanism, and erodes the willingness of product stakeholders to honor the policy the next time it's actually needed for a real, ongoing problem.
Code Example
# Step 1: overlay deploy events against the exact burn window
kubectl rollout history deployment/checkout-worker -n checkout
# Cross-reference deploy timestamps against the burn window start time from your SLO dashboard
# Step 2: check dependency health over the same window (example: Prometheus query)
sum(rate(http_requests_total{job="postgres-proxy", status=~"5.."}[5m]))
sum(rate(grpc_requests_total{job="payments-api", grpc_code!="OK"}[5m]))
# Step 3: break down checkout-worker's failures by endpoint and status code
sum by (endpoint, status) (
rate(http_requests_total{job="checkout-worker", status=~"5.."}[5m])
)
# Step 4: confirm whether the burn is still active right now
sli:checkout_worker_success_rate:5m # if this has already recovered, burn is historical, not ongoing
# If still active and tied to a specific deploy, roll back immediately
kubectl rollout undo deployment/checkout-worker -n checkoutInterview Tip
A junior engineer typically jumps straight to 'trigger the feature freeze because the budget dropped,' but for a senior role the interviewer is actually looking for a correlation-first investigation — overlaying the exact burn window against deploys and dependency health, since a steep sudden drop almost always maps to a specific change rather than gradual drift. The strongest answers explicitly distinguish an already-resolved historical burn from an actively ongoing one, because that distinction is what should actually determine whether a freeze is warranted, not the raw budget percentage alone. Mentioning that treating the freeze as a mechanical, automatic response regardless of context erodes organizational trust in the policy shows systems-level thinking about how reliability processes function politically, not just technically.
◈ Architecture Diagram
budget: 80% ──▶ 20% in 6h │ │─ deploy at t-2h? ──▶ ✓ prime suspect │─ dependency health dip? ──▶ check upstream │─ error concentrated on 1 endpoint? ──▶ narrow fast └─ still burning now? ──▶ yes: freeze / no: postmortem
💬 Comments
Quick Answer
Track toil percentage (manual, repetitive on-call work as a share of total on-call hours), pages per shift, after-hours page rate, and mean time to acknowledge trending upward over time, since these process-health metrics reveal organizational strain long before it shows up in attrition or morale surveys. The key principle is treating on-call sustainability itself as a measurable system with leading indicators, the same way you'd treat service reliability, rather than waiting for a lagging indicator like someone actually quitting.
Detailed Answer
Most teams instrument their services obsessively — latency, error rate, saturation, traffic — but treat the health of the humans operating those services as something you just informally sense from morale or notice only after someone quits. That's like a factory meticulously monitoring every machine on the line while never checking whether the workers running those machines are being pushed past a sustainable pace — the machines can look perfectly healthy on every dashboard right up until the workers operating them start walking out, at which point it's far too late to have caught the problem early. On-call sustainability deserves the same instrumentation discipline as the services it protects, precisely because human burnout, like service degradation, has measurable leading indicators long before it becomes a visible, painful lagging one.
This reframing — treating the on-call process itself as a system with SLIs, not just a duty roster — exists because burnout is otherwise almost always caught too late. Exit interviews and engagement surveys are lagging indicators; by the time someone reports burnout in a survey or, worse, resigns, the underlying operational conditions that caused it have usually been present and worsening for months. A platform team that only reacts to those late signals is running on-call reactively in exactly the same way a service that only alerts on outright downtime, rather than leading indicators of degradation, is running reliability reactively.
Step by step, the metrics worth building dashboards around: toil percentage — the fraction of on-call hours spent on manual, repetitive interventions that could in principle be automated, tracked weekly per person and per team, with a rising trend flagged well before it crosses an unsustainable threshold (commonly cited around 50%). Pages per shift — both the raw count and its trend over time, since a rotation that used to average one page per shift creeping toward four or five over a few months is a measurable early warning even if no single week looks alarming in isolation. After-hours and overnight page rate specifically, separated from business-hours pages, because a page at 3 AM has a categorically different human cost than the same page at 2 PM, and blending them into one aggregate metric hides exactly the dimension that correlates most directly with burnout and sleep disruption. Mean time to acknowledge and its trend — a rotation where acknowledgment times are quietly creeping up over weeks is often an early, indirect signal that on-call engineers are increasingly reluctant to engage quickly, frequently because they've learned from experience that pages are usually low-value noise rather than genuine emergencies.
At organizational scale, the most mature teams review these on-call health metrics in the same recurring operational review cadence as service-level reliability metrics — not as a one-off survey exercise, but as a standing agenda item with an owner and a trend line, exactly like an SLO dashboard. This normalizes discussing on-call sustainability as an ongoing engineering concern with real data behind it, rather than an occasional, hard-to-quantify cultural conversation that only comes up after morale has already visibly deteriorated.
The gotcha: teams that track these metrics in aggregate across an entire large team can miss a severe, concentrated burnout risk sitting on just one or two individuals — if one person is disproportionately picking up extra shifts, covering for a teammate on leave, or handling most of the after-hours pages because they happen to respond fastest, a team-wide average of 'reasonable' pages per shift can look completely healthy while that one specific person is quietly approaching burnout, invisible until the aggregate view is broken down per individual rather than only viewed at the team level.
Code Example
# Weekly on-call health dashboard queries (example: PromQL against PagerDuty/Opsgenie exporter)
# Toil percentage: manual/repeatable interventions vs total on-call hours, per engineer
sum by (engineer) (toil_hours_weekly) / sum by (engineer) (oncall_hours_weekly)
# Pages per shift, trended over the last 8 weeks to catch a creeping increase
sum by (week) (pages_total{team="platform"}) / count by (week) (oncall_shifts_total{team="platform"})
# After-hours page rate specifically, separated from business-hours pages
sum(pages_total{hour_bucket="after_hours"}) / sum(pages_total)
# Mean time to acknowledge, trended weekly — a rising trend signals disengagement
avg_over_time(page_ack_seconds[7d])
# Per-individual breakdown, to catch concentrated burnout risk an aggregate hides
topk(3, sum by (engineer) (pages_total{team="platform"}[4w]))Interview Tip
A junior engineer typically answers with generic service metrics like error rate and latency, but for a senior role the interviewer is actually looking for whether you think to instrument the on-call *process itself* — toil percentage, pages per shift trend, after-hours rate, acknowledgment time — as leading indicators of burnout, rather than waiting for a lagging signal like a resignation or a bad engagement survey. The strongest answers point out that team-wide averages can hide a single individual quietly absorbing disproportionate on-call load, and that per-person breakdowns are necessary to catch that risk before it becomes visible at the aggregate level. Framing on-call sustainability as a system with its own SLIs, reviewed with the same operational rigor as service reliability, shows mature SRE thinking beyond just keeping services up.
◈ Architecture Diagram
team avg pages/shift: 1.8 ── looks healthy per-engineer breakdown: engineer A: 1.0 ✓ engineer B: 1.2 ✓ engineer C: 5.4 ✗ ── hidden burnout risk in aggregate
💬 Comments
Quick Answer
Isolate the SLI to only what genuinely represents user-facing impact — excluding retried, non-critical, or best-effort calls from the success/failure calculation — and route alerts through dependency-aware correlation so a known-flaky, non-critical dependency's failures don't independently trigger a page if the primary user-facing SLI is unaffected. The core design principle is that burn-rate alerts should fire on symptoms (actual degraded user experience) rather than on every individual cause, since a robust system can absorb a flaky non-critical dependency's failures without that ever reaching the user at all.
Detailed Answer
This is like a hospital's alarm system paging the on-call surgeon every time a vending machine on a different floor malfunctions, just because both incidents technically happened somewhere in the building. The alarm system conflates 'something, somewhere, isn't working' with 'a patient's condition requires an immediate specialist,' and that conflation is exactly what erodes trust in the alarm over time — eventually the surgeon starts treating every page with skepticism, which is dangerous the one time it's a real emergency. SLO burn-rate alerting has to make the same distinction deliberately: not every internal failure deserves to page a human, only the ones that actually threaten the user-facing promise the SLO represents.
The design principle at the heart of this is symptom-based alerting versus cause-based alerting. An SLI should be defined around what the user actually experiences — did their request succeed within an acceptable time — not around whether every internal component behaved perfectly. If payments-api calls a non-critical fraud-scoring service with a fallback (skip scoring and proceed conservatively) when that dependency is slow or unavailable, then a flaky fraud-scoring dependency failing shouldn't show up as a payments-api SLI violation at all, because the user's actual request still succeeded — the fallback absorbed the failure exactly as designed. If the alerting doesn't distinguish this, every blip in that one non-critical dependency independently pages the entire on-call rotation for a problem that, by design, never reached a real user.
Step by step, building this correctly requires several deliberate choices: first, define the SLI's success criteria to reflect genuine end-to-end user outcome, explicitly excluding calls that were retried and succeeded, or that fell back gracefully and still returned a valid response to the user — the raw request either succeeded from the user's point of view or it didn't, and internal retries or fallbacks that worked should count as success, not failure. Second, for dependencies you've explicitly identified as non-critical (with a working fallback path), route their own failure metrics to a separate, lower-severity signal — track them, absolutely, since a fallback path being invoked constantly instead of rarely is itself useful information, but don't let that separate signal independently trigger the same page-worthy alert as the primary user-facing SLI. Third, where a dependency actually is critical (no fallback exists, and its failure genuinely does break the user-facing request), its failures should already show up correctly in the primary SLI's burn rate without needing a separate alert at all — the SLI-based approach is precisely designed so you don't need a hand-maintained list of "which dependencies matter," because a dependency that matters will already move the number that matters.
At production scale, this distinction is what prevents alert fatigue from a very common but often overlooked source: over-instrumentation of internal component health, layered on top of proper SLO-based alerting, effectively double-counts the same underlying problem through two different alerting paths — one appropriately symptom-based and page-worthy, one cause-based and noisy. Teams that carefully audit which of their alerts are cause-based versus symptom-based often discover the majority of their 3 AM pages trace back to internal component alerts that never actually represented real user impact, and retiring or downgrading those to tickets is frequently the single highest-leverage change available to reduce on-call burden without weakening actual incident detection at all.
The gotcha: teams that build fallback logic for a non-critical dependency but never update their alerting to match often keep an old cause-based alert ("page if fraud-scoring service error rate exceeds X%") that predates the fallback being built, so the fallback quietly and correctly protects users from ever seeing an impact, while the stale alert keeps paging on-call for a problem that, by design, no longer affects anyone — the fix isn't tuning the threshold on that old alert, it's recognizing the alert itself no longer represents anything a human needs to act on and retiring it in favor of trusting the primary, already-correct SLI.
Code Example
# SLI defined around true user-facing outcome — retries and fallbacks that succeeded still count as success
- record: sli:payments_api_user_success:5m
expr: |
sum(rate(http_requests_total{job="payments-api", final_outcome="success"}[5m]))
/ sum(rate(http_requests_total{job="payments-api"}[5m]))
# final_outcome reflects the response actually sent to the user,
# regardless of whether a non-critical dependency (e.g. fraud-scoring) failed internally
# Primary burn-rate alert: pages based on the true user-facing SLI only
- alert: PaymentsAPIUserFacingFastBurn
expr: (1 - sli:payments_api_user_success:5m) > (14.4 * 0.001)
for: 2m
labels: { severity: page }
# Non-critical dependency failure: tracked, but routed to a ticket, never pages directly
- alert: FraudScoringFallbackInvokedFrequently
expr: rate(fraud_scoring_fallback_total[15m]) > 0.5
labels: { severity: ticket }
annotations:
summary: "Fraud-scoring fallback engaging often — investigate dependency, but no user impact detected"Interview Tip
A junior engineer typically tries to fix noisy pages by tuning thresholds or adding suppression rules per dependency, but for an architect-level role the interviewer is actually looking for the deeper fix — defining the SLI around true user-facing outcome so that a non-critical dependency with a working fallback simply never shows up as an SLI violation in the first place, making dependency-specific alert tuning unnecessary. The strongest answers explain the difference between symptom-based alerting (paging on actual degraded user experience) and cause-based alerting (paging on every internal component hiccup), and point out that stale cause-based alerts left over from before a fallback was built are one of the most common, fixable sources of on-call noise. This question tests whether someone designs alerting around user impact as a first principle, rather than accumulating ad hoc alert rules over time.
◈ Architecture Diagram
fraud-scoring dep fails ──▶ fallback engages ──▶ user still succeeds
│ │
↓ ↓
ticket-level signal SLI: success (no page)
(tracked, not urgent) ✓ symptom-based alerting💬 Comments
Quick Answer
Start with an SLI (Service Level Indicator — a directly measurable metric like the percentage of requests under 200ms) and set an SLO (Service Level Objective — a target for that metric, like 99.9% over 30 days); the error budget is simply 1 minus the SLO, expressed as an allowance of acceptable failure, roughly 43 minutes of downtime a month at 99.9%. Once that budget is exhausted, the agreed consequence is a reliability freeze — new feature launches pause and the team's priority shifts entirely to reliability work until the budget recovers, which only works if that consequence was agreed to and enforced before the crisis, not negotiated in the moment.
Detailed Answer
An error budget is like a household's monthly discretionary spending limit on top of fixed bills — you're not expected to spend zero on entertainment, you're allowed a specific, agreed amount, and the moment you blow through that amount, the sensible response is not to keep spending as normal and hope it works out, it's to stop discretionary spending until next month's budget resets. SLOs and error budgets formalize exactly this idea for reliability: perfection (100% uptime, zero errors) is neither achievable nor actually necessary for most services, so instead of chasing an impossible target, teams agree in advance on how much unreliability is acceptable, and then treat that allowance as a real, trackable budget rather than a vague aspiration.
This framework exists because, without it, reliability work and feature work compete for the same engineering time with no objective tiebreaker — every team believes their velocity matters and every team believes reliability matters, and without a quantified, pre-agreed threshold, that tension gets resolved by whoever argues loudest in a planning meeting rather than by data. Google's SRE model introduced error budgets specifically to convert 'how reliable is reliable enough' from a subjective argument into an objective, numeric answer that both the reliability-focused and feature-focused sides of an organization agree to up front, before there's a live incident to argue about.
Step by step: first, define the SLI — pick the specific, measurable signal that represents user-facing quality, commonly success rate (percentage of requests that don't return a 5xx) or latency (percentage of requests under some threshold, like p99 under 200ms). Second, set the SLO as a target percentage of that SLI over a rolling window, commonly 30 days — 99.9% is a common target implying roughly 43 minutes of acceptable downtime or SLI-violating time per month. Third, the error budget is simply the inverse: 1 minus 99.9% equals 0.1%, which converts into that same ~43 minutes over 30 days, or however many failed requests that percentage represents given your traffic volume. Fourth, track budget consumption continuously — not just after the fact — using recording rules that calculate a rolling burn rate (how fast you're consuming the budget relative to a sustainable pace), so the team knows in real time whether they're on track to exhaust the budget early or comfortably coast to the end of the window with room to spare.
At production scale, the actual mechanics matter as much as the definition: teams implement this with Prometheus recording rules that continuously calculate the SLI over rolling windows, feed that into a burn-rate calculation, and alert on two distinct thresholds — a fast-burn alert (consuming budget so quickly that at the current rate you'd exhaust it in hours, demanding immediate paging) and a slow-burn alert (a steadier, less urgent trend that would exhaust the budget in days, worth investigating but not necessarily waking anyone up at 3 AM). What actually happens when the budget hits zero is the part organizations most often get wrong in practice: the 'freeze new features until reliability improves' consequence only has teeth if it was agreed to by both engineering leadership and product stakeholders *before* the budget ran out — negotiating it in the moment, mid-crisis, means whoever has the most organizational leverage simply overrides it, and the entire framework collapses back into the same subjective argument it was designed to eliminate.
The gotcha: teams that only look at the error budget number at the end of the 30-day window, rather than tracking burn rate continuously, discover the budget is already gone with two weeks still left in the window and have no way to react proactively — by definition, a lagging, retrospective view of budget consumption can only tell you the crisis already happened, never that it's *about* to happen, which is exactly why burn-rate alerting (not just a raw budget-remaining dashboard) is the difference between an SRE practice that actually prevents outages and one that just narrates them after the fact.
Code Example
# Prometheus recording rule: rolling 5-minute SLI for a request-success SLO
- record: sli:payments_api_success_rate:5m
expr: |
sum(rate(http_requests_total{job="payments-api", status!~"5.."}[5m]))
/ sum(rate(http_requests_total{job="payments-api"}[5m]))
# Error budget remaining, given a 99.9% SLO target over a 30-day window
- record: sli:payments_api_error_budget_remaining:30d
expr: |
1 - ((1 - avg_over_time(sli:payments_api_success_rate:5m[30d])) / (1 - 0.999))
# Fast-burn alert: consuming budget at a rate that would exhaust it in hours
- alert: PaymentsAPIFastBurn
expr: |
(1 - sli:payments_api_success_rate:5m) > (14.4 * 0.001) # 14.4x sustainable burn rate
for: 2m
labels: { severity: page }
# Slow-burn alert: steadier trend that would exhaust budget in days, worth investigating
- alert: PaymentsAPISlowBurn
expr: |
(1 - avg_over_time(sli:payments_api_success_rate:5m[6h])) > (6 * 0.001)
for: 15m
labels: { severity: ticket }Interview Tip
A junior engineer typically defines SLOs and error budgets correctly on paper but stops there, while for a senior role the interviewer is actually looking for whether you understand that the 'freeze features when budget is exhausted' consequence only works if it's negotiated and agreed to by product and engineering leadership *before* a crisis, not improvised during one — without that pre-agreement, the whole framework collapses back into a political argument. The strongest answers distinguish fast-burn from slow-burn alerting and explain why a single end-of-window budget check is purely retrospective and useless for prevention, while continuous burn-rate tracking is what actually lets a team react before the budget is fully gone. Bringing up real PromQL for burn-rate calculation, not just the conceptual definition, signals hands-on SRE experience rather than textbook familiarity.
◈ Architecture Diagram
SLO target: 99.9% ── budget: 43min/30days burn rate: ●●●●●●●● ── fast burn: page now burn rate: ●●●●●●●● ── slow burn: ticket, investigate budget = 0 ──▶ ✗ feature freeze (must be pre-agreed)
💬 Comments
Quick Answer
A sustainable rotation needs a minimum team size (commonly 8+ for full 24x7 coverage), a primary-plus-secondary escalation structure, alerts tiered so only genuine customer-impacting issues page a human, and an explicit toil budget — tracking how much on-call time goes to repetitive manual work versus real engineering, with automation prioritized once toil crosses roughly 50%. Adding more people spreads the same number of low-value pages across more humans, which reduces individual burnout risk but does nothing to fix the underlying alert quality problem that's actually causing burnout in the first place.
Detailed Answer
Imagine a fire department that gets called out constantly for burst water pipes, not actual fires — adding more firefighters to the roster does reduce how often any single firefighter gets called, but it doesn't address the real problem, which is that most of the calls shouldn't require a firefighter at all. On-call burnout works exactly the same way: the actual cause is almost never 'too few people in the rotation,' it's alerts that page a human for things that don't need urgent human judgment — noisy, low-value pages that erode trust in the alerting system and train people to view every page with dread rather than as a meaningful signal.
This is why the on-call design problem should be tackled as an alert-quality problem first and a staffing-math problem second. Google's SRE practice frames this explicitly through the concept of toil — repetitive, manual, automatable operational work that scales linearly with service growth rather than getting cheaper over time — and recommends capping toil at roughly 50% of an SRE's time specifically because toil that exceeds that threshold means the team is spending more time reacting to the same categories of problems than it is spending building the automation that would prevent those problems from recurring, a trap that gets structurally worse, not better, the longer it's tolerated.
Step by step, a well-designed rotation layers several independent safeguards: first, alert tiering — only genuinely customer-impacting symptoms (elevated error rate, breached latency SLO, full service unavailability) should page a human immediately; internal, non-urgent signals (a single retryable failed job, a metric drifting but still within a safe range) should generate a ticket or a dashboard entry, not a 2 AM page. Second, a primary-plus-secondary structure means a single missed or delayed acknowledgment doesn't leave an incident unattended — the secondary is a deliberate redundancy, not just a backup name on a list nobody expects to actually page. Third, hard caps on pages per shift (a common target is fewer than two genuine pages per shift) act as a forcing function: if a service consistently exceeds that cap, that's treated as a signal the alerting or the service itself needs engineering attention, not just something to individually tolerate. Fourth, blameless postmortems — analyzing what happened and why without assigning individual fault — exist because on-call engineers who fear blame for an incident start hiding problems or delaying escalation, which directly undermines the psychological safety needed for people to sustainably volunteer for high-stakes rotations at all.
At organizational scale, teams that track toil explicitly (logging roughly how much on-call time each week goes to manual, repeatable interventions versus genuine incident response or engineering work) get an early, objective warning sign long before individual burnout becomes visible through attrition or morale surveys — a toil percentage climbing past 50% for several consecutive weeks is a leading indicator that the team's operational load has outgrown its automation, and is a far earlier signal than waiting for someone to actually quit or burn out visibly.
The gotcha: teams under pressure to 'fix on-call burnout' often reach first for the easiest lever — hire more people, widen the rotation — because it's organizationally simple to approve and feels proactive, but if the underlying page volume and alert quality don't improve, the same number of low-value pages simply gets divided across a larger group, which measurably reduces each individual's page count without addressing why those pages exist in the first place, and the team is right back where it started the moment headcount growth stalls or the service scales up traffic again.
Code Example
# Alert tiering example: only customer-impacting symptoms page immediately
- alert: PaymentsAPIHighErrorRate
expr: sum(rate(http_requests_total{job="payments-api",status=~"5.."}[5m])) / sum(rate(http_requests_total{job="payments-api"}[5m])) > 0.01
for: 5m
labels:
severity: page # customer-impacting: pages a human immediately
- alert: PaymentsAPIRetryableJobFailure
expr: increase(job_failures_total{job="payments-api", retryable="true"}[15m]) > 0
labels:
severity: ticket # not customer-impacting: creates a ticket, no page
# Escalation policy (PagerDuty-style config)
# Level 1: Primary on-call, 5 min ack window
# Level 2: Secondary on-call, 10 min ack window
# Level 3: Engineering manager, 15 min ack window
# Weekly toil tracking query: hours spent on manual/repeatable work vs total on-call hours
toil_hours_this_week / total_oncall_hours_this_week # target: keep under 0.50Interview Tip
A junior engineer typically answers 'add more people to the rotation and set up a secondary,' but for a senior role the interviewer is actually looking for the insight that burnout is fundamentally an alert-quality and toil problem, not a headcount problem — adding people to a rotation full of noisy, low-value pages just spreads the same dysfunction across more humans without fixing it. The strongest answers bring up Google's explicit 50% toil threshold as a leading indicator that predates visible burnout or attrition, and explain why blameless postmortems are a structural requirement for psychological safety in on-call, not just a nice cultural practice. Mentioning hard page-per-shift caps as a forcing function that triggers engineering investment, rather than something individuals are just expected to tolerate, shows systems-level thinking about sustainability.
◈ Architecture Diagram
┌─ noisy alerts ──┐ ┌─ tiered alerts ─────┐
│ every anomaly │ │ customer-impact→page│
│ pages a human │ ✗ │ internal signal→ticket✓│
└──────┌──────────┘ └──────┌───────────────┘
↓ ↓
burnout (more people sustainable (fewer,
doesn't fix root cause) higher-value pages)💬 Comments
Quick Answer
Measure as close to the user experience as feasible: server-side SLIs miss whole failure classes (DNS, LB, network, TLS, the server being down), LB-side catches most of those, and client/synthetic measurement catches everything but adds noise you don't control. Most teams should anchor SLOs at the load balancer and complement with synthetic probes; pure server-side SLOs quietly overstate reliability.
Detailed Answer
The measurement point defines the denominator. Server-side (app metrics): cheapest, richest labels — but requests that never reached the app (crashed pods, connection-refused, LB misroutes, DNS failures) simply don't exist in the data, so your 'availability' excludes exactly the worst outages; a service that's down reports no errors. LB/gateway-side: sees every request that reached your edge including 5xx from dead backends and timeouts — the sweet spot of coverage vs control for most services; this is where the SLO's error and latency SLIs should usually live. Client-side (RUM) and synthetics: capture DNS, CDN, TLS, last-mile — the real user truth — but RUM mixes in user networks/devices you can't fix, so it's better for tracking than for paging; synthetics give controlled outside-in coverage for the paths RUM can't (before launch, low-traffic endpoints) and catch 'green dashboards, site down' failures. Practical answer: page on LB-measured multi-window burn rate, run synthetics as the independent check, watch RUM for trend truth, and keep server-side metrics for debugging. And measure latency as a distribution at the same point — a p99 SLI computed from per-pod averages is fiction.
Code Example
# SLI at the edge (envoy/ALB), not the app slo: checkout-availability sli: 1 - (lb_5xx + lb_timeouts) / lb_requests window: 30d, objective: 99.9% synthetic: probe /checkout from 3 regions every 30s # catches 'nothing reaches the LB' rum: track true user p95 as trend, not page
Interview Tip
'A dead server reports no errors' is the sentence that shows you understand why server-side availability SLIs lie. Then name the layered answer: page at the LB, verify with synthetics, trend with RUM.
💬 Comments
Quick Answer
Toil is manual, repetitive, automatable, reactive work that scales with service growth — measure it by tagging tickets/interrupts and periodic time-tracking sampling, targeting the classic <50% of SRE time bound. Prioritize automation by frequency x time-per-occurrence x growth trajectory, and fund it explicitly — toil above the bound is a staffing/roadmap decision, not a personal failing.
Detailed Answer
Definition discipline matters because everything unpleasant gets called toil: the test is manual + repetitive + automatable + tactical + no enduring value + O(service growth). Overhead (meetings, planning) isn't toil; novel incident response isn't toil (it's unplanned but not repetitive); the fifth identical certificate rotation absolutely is. Measurement: tag every ticket/page/interrupt with a toil category at close (same one-click discipline as alert dispositions); run quarterly time-sampling surveys as the cross-check (ticket data undercounts untracked interrupts); report toil-percentage per team with trend lines. The 50% ceiling (Google's canon) is a circuit breaker: above it, the team is servicing scale instead of engineering it away, and the number becomes an argument for headcount or roadmap change. Prioritization is expected-value arithmetic: annual occurrences x minutes x (1 + growth rate), against automation cost — plus risk-weighting for toil that occurs during incidents (3am toil compounds into outages via human error). One more honest input: automation that removes toil for the automating team but creates review burden elsewhere hasn't removed toil, it's exported it — count the system, not the team.
Code Example
toil_score = freq_per_year * minutes_each * (1 + yoy_growth) / 60 # hours/yr backlog (sorted): cert-rotations: 52 * 45 * 1.0 = 39h/yr -> automate (2d effort) db-failover-drill: 4 * 240 * 1.0 = 16h/yr -> keep manual (practice value) stale-node-cleanup: 180 * 12 * 1.4 = 50h/yr -> automate first
Interview Tip
Cite the definition tests (not everything unpleasant is toil) and the 50% bound as a circuit breaker that triggers staffing conversations — that's the canonical-but-practiced answer. The 'exported toil' caveat earns senior points.
💬 Comments
Quick Answer
MTTR averages a fat-tailed distribution (one 8-hour incident swamps twenty 5-minute ones), conflates detection/triage/mitigation/repair into one number, and invites gaming (close fast, reopen quietly). Better: percentile-based phase timings (detect, engage, mitigate) per severity, plus distribution-aware views — and pair speed metrics with recurrence and postmortem-action completion so you're not just getting faster at having the same incident.
Detailed Answer
Three structural problems: (1) statistical — incident durations are log-normal-ish with heavy tails; the mean tracks your worst incident, not your typical one, so 'MTTR improved 30%' usually means 'we had fewer monsters this quarter', which is luck, not capability; (2) aggregation — time-to-detect (monitoring quality), time-to-engage (paging/escalation), time-to-mitigate (runbooks, rollback tooling), and time-to-full-repair are owned by different systems; one blended number can't tell you which to invest in; (3) incentive — a single headline number rewards closing tickets over restoring service. The better dashboard: per-severity percentiles (median and p90) of each phase; 'customer-first detection' count (should trend to zero); mitigation-vs-repair separated (mitigate fast, repair calmly); recurrence rate (same root-cause class within 90 days) as the counterweight — a team that mitigates in 5 minutes but has the same incident weekly is failing; and postmortem action-item completion within SLA as the learning-loop health check. Present distributions, not means, in ops reviews. The mature framing: incident speed metrics measure your response machinery; recurrence and action-completion measure whether the organization learns.
Code Example
incident scorecard (quarterly, per severity): detect_p50/p90: 2m / 11m (alert quality) engage_p50/p90: 3m / 9m (paging/escalation) mitigate_p50/p90: 14m / 62m (runbooks, rollback) customer_first_detections: 1 (target 0) recurrence_90d: 8% (learning loop) postmortem_actions_on_time: 74%
Interview Tip
Lead with the fat-tail statistics problem (means lie about skewed distributions), then decompose into phases with different owners — that two-step critique is what distinguishes metric literacy from metric recitation.
💬 Comments
Quick Answer
SLI is a measured indicator (e.g., availability = 99.95%), SLO is the internal target on it (e.g., 99.9% uptime), and SLA is the external contract with the customer (looser, with a penalty if breached). SLI = measurement, SLO = target, SLA = promise.
Detailed Answer
They nest: you measure an SLI (success rate, latency), set an SLO above what you promise to keep margin, and the SLA is the commercial agreement with consequences. Example: SLI current availability 99.95%; SLO target 99.9%; SLA promises 99.5% or credits apply. The gap between the SLO and 100% is the error budget that governs release risk.
Interview Tip
Use the mnemonic SLI=measurement, SLO=target, SLA=promise, and note SLA is looser than SLO.
💬 Comments
Quick Answer
Error budget = 100% − SLO — the allowed unreliability. A 99.9% SLO allows ~0.1% (~43 min/month) of failure. If the budget is healthy, ship features faster; if it is exhausted, freeze risky changes and focus on stability.
Detailed Answer
The error budget turns reliability into a shared, objective decision tool that balances feature velocity against stability. Teams spend the budget on releases and risk; when it runs out, a change freeze (feature deployments stop, only stability work) protects the SLO. This aligns dev and ops around one number instead of arguing about "how much testing is enough".
Code Example
# Error Budget = 100% - SLO # SLO 99.9% -> budget 0.1% ~ 43.2 min / month
Interview Tip
Give the formula and the 43 min/month figure, and explain the freeze-when-exhausted policy — that operational use is the point.
💬 Comments
Quick Answer
Metrics (what is happening — trends/aggregates, e.g., Prometheus/Grafana), logs (what happened — discrete events, e.g., ELK/Loki), and traces (why it happened — a request across services, e.g., Jaeger). Together they let you investigate unknown failures.
Detailed Answer
Metrics quantify system state over time and drive alerts; logs give event-level detail for a component; distributed traces follow one request end to end to localize latency or errors across services. Correlating a trace ID into logs links them. Observability (explore unknown-unknowns) is broader than monitoring (watch known conditions).
Interview Tip
Map each pillar to its question: metrics=what, logs=what happened, traces=why — and name a tool for each.
💬 Comments
Quick Answer
Alert → Detect → Respond → Mitigate → Recover → RCA (root-cause analysis) → Postmortem. Mitigate first to stop customer impact, then find and fix the root cause, then a blameless postmortem with tracked actions to prevent recurrence.
Detailed Answer
Monitoring generates an alert; responders detect and acknowledge, then mitigate to restore service (rollback, failover, scale) before chasing root cause. After recovery, do RCA (e.g., 5 Whys) and a blameless postmortem documenting timeline, contributing factors, and owned action items. The goal is to reduce impact fast and prevent recurrence, not to assign blame.
Interview Tip
Stress mitigate-before-root-cause and the blameless postmortem with tracked actions — that ordering is what interviewers want.
💬 Comments
Quick Answer
A root-cause technique: ask "why" repeatedly (about five times) to move from symptom to underlying cause. Example: Website down → DB full → disk full → logs not deleted → no log rotation (root cause).
Detailed Answer
Each answer becomes the next question, drilling past symptoms to the systemic cause so the fix prevents recurrence rather than patching the surface. It works best for linear cause chains; complex incidents may need broader techniques (fishbone, contributing-factors analysis). Pair it with a blameless postmortem so people answer honestly.
Interview Tip
Give the classic chain (down → DB full → disk full → logs → no rotation) — a concrete example lands better than the definition.
💬 Comments
Quick Answer
A hard freeze blocks all changes and deployments for maximum stability (e.g., peak shopping events). A cold freeze is more restrictive-but-partial: only critical fixes and security patches are allowed. Both reduce change-induced risk during sensitive windows.
Detailed Answer
Change freezes cut the biggest source of incidents — change — during high-risk periods (holidays, launches, on-call gaps). A hard freeze = no changes, no deployments, maximum stability. A cold freeze = critical fixes and security patches only, with everything else held. Define the window, the exception process, and who approves emergency changes.
Interview Tip
Hard freeze = nothing ships; cold freeze = only critical/security fixes. Mention the exception/approval process for emergencies.
💬 Comments
Quick Answer
Toil is manual, repetitive, automatable operational work that scales with service size and adds no lasting value (restarts, log cleanup, backups, user creation). SREs cap and reduce toil by automating it, freeing time for engineering.
Detailed Answer
Google defines toil as work that is manual, repetitive, automatable, tactical, and O(n) with growth. Left unchecked it consumes the team and prevents reliability engineering. The response: measure toil, keep it under a budget (often ~50%), and automate the recurring tasks (scripts, operators, self-healing) so engineers work on durable improvements.
Interview Tip
Define toil precisely (manual+repetitive+automatable+scales) and mention capping it (~50%) and automating — not just "boring work".
💬 Comments
Context
A platform org with 30 microservices, alerting on raw infrastructure thresholds (CPU, memory, pod restarts), chronic page noise, and no shared language between product and engineering about acceptable reliability.
Problem
Every team defined 'reliable' differently; alert thresholds were folklore; and reliability-vs-velocity arguments had no data — product always won until an outage, then engineering always won until memories faded.
Solution
Ran an SLO program deliberately: (1) picked 5 pilot services with willing owners; (2) defined user-journey SLIs measured at the LB (availability, latency p95) with objectives negotiated from actual historical performance (start achievable, tighten later) rather than aspirational 99.99s; (3) replaced threshold alerts with multi-window multi-burn-rate paging on the SLOs; (4) instituted error-budget policies co-signed by product (budget exhausted = reliability work jumps the queue — agreed before any breach, not during); (5) templated everything (SLO definitions as code, dashboards, alert rules generated) and scaled to the remaining services in waves, with a monthly review pruning SLOs nobody consulted.
Commands
slo-gen apply slo/checkout.yaml # SLI, objective, windows -> rules + dashboards
alert: burn_rate(1h)>14.4 AND burn_rate(5m)>14.4 -> page
monthly: error-budget report per service -> product/eng review
Outcome
Pages per on-call shift dropped 60% (burn-rate paging replaced threshold noise) while two real incidents paged faster than the old rules would have; three roadmap disputes were settled by budget data instead of seniority; two services' objectives were deliberately loosened after data showed users didn't notice — freeing real engineering time.
Lessons Learned
Deriving objectives from historical data (then tightening) avoided the instant-failure demoralization of aspirational targets. The error-budget policy only worked because product co-signed it before the first breach — negotiating it during an incident would have failed.
💬 Comments
Context
An 8-person SRE team 'too busy to automate' — subjective consensus said operational load was crushing, but there was no data on where time actually went, so nothing changed.
Problem
Toil was invisible: interrupts weren't tracked, tickets weren't categorized, and the automation backlog was prioritized by annoyance rather than return. Two engineers were job-hunting citing burnout.
Solution
Instituted a toil ledger: every ticket, page, and interrupt tagged at close with category and minutes spent (one-click, enforced by tooling); quarterly time-sampling survey as cross-check. First month's data ranked toil by annual hours; the team negotiated a protected 30% automation budget with management using the ledger as evidence. Top items got standard treatment: automate (cert rotations, node cleanup, access requests), eliminate (two report categories nobody read — deleted), or delegate to self-service (quota bumps became a PR-approved config change product teams do themselves).
Commands
ticket close form: {toil_category, minutes} requiredquarterly: toil report — hours by category, trend, top-10 automation candidates
budget: 30% of team capacity ring-fenced for toil-reduction work
Outcome
Measured toil fell from ~58% to ~28% of team time in six months; off-hours pages halved (much toil was page-shaped); the freed capacity shipped a self-service provisioning portal that removed the next tier of toil — compounding. Both flight-risk engineers stayed.
Lessons Learned
Measurement preceded every win — the loudest-complained-about toil ranked 6th by actual hours; the top item (access requests) was so routine nobody had mentioned it. Ring-fencing the automation budget was essential: without protection, toil reduction loses to every urgent thing forever.
💬 Comments
Context
A team that had adopted SLOs on paper but still paged on old threshold alerts, because the first attempt at SLO alerting (page when budget < 50%) either fired far too late or flapped on noise — so the SLO layer decayed into an ignored dashboard.
Problem
Single-window budget alerts are structurally wrong: slow burns exhaust budget before a 'budget low' alert acts, and fast burns need paging within minutes — no single threshold serves both. Meanwhile the threshold alerts that did page had ~20% precision.
Solution
Implemented canonical multi-window multi-burn-rate alerting: page on fast burn (14.4x over 1h, confirmed by 5m window — catches outages in minutes while requiring sustained signal), ticket on slow burn (3x over 6h/30m, and 1x over 3d — catches degradations that silently eat the budget). Both windows required per alert to prevent flapping on blips. Validated by replaying six months of metric history against the new rules before cutover: every historical Sev-1/2 would have paged, and 80% of the old pages would not have fired. Ran the old and new alerting in parallel for a month, then retired the thresholds.
Commands
page: burn_rate(1h)>14.4 AND burn_rate(5m)>14.4
ticket: burn_rate(6h)>3 AND burn_rate(30m)>3; burn_rate(3d)>1 AND burn_rate(6h)>1
replay: python replay_alerts.py --history 180d --rules new.yaml --compare old.yaml
Outcome
Post-cutover: pages down 78%, zero missed incidents in the following two quarters, and one slow-burn ticket caught a creeping p95 regression that the old alerting would never have seen (it wasn't a threshold breach, just budget arithmetic). On-call started trusting — and therefore maintaining — the SLOs.
Lessons Learned
The historical replay was what earned the team's trust to cut over — 'here is every incident from the last 6 months under the new rules' ends the debate. The multi-window AND condition (long window for significance, short for freshness) is what kills flapping.
💬 Comments
Symptom
Users can't reach the product for 40 minutes (regional LB misconfiguration after a network change); support queues explode; Twitter notices. Meanwhile every SLO dashboard is green and no SLO alert fires — the availability SLI shows 100% for the whole outage window.
Error Message
No alert. The SLI query: sum(rate(http_requests_total{code=~'5..'}[5m])) / sum(rate(http_requests_total[5m])) — computed from app-server metrics that received no traffic at all during the outage.Root Cause
The availability SLI was measured from application-side request metrics: when the LB misroute stopped traffic from reaching the pods entirely, the error numerator AND request denominator both went to ~zero, so error ratio stayed 0% — mathematically 'perfectly available' while completely down. No traffic-volume sanity check existed, and the synthetic probes that would have caught it externally had been decommissioned as 'redundant with SLOs' a year earlier.
Diagnosis Steps
Solution
Detection first: restored outside-in synthetic probes from multiple regions as an independent paging signal, and added a traffic-anomaly guard (request rate dropping >80% below the time-of-week baseline pages regardless of error ratio). Structurally: moved the availability SLI's measurement point to the LB/edge, where 'requests that failed to reach the service' exist in the data.
Commands
promql: sum(rate(http_requests_total[5m])) # denominator collapse visible
lb logs: 100% NR (no route) responses in the window
blackbox_exporter: probe_success{job='synthetic-checkout'} # restoredPrevention
Availability SLIs must be measured where absence of traffic is visible — edge/LB, plus synthetics as the independent check. Every ratio SLI needs a denominator sanity guard (no data ≠ no errors). Treat 'monitoring redundancy' cuts with the same rigor as capacity cuts: the synthetic probes were the only layer that saw this class.
💬 Comments
Symptom
During a dependency brownout, users experience ~30% failure rate for an hour, but the SLO burn shows a modest blip well within budget. Postmortem arithmetic doesn't add up: complaints and refund volume imply far more failed user actions than the SLI recorded.
Error Message
No alerting anomaly. Investigation: request volume during the incident was 3.2x baseline — client SDKs retried failed calls up to 4 times, and each retry counted as a fresh request in the SLI denominator.
Root Cause
The SLI counted HTTP requests, not user attempts. Under failure, aggressive client retries multiplied the denominator: 1 user action = up to 5 requests, of which several succeeded on retry — so request-level success rate stayed high while user-level success rate cratered. The SLI structurally underestimates impact exactly when impact is worst (failure triggers retries), a self-flattering metric.
Diagnosis Steps
Solution
Redefined the SLI at the user-action level: client attaches an idempotency/attempt-group key; the SLI numerator/denominator aggregate by group (action succeeded if any attempt succeeded, counted once). Where client changes lagged, an interim server-side approximation deduplicated by (user, endpoint, 30s window). Recomputed the incident's true burn — it would have consumed 40% of the monthly budget and paged in 4 minutes.
Commands
promql: rate(http_requests_total[5m]) vs rate(unique_action_groups[5m])
trace query: group spans by idempotency_key, count groups with zero successes
replay: recompute burn with deduplicated SLI over the incident window
Prevention
Define SLIs in units of user experience, not implementation artifacts — 'fraction of user actions that succeed', with retries collapsed. Load-test SLI behavior under failure modes (retry storms, timeouts) the way you load-test the service: a good SLI must get worse when users suffer, mechanically.
💬 Comments
Symptom
The same class of incident — config change without canary hitting a shared dependency — recurs three times in five months. Each prior postmortem produced the same action items ('add canary stage', 'add dependency pre-check'). All three sets sit unstarted in three different teams' backlogs.
Error Message
No system error. The organizational signal: postmortem action-item completion rate, once someone finally measured it, was 31% overall and near-zero for items assigned across team boundaries.
Root Cause
Postmortems ended at document publication: action items went to backlogs with no SLA, no owner-of-record beyond 'team X', no severity weighting, and no review loop. Cross-team items died in triage because no one owned the seam. The org had a learning ritual but no learning loop — blameless culture without follow-through converts incidents into documentation instead of prevention.
Diagnosis Steps
Solution
Instituted action-item governance: every item gets a named individual owner, a severity-derived SLA (Sev-1 preventions: 30 days), and tracking in the same system as feature work (visible in sprint planning, not a side spreadsheet). A monthly reliability review walks every overdue item with directors present; recurrence of an incident class with open prior actions escalates automatically. Completion rate and 'repeat incidents with open actions' became reported org metrics.
Commands
jql: label=postmortem-action AND status!=Done ORDER BY created # the rot, quantified
report: completion_rate by team, by severity, by cross_team flag
monthly review agenda: every overdue Sev-1/2 action, owner present
Prevention
Treat prevention work as production work: named owners, SLAs, sprint visibility, and executive review of the overdue list. Track recurrence-with-open-actions as the shame metric — it converts diffuse 'we should learn' into a specific accountability. Cap open action items per team to force prioritization honesty (close-as-won't-fix is allowed but must be explicit).
💬 Comments