Engineering for Reliability · SLO Windows & Composite SLOs

SLO Windows & Composite SLOs

SLIs, SLOs & error budgets gave you the formula: error budget equals 100% minus your SLO, times the window. That page — and the SREF exam's own Module 2 — mostly leaves "the window" as a number you plug in. This page goes past that and asks two questions the formula quietly assumes away. First: what actually is a window — does it slide forward continuously, or reset on a fixed calendar boundary, and does that choice change anything besides bookkeeping? Second, and more consequential: what happens to an SLO once the request it describes doesn't stay inside one service — once getting the user an answer requires several independently-reliable services to each do their job? We'll work both all the way through with real numbers, including a full worked example of three chained 99.9% SLOs that don't add up to 99.9% end-to-end, and why.

☺ Explain it like I'm 10

Imagine your allowance resets two different ways depending on the house. In one house, your allowance is always "whatever you've spent in the last 4 weeks" — spend it on a Tuesday, and by the following Tuesday that spending has quietly aged out and your balance looks better again, a little each day. In the other house, your allowance is "this calendar month" — blow it all by the 28th and you're broke until the 1st, then instantly rich again at midnight, whether or not you actually changed how you spend. Neither is wrong, but they train completely different habits. Now imagine three friends each have to relay a message correctly for you to get paid at all — each one gets it right 999 times out of 1,000, which sounds great — but you only get paid when all three get it right on the same day. Even though every friend is individually excellent, the group's "all three, together" rate is measurably worse than any one friend's rate alone. That gap is exactly what a composite SLO measures.

🦥🐢Your hosts for this topic: Sol the Sloth & Timmy the Turtle — Sol works out exactly how a window's shape changes the arithmetic; Timmy is the one who won't let a clean multiplication go unquestioned until someone's checked whether the independence it assumes actually holds.

The window is part of the SLO, not a footnote

☺ Like you're 10: "99.9% reliable" isn't a complete sentence on its own — you still have to ask 99.9%, measured over what stretch of time, and reset how, before the number tells you anything useful.

The error-budget formula — (100% − SLO) × window — treats "window" as a single number: 30 days, 43,200 minutes, done. That's enough to compute a budget, but it hides a real design decision. As time passes, what does the window actually do? Two fundamentally different mechanics exist in production SLO tooling, and they produce genuinely different lived experiences of the same percentage target: a rolling window, which slides forward continuously and never resets to zero, and a calendar-aligned window, which snaps back to full at a fixed boundary regardless of what just happened. Same SLO, same window length even — 99.9% over 30 days, say — and the two mechanics still hand your team a different incentive structure, a different gaming risk, and a different answer to "how long until this incident stops counting against us."

Rolling windows — the trailing view that never resets to zero

☺ Like you're 10: Think "the last 28 days, always" — a bad day slides out the back of the window on its own as a new day slides in the front, with nobody flipping a switch.

A rolling window covers, at any instant, exactly the trailing N days (or hours) up to right now. Tomorrow, the window is a different 28-day slice — today's data joins it, and the day 28 days ago drops out. There's no reset event a human would ever point to; the budget recovers a little, continuously, every single day, as old bad minutes age past the trailing edge and stop counting.

Most teams that measure internally use 28 days specifically, not the more calendar-friendly 30 — and the reason is subtler than "it's a round number." Twenty-eight days is exactly four full weeks, so every rolling 28-day window contains precisely four Mondays, four Saturdays, four of every weekday, no matter which day you happen to compute it on. A 30-day window doesn't have that property — it drifts across different weekday compositions depending on where the month falls, sometimes catching five Mondays and four Sundays, sometimes the reverse. For a service with genuine day-of-week seasonality — B2B traffic that goes quiet on weekends, a batch pipeline that only runs on weekdays — that drift leaks into the SLI itself: whether the service looks reliable can depend partly on which weekdays happened to fall inside the window, not just on how the service actually performed. Twenty-eight days removes that confound entirely.

◆ Key idea

A rolling window's defining property isn't its length — it's that it has no reset moment at all. An incident's contribution to the failure count shrinks a little every day until, N days later, it's gone, with no single point in time where anyone can say "the budget just came back." That's what makes rolling windows resistant to the specific gaming pattern the next section describes.

Calendar-aligned windows — the reset cliff

☺ Like you're 10: Think "this calendar month" — spend it all by the 28th and you're frozen until the 1st, then the balance snaps back to full at midnight, whether or not the actual problem got fixed.

A calendar-aligned window covers a fixed period with hard boundaries — a calendar day, week, month, or quarter — and resets to a fully replenished budget the instant the boundary passes, independent of the trailing history. This produces a real distortion a rolling window doesn't have: the same incident produces wildly different apparent severity purely based on where it lands in the calendar. An outage on day 2 of a calendar-month window gets diluted across 28 more days of clean behavior before the window closes — plenty of room to recover. The identical outage on day 29 has almost no runway left to dilute into; it can single-handedly blow the whole month's budget with the window closing the next day regardless.

Worse, the reset date is known in advance, which creates a genuinely perverse incentive: a team that's already blown its calendar-month budget on the 27th has very little reason to urgently fix the underlying cause before the 1st, because the meter is about to snap back to 100% on its own, on schedule, whether or not anything was actually repaired. A rolling window never offers that shortcut — the only way budget recovers is for enough clean days to accumulate, which is exactly the incentive you want.

⚠ Watch out

Never let a calendar-window reset stand in for confirmation that a problem was fixed. A budget reading 100% on the 1st says nothing about whether the root cause from the 28th's incident was ever addressed — it's a bookkeeping event, not a health signal. Check the postmortem's tracked action items (see postmortems & blameless culture), not the meter, before you conclude anything actually got better.

None of this means calendar windows are a mistake — they exist for a real reason. External commitments are usually already calendar-shaped: a customer's invoice lands monthly, a quarterly business review covers a quarter, and an SLA promising "99.5% per calendar month, service credit on a miss" maps directly onto that billing cycle in a way a rolling window can't. This is the same SLA-looser-than-SLO gap already established in SLIs, SLOs & error budgets — it's common, and reasonable, for the internal SLO driving day-to-day engineering behavior to run on a rolling window while the external SLA reported to the business runs on a calendar one.

Rolling 28-day window — no reset moment Day 10 — incident 2 days old Day 20 — drifting toward the edge Day 38 — aged out, budget recovered time flows right — the window itself is always "today minus 28 days" Calendar-month window — the reset cliff Day 29 of the month budget: 0% left, frozen snap Day 1 — new month budget: 100% (instant reset) resets at the boundary — whether or not the root cause was actually fixed

Choosing a window type — and not confusing it with burn-rate windows

☺ Like you're 10: Pick rolling for the number that governs how your own team behaves day to day; keep calendar-aligned for whatever you've promised in writing to somebody outside the team.

Rolling windowCalendar-aligned window
Reset behaviorNever — recovers gradually, one interval at a timeHard reset to 100% at each fixed boundary
Gaming riskLow — there's no known reset date to "wait out"Real — late-period incidents can be run out the clock
Reporting alignmentAwkward for monthly/quarterly business reportingMatches billing cycles, QBRs, exec dashboards
Compute/storage costHigher — a continuously recomputed sliding aggregateLower — a single counter that resets on schedule
Typical useInternal error-budget policy driving release decisionsExternal SLAs and periodic business reporting

Most SLO-as-code tooling exposes the choice explicitly rather than picking one for you. Google Cloud's Service Monitoring API, for example, models an SLO's period as a mutually exclusive choice between the two mechanics — you set one field or the other, never both:

# Illustrative — field names follow Google Cloud Monitoring's SLO API shape;
# verify exact syntax against current provider docs before wiring this into a real service.
serviceLevelObjective:
  displayName: "checkout-api-availability"
  goal: 0.999                 # the SLO target itself
  rollingPeriod: "2419200s"    # 28 days, as a duration — mutually exclusive with the next line
  # calendarPeriod: MONTH      # DAY | WEEK | FORTNIGHT | MONTH | QUARTER | HALF | YEAR

In practice, nobody queries a raw 28-day range vector straight off a Prometheus server — retention limits and query cost make that impractical at scale. Teams pre-aggregate with recording rules that maintain a running sum instead, which is exactly the scaffolding a tool like Sloth generates from a short declarative SLO spec, or that a hosted platform like Nobl9 computes for you. A common, pragmatic pattern splits the difference rather than picking one window type: track the operational SLO on a rolling window for the day-to-day error-budget policy that actually gates releases, and separately roll the same underlying SLI up into a calendar-aligned figure purely for the SLA and the exec dashboard — two views of one measurement, each serving the audience that needs it.

⚠ Watch out

Don't confuse this page's "window" with the windows in multi-window, multi-burn-rate alerting. That technique layers several short lookback windows — say, 1 hour and 6 hours — on top of whichever window type (rolling or calendar) you picked for the SLO itself, purely to detect a fast budget burn early. The SLO's own measurement window and an alert's short diagnostic lookback windows are two different mechanisms that happen to share the word "window" — don't let a stem or a design doc conflate them.

Composite SLOs — when a user's request depends on more than one service

☺ Like you're 10: If getting the user an answer takes three different services each saying yes, the user only gets a yes when all three do — and that's a stricter bar than any one of the three clears on its own.

Everything above still assumes a single service being measured against a single SLO. Most real user journeys don't stay inside one service. A checkout click might route through an API gateway, then an authentication service, then a payments service before the user ever sees "order confirmed" — and each of those services can be independently hitting its own respectable SLO while the user's actual end-to-end experience is worse than any one of them reports. Google's SRE Workbook has a name for the thing you should actually be measuring in this situation: a critical user journey (CUJ) — an SLO scoped to what the user is trying to accomplish, not to any single backing service's own dashboard.

The reason the journey's guarantee is weaker is structural, not a measurement error. If completing the journey requires every service on its critical path to succeed, that's a logical AND — and it doesn't matter whether those calls happen strictly in sequence (auth must finish before payments starts) or fan out concurrently and get joined before a response goes back to the user. What matters is whether success requires all of them, not the wall-clock order they happen in. For N services on the critical path, each succeeding independently with its own probability, the probability the whole journey succeeds is the product of all of them: P(journey succeeds) = p₁ × p₂ × ... × pₙ.

The worked example: three chained 99.9% SLOs

☺ Like you're 10: Multiply 999 out of 1,000, three times in a row, and the answer comes out measurably below 999 out of 1,000 — not because any one link got worse, but because you needed all three at once.

Take the checkout journey above literally: an API gateway with a 99.9% SLO, an auth service with a 99.9% SLO, and a payments service with a 99.9% SLO. Every one of the three teams is comfortably meeting their own number — nobody's dashboard is red. The end-to-end journey's actual availability is the product of all three:

0.999 × 0.999 × 0.999 = 0.997002999  ≈  99.70%

Converted to a 30-day error budget (43,200 minutes):
  single 99.9% hop:     (100% − 99.9%)   × 43,200 =  43.2  min
  composite (3 hops):   (100% − 99.70%)  × 43,200 ≈ 129.47 min

129.47 / 43.2 ≈ 2.997 — just under 3× the single-hop budget.

The composite journey can appear "down" to the user for almost three times as many minutes across the month as any single service's own dashboard would suggest — even though every team involved is individually hitting three nines. This isn't a rounding artifact; it's what an AND across independent components does to a probability, and it gets worse the longer the chain gets. The general form, for N equally-reliable services each with per-hop reliability r on the critical path, is composite = rN.

Run the formula the other direction and you get the number that actually matters when you're designing a chain: if you want the end-to-end journey to hit 99.9%, what does each individual hop need to hit, given how many hops are on the path? Solve rN = target for r:

Hops on the critical path (N)Per-hop SLO needed for a 99.9% end-to-end target
199.9000%
299.9500%
399.9667%
499.9750%
599.9800%

The pattern is the whole lesson in one table: "everyone independently hits three nines" is exactly enough for a one-hop journey and progressively insufficient as more services join the critical path. A five-hop journey needs every single hop north of 99.98% — noticeably closer to four nines than three — just to deliver the same 99.9% the user actually experiences.

DEPENDENCY CHAIN — AND API Gateway 99.9% AND Auth Service 99.9% AND Payments Service 99.9% Composite ≈ 99.70% 0.999 × 0.999 × 0.999 = 0.997003 REDUNDANT REPLICAS — OR Replica A — 99.9% Replica B — 99.9% OR either is enough Composite ≈ 99.9999% six nines (1 − 0.001²)

Why the naive multiplication isn't even the worst case

☺ Like you're 10: The multiplication only works cleanly if the three friends fail on totally unrelated days — if they all catch the same cold at once, the real number is worse than the neat math predicts.

The rN formula above has a load-bearing assumption baked in: each hop fails independently of the others. Real production systems routinely violate that assumption, and when they do, the violation only makes things worse. Picture the same three services — gateway, auth, payments — built from the same base container image, running in the same Kubernetes cluster, in the same availability zone, behind the same load balancer, authenticating through the same shared library. A bad shared-dependency push, a single AZ network partition, or one expired shared TLS certificate can take out all three hops at the exact same moment — a failure mode the independence assumption has no way to represent, because it treats each hop's dice roll as unrelated to the others'. When failures are positively correlated like this, the real joint-failure rate is higher than 0.2997% predicts. Treat 99.70% as an optimistic ceiling for a real chain that shares infrastructure, not a guarantee.

The same math cuts the other way for redundancy, which is worth holding in your head as the direct counterpoint to the chain above. Not every multi-service relationship is an AND — a pair of replicas behind a load balancer, where either one succeeding is enough, combines via the opposite operation, the union: P(at least one succeeds) = 1 − ∏(1 − pᵢ). Two independent 99.9% replicas produce a combined unavailability of 0.001 × 0.001 = 0.000001 — 99.9999% composite availability, six nines, dramatically better than either replica alone. But the correlation caveat applies with exactly the same force here: if "two independent replicas" actually share a rack, a region, or a deploy pipeline, the real joint failure rate collapses back toward the single-replica number, not the optimistic union prediction. Independence has to be engineered — genuinely separate fault domains — not assumed by calling something "a second replica." Multi-region & multi-AZ architecture covers what building real fault-domain separation actually takes.

⚠ Watch out

Independence is a modeling assumption, not a fact about your architecture. Before trusting either the multiplied-down AND number or the multiplied-up OR number, ask what infrastructure, deploy pipeline, or on-call human the "independent" components actually share. That shared thing — not either idealized formula — is where the real joint-failure probability actually lives.

Designing and governing around the compounding effect

☺ Like you're 10: Once you know the multiplication is coming, you can fight back — give the busiest link a tighter target, add a backup path, or take a link out of the chain entirely.

Knowing the compounding effect exists is the easy part; a few concrete levers actually do something about it. First, allocate budget unevenly rather than handing every service on every critical path the same flat target. A service that sits one hop deep in twenty different composite journeys needs a materially tighter internal SLO than a leaf service that only ever appears in one journey, because its failures multiply into twenty places at once instead of one — the same reasoning behind treating high-fan-out "hub" services as a special case when planning capacity (see capacity planning & performance).

Second, and more powerful: some mitigations don't tighten a hop's number, they remove the hop from the AND chain entirely. A cache serving a recent-good response turns a downstream timeout into slightly-stale data instead of a hard failure. A circuit breaker with a defined fallback (see reliability patterns, Timmy's page) turns "auth service must succeed for this request to succeed" into "auth service should succeed, and here's what happens if it doesn't." Moving a genuinely optional side effect — an analytics write, a non-blocking notification — off the synchronous critical path entirely, onto an async queue, means it literally cannot multiply into the user-facing SLO at all, because it was never part of the AND to begin with. Where a dependency truly can't be made optional, the redundancy math from the previous section still applies — engineered with real fault-domain separation, not assumed.

Third: someone has to own the composite number itself. No individual service team is naturally accountable for an AND that spans team boundaries — each team can legitimately point at their own green dashboard and be telling the truth. That's exactly why a platform or SRE org typically owns the critical-user-journey SLO as its own first-class metric, monitored and alerted on separately from any single service's SLO, not inferred from the fact that all the components happen to be green. "Every service dashboard is green, and the composite journey SLO is red" isn't a contradiction — it's precisely the signal this page's math predicts, and it's the reason the composite number needs its own owner and its own alert.

🎬 At the Reliability Watch
🦊

Foxy: Every one of our three checkout services is sitting right at 99.9%. Dashboards are all green. So why is Ellie seeing incidents on the checkout journey that never triggered any single service's page?

🦥

Sol the Sloth: ...Because green times green times green isn't green. Point nine nine nine, three times... comes out to about point nine nine seven. I worked it earlier — it's roughly three times the single-service budget.

🐢

Timmy the Turtle: And that's the generous number. Are those three services actually independent — different clusters, different AZs — or do they share a load balancer?

🦊

Foxy: ...same load balancer. Same deploy pipeline too, now that you ask.

🐢

Timmy the Turtle: Then point nine nine seven is the best case, not the real one. I'm not calling this journey healthy until somebody owns the composite number directly — not three service dashboards that all happen to be green today.

🦉

Professor Owl: That's the whole lesson in one sentence: a chain of green lights isn't the same claim as a green chain.

✓ Checkpoint

1. What's the structural difference between a rolling window and a calendar-aligned window, and why does that difference create a "reset cliff" gaming risk for the calendar kind but not the rolling kind? 2. Why do many teams prefer a 28-day rolling window over a 30-day one for internal SLOs? 3. Three independent services, each with a 99.9% SLO, sit on one journey's critical path. What's the composite availability, and roughly how many minutes of error budget does that composite figure allow over a 30-day (43,200-minute) window? 4. Why does the naive multiplication formula tend to understate real-world risk for a chained (AND) dependency, and what's the one architectural move that removes a hop from the chain entirely rather than just tightening its SLO?

Check your answers
  1. A rolling window covers the trailing N days as of right now and never resets — an incident's contribution ages out gradually as the window slides forward. A calendar-aligned window resets to a full budget at a fixed boundary regardless of trailing history, so an incident late in the period has almost no time left to dilute into, and the team knows exactly when the meter snaps back to 100% — creating an incentive to run out the clock rather than fix the root cause before the boundary.
  2. Twenty-eight days is exactly four full weeks, so a rolling 28-day window always contains exactly four of each weekday no matter when it's computed. A 30-day window drifts across different weekday compositions month to month, which can leak a service's own day-of-week traffic seasonality into whether it looks reliable — 28 days removes that confound.
  3. 0.999 × 0.999 × 0.999 ≈ 0.997003, or about 99.70% composite availability. Its error budget over a 43,200-minute window is (100% − 99.70%) × 43,200 ≈ 129.47 minutes — just under 3× the 43.2-minute budget any single one of the three services would show on its own dashboard.
  4. The multiplication assumes each hop fails independently, but chained services frequently share infrastructure — the same cluster, AZ, load balancer, or deploy pipeline — so a single shared-cause failure can take out several hops at once, a mode the independence assumption can't represent; the real joint-failure rate is typically higher than the formula predicts. The architectural move that removes a hop entirely (rather than tightening its number) is taking it off the synchronous critical path — for example, via a cache/fallback, a circuit breaker with a defined default, or moving a non-essential side effect onto an async queue — so its own failure can no longer participate in the AND at all.