Knowledge Guide
HomeSystem DesignObservability & SRE

Observability & SRE II — SLO Composition, Pipeline Failure Under Load, Latency Burn Rate & SRE Practice (Deep Dive)

An SLO is not a property of one service in isolation — it is a property of a request path, and the moment that path crosses a second service the arithmetic changes from addition to multiplication: every hard, synchronous dependency an incoming request must succeed through multiplies the whole chain’s availability down, never up, so a service built entirely from “three nines” dependencies cannot itself deliver three nines no matter how bug-free its own code is.

Why availability multiplies down a synchronous critical path

If a request must succeed through N independent hard dependencies in series — meaning the request fails if any one of them fails — the end-to-end success probability is the product of each link’s own availability, because success requires every link to succeed (a logical AND across independent failure domains). For 5 services each independently available 99.9% of the time:

Hops composedExpressionRunning availability
10.999¹99.900%
20.999²99.8001%
30.999³99.7003%
40.999⁴99.6006%
50.999⁵99.5010%

0.999⁵ ≈ 0.995010, i.e. ≈ 99.5%. A team that sets its own SLO to 99.9% while sitting on top of five 99.9% dependencies has already lost 0.4 percentage points before writing a single line of code — the target was mathematically unreachable at design time, not missed through execution failure.

Whose budget burns? Cross-service error-budget attribution

Multiplication also answers a question every senior candidate gets asked: when a shared dependency degrades, which team’s error budget should the incident debit? Naively, the calling service’s SLI just fails and its own budget burns — but that silently blames the caller for a bug in someone else’s system.

Trace it with real numbers. Frontend F has a 99.9% SLO over a 30-day period (0.1% budget). One of its five hard dependencies, Auth Svc, has a bad deploy that drops its own availability from its normal 99.9% down to 99.0% for a full 24-hour day, then recovers.

QuantityValue
Excess unavailability at Auth Svc during the bad day(1 − 0.990) − (1 − 0.999) = 1.0% − 0.1% = 0.9 percentage points
Fraction of F’s 30-day traffic that fell inside the bad day1 day / 30 days = 1/30
Excess fraction of F’s monthly requests that failed solely because of Auth Svc(1/30) × 0.9% = 0.03%
Share of F’s entire 30-day, 0.1% error budget consumed by that one dependency-day0.03% / 0.1% = 30%

One 24-hour bad day at one dependency ate roughly a third of F’s entire monthly error budget — and if Auth Svc has four other consumers besides F, all five of them independently see the same 30% hit to their own budgets, from a single incident in a single team’s deploy.

Design implications

When the observability pipeline fails under the very incident it exists to diagnose

The pillars have a failure mode the emit-side cost model hides: telemetry volume does not stay constant during an incident — it spikes, exactly when you most need it, because clients retry failed calls (multiplying request count), error paths log more verbosely (stack traces, warn-level noise), and slow requests keep their trace spans “open” for longer. That last effect is the sharpest: tail-sampling holds every span of a trace in a buffer until the whole trace completes and a keep/drop decision is made, so the number of traces sitting in that decision-window buffer at any instant is roughly (arrival rate) × (average trace duration). When a latency incident stretches average trace duration, the buffer’s occupancy grows in lockstep with the incident itself — a self-correlated back-pressure that can exhaust collector memory at the exact moment engineers are staring at dashboards trying to find a slow trace.

Back-pressure vs. load-shed at agents and collectors

When an agent’s bounded emission queue fills — because the collector has slowed down or the emission rate has spiked — there are exactly two responses, and they trade off against each other in opposite directions:

Production systems overwhelmingly choose load-shedding on the hot path, refined with priority sampling: rather than dropping data at random, shed healthy/fast traces first and always keep error traces and traces over a slow-latency threshold — precisely the traces useful for the incident. This is why tail sampling (a decision made only after the whole trace completes) can prioritize this way, and precisely why its buffer must hold every in-flight trace until that decision is made — the flexibility to keep “the interesting ones” is bought with the correlated-buffer-growth risk described above. Bound it: a hard per-collector trace cap, a short decision window (seconds, not tens of seconds), and shed-oldest-healthy-first so a crash loses only healthy, uninteresting traces, not the ones you needed. Metrics degrade differently again — a scrape that times out leaves a gap, not a wrong value, which is itself alertable (up == 0); that asymmetry is one reason metrics remain the primary alerting substrate even though logs and traces carry richer detail.

Because collector instances typically own a shard of trace IDs (consistent-hashed so all spans of one trace land on the same collector for the sampling decision), a collector crashing under this exact back-pressure loses every in-flight trace on its shard, with no replay — tail sampling is at-most-once trace capture, not a durable system of record. Treat it that way: keep the decision window short, do not try to replicate the buffer for durability, and lean on head-sampled coverage plus exemplar-pinned trace IDs (linking a slow metric bucket to at least one trace ID) as your fallback path to a trace when the tail sampler drops exactly the one you wanted.

Design implication: capacity headroom is a first-class SLO. Provision agent and collector capacity for worst-case incident telemetry volume — retries, verbose error logging, and elongated trace buffers — not average-case traffic, and monitor the observability pipeline itself (collector queue depth, drop-rate, WAL/replay lag) with its own alerts. If the pipe falls over during the incident it exists to explain, you are debugging blind at the exact moment stakes are highest.

Burn rate generalizes: it is not only about errors

Every burn-rate derivation on this topic so far has framed the “bad event” as an error. The formula generalizes to any SLI with a threshold: the bad event is simply “did this request violate the SLI’s target,” and:

burn_rate = (fraction of requests violating the SLI) / (1 - SLO_target)

For a latency SLO of “95% of requests complete in under 300 ms” over a 30-day period, the bad event is “request took ≥ 300 ms,” the target is 95%, and the allowed budget is (1 − 0.95) = 5% of requests. Baseline “slow” fraction is normally ≈1% (burn rate 1/5 = 0.2, comfortably sustainable). At 10:00 a downstream timeout cascade pushes the slow-fraction to a sustained 80%, continuously, until a rollback lands at 11:00 (a full 60-minute incident, traced end to end exactly as the burn-rate-alerting page traces its error-rate example):

burn_rate = 80% / 5% = 16. Reusing the identical fast-burn tier from multi-window multi-burn-rate alerting (1h / 5m windows, multiplier 14.4), the corresponding threshold for this SLI is 14.4 × 5% = 72% of requests slow, not 1.44% of requests erroring — same machinery, different bad-event definition and a rescaled threshold because (1 − SLO) differs.

Time5m window (short)1h window (long)AND → PAGE?
10:00rising≈1% (baseline)no
10:0580% ≥ 72% ✓(55×1+5×80)/60 ≈ 7.58% ✗no (long not yet significant)
10:5480% ✓(6×1+54×80)/60 ≈ 72.1% ≥ 72% ✓PAGE
11:00still 80% (rollback lands right at 11:00)window 10:00–11:00, fully elevated → 80% ✓page holds
11:05rollback took effect → ≈1% ✗window 10:05–11:05, 55 of 60 min elevated → (55×80+5×1)/60 ≈ 73.4% ✓ (still high)RESET — short dropped, AND goes false even though the long window is still mathematically hot
12:00≈1%window 11:00–12:00, entirely post-rollback → ≈1% ✗fully clear

Detection time follows the same law as the error-rate case: t_detect ≈ (threshold_fraction × long_window) / actual_fraction = (72%×60)/80% = 54 min — which is exactly when the trace above pages. And the full 60-minute incident spends burn_rate × (window/period) = 16 × (60/43200) ≈ 2.22% of the entire 30-day latency budget. Nothing here is a new mechanism — it is the same MWMBR machinery from the burn-rate-alerting page, re-fed a latency-threshold SLI instead of an error-rate SLI, which is exactly the generalization a senior interviewer expects you to state unprompted.

SRE practice beyond the SLO math

Error budgets and burn rate are the quantitative core of SRE, but the operating model around them is what actually keeps a team sustainable and the postmortem data trustworthy.

Cardinality-bomb detection and triage playbook

Prevention (bounding label cardinality at the client) is necessary but not sufficient — you also need to localize a cardinality explosion while it is happening, to a specific metric name and the specific offending label, fast enough to stop an OOM before it takes the whole time-series database down.

  1. Alert on head-series growth rate, not just an absolute ceiling. Prometheus exposes total active series as the gauge prometheus_tsdb_head_series. An absolute-ceiling alert (“page if > 5M series”) misses a bomb that is climbing fast but hasn’t yet crossed the line, and false-positives on a service that is legitimately, stably large. Instead alert on relative growth over a short window, e.g. a rule shaped like (prometheus_tsdb_head_series - prometheus_tsdb_head_series offset 1h) / prometheus_tsdb_head_series offset 1h > 0.2 — a step-change after a deploy is exactly the signature of a new unbounded label going live, while a slow, steady rise from organic service growth stays under the rate threshold.
  2. Find the fattest metric with topk grouped by name. topk(10, count by (__name__) ({__name__=~".+"})) counts active series per metric name across the whole namespace and returns the 10 largest. In a cardinality incident, one metric will dominate the list by one or two orders of magnitude — that is your suspect.
  3. Find the offending label by counting its distinct values. Once you have a suspect metric, for each of its label names run count(count by (<label>) (<metric_name>)): the inner query groups the metric’s series by that label’s value (one group per distinct value), and the outer count(…) counts how many distinct values exist. A normally low-cardinality label (environment, region, status class) should return single or double digits; a label secretly carrying a user ID, request ID, or raw URL path will return a number in the thousands-to-millions — that is the bomb.
  4. Cross-check with Prometheus’s built-in endpoint. /api/v1/status/tsdb exposes “top series count by metric name” and “top label value count by label name” directly from the TSDB’s internal stats, with zero PromQL evaluation cost — the fastest first move during an active incident when running expensive ad-hoc queries against an already-struggling TSDB is itself risky.
  5. Mitigate at the source. Once the metric and label are identified: drop or relabel the label at scrape time (metric_relabel_configs with an action: labeldrop/replace), or fix the client library that is emitting it (the label almost always traces back to a recent deploy that started stamping a per-entity identifier onto a metric that was previously low-cardinality).

Pitfalls

Selection and trade-offs

Back-pressure vs. load-shed on the telemetry hot path. Choose back-pressure only for telemetry you have already decided is more important than the request it is attached to — audit logs required for compliance, for example, where losing the record is a worse outcome than added latency. For ordinary observability data (metrics, traces, application logs) on a user-facing request path, load-shedding with a drop counter and priority sampling is almost always the right default: the alternative, back-pressure, converts an observability problem into an availability problem, which is the one failure mode a telemetry system must never cause. The exception that flips this: an internal batch/offline pipeline with no live user waiting, where blocking a producer briefly is genuinely free.

Reducing sync depth vs. accepting the composed ceiling. Cutting a hard dependency (cache-and-fallback, making a call optional, negotiating a tighter upstream SLO) is real engineering work with its own cost — cache staleness, added fallback-path complexity, or a harder negotiation with another team. It is worth paying when the composed ceiling sits below a target the business has already committed to externally (a contractual SLA, for instance) — there the math is not optional. It is not worth the churn when the composed ceiling already comfortably clears the target the business actually needs; matching architecture cost to the nines a user will notice, not the theoretical maximum, is the same judgment call that governs every reliability investment on this topic.

Takeaways

  1. Availability multiplies, never averages, down a synchronous critical path: 0.999⁵ ≈ 99.5% — a downstream SLO target can be mathematically unreachable before any code runs, and only removing a dependency from the mandatory set (not parallelizing it) changes the ceiling.
  2. A shared dependency’s bad day should debit its own error budget via a per-dependency SLI, not silently freeze every consumer’s release — in the worked example, one 24-hour incident at one dependency consumed 30% of a single downstream team’s entire monthly budget.
  3. The observability pipeline is a system with its own failure modes that correlate with the incidents it diagnoses; the governing rule is fail open for the app, fail loud for the operator — and burn rate itself generalizes past error rate to any thresholded SLI via the same bad_fraction / (1−SLO) formula.
  4. SRE practice outside the SLO math — blameless postmortems, measured toil budgets, on-call load tracking, and a named error-budget escalation path — is what keeps the quantitative machinery trustworthy and the team sustainable enough to keep using it.

Recall question

A frontend with a 99.9% SLO calls five hard dependencies in series, each independently at 99.9%. One of them has a bad day and drops to 99.0% availability for 24 hours out of a 30-day period. Whose error budget should that incident debit, and why? (Answer: the failing dependency’s own error budget, via a per-dependency SLI tracked separately from the frontend’s end-to-end SLI — the frontend’s user-facing SLI still legitimately fails, since users experienced the outage, but the policy consequence, such as a release freeze, should route to the team that can actually fix the cause; otherwise every consumer of a shared dependency is punished simultaneously for a bug none of them own.)

Related pages


Sources: Google SRE Book, ch. 4 “Service Level Objectives,” ch. 5 “Eliminating Toil,” and ch. 15 “Postmortem Culture”; Google SRE Workbook, ch. 2 “Implementing SLOs” and ch. 5 “Alerting on SLOs”; OpenTelemetry Collector documentation (tail sampling processor, queued-retry back-pressure); Prometheus documentation (TSDB head-series metrics, /api/v1/status/tsdb). Re-authored/Deepened for this guide.

🤖 Don't fully get this? Learn it with Claude

Stuck on Observability & SRE II — SLO Composition, Pipeline Failure Under Load, Latency Burn Rate & SRE Practice (Deep Dive)? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Observability & SRE II — SLO Composition, Pipeline Failure Under Load, Latency Burn Rate & SRE Practice (Deep Dive)** (System Design) and want to truly understand it. Explain Observability & SRE II — SLO Composition, Pipeline Failure Under Load, Latency Burn Rate & SRE Practice (Deep Dive) from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Observability & SRE II — SLO Composition, Pipeline Failure Under Load, Latency Burn Rate & SRE Practice (Deep Dive)** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Observability & SRE II — SLO Composition, Pipeline Failure Under Load, Latency Burn Rate & SRE Practice (Deep Dive)** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Observability & SRE II — SLO Composition, Pipeline Failure Under Load, Latency Burn Rate & SRE Practice (Deep Dive)** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes