Topic 71

SLOs for One Service

Reliability

"Checkout should be fast" is a wish. "99.9 percent of POST /orders requests complete under 800 milliseconds, measured over 30 days" is a service-level objective, and the difference is everything that follows from it: it defines the alert, it sets the error budget that decides whether this week is for features or for the pool, and it is stated on a number the histogram of Topic 69 can answer exactly. Stagedoor has three SLOs, one per path a buyer or an organizer would notice failing, and each one was agreed with the person who owns that path before it was written into an alert rule.

This topic is how the three were chosen, how each is measured, and what the team does with them: the indicator first, then the objective, then the budget the objective implies, then the alert that fires on the rate the budget is being spent rather than on any single bad minute. It closes with the discipline that keeps the list at three, because a service with thirty SLOs has none.

The SLI First

The service-level indicator is a ratio the service can measure: good requests over total requests, where good has a definition that a buyer would agree with. For checkout, good is a status below 500 and a duration under 800 milliseconds; a 402 for a declined card is good, because the service answered correctly and quickly, and a 201 that took 3 seconds is not. Both halves come from the two instruments of Topic 69, the counter for the status and the histogram for the duration, and 800 milliseconds is a bucket boundary precisely so that this ratio is a count and not an estimate. The demonstrated query is the ratio as the collector computes it.

The checkout SLI: good requests over all requests, from the histogram and the counter
# good: finished under 800 ms and was not a 5xx; the 0.8 bucket counts every request under 800 ms,
# including the rare 5xx, so the 5xx count is subtracted, which errs in the strict direction
(
  sum(rate(http_request_duration_seconds_bucket{route="/orders", method="POST", le="0.8"}[30d]))
  - sum(rate(http_requests_total{route="/orders", method="POST", status=~"5.."}[30d]))
)
/
sum(rate(http_requests_total{route="/orders", method="POST"}[30d]))

# August: 0.99912 — 4,320,000 checkouts, 3,802 of them bad, 518 of the budget left

The numerator is the count of checkouts that finished inside the 800-millisecond bucket, less the count that returned a 5xx, and the denominator is every checkout; the ratio over 30 days is the SLI, and in August it was 99.912 percent. Two honesties about where it is measured. The number comes from the metrics ring inside the service, so it counts from the moment the process saw the request to the moment it wrote the response, and the buyer's experience includes the 40 milliseconds of network in front of it that Chapter 1 showed the service cannot see, plus whatever her carrier does. The indicator is therefore stated at the edge where possible, and Stagedoor checks its own number against the load balancer's latency once a month to know how optimistic it is. And the target is not 100 percent, because the network the service does not control fails at some rate whatever the code does, and a target that ignores that is a target the service cannot meet.

The Objective

The objective is the number the ratio must stay above over the window, and each of Stagedoor's three is a decision about how much failure is acceptable on that path, made with the people who own the product, and written down where the alert rules can read it. Checkout: 99.9 percent of POST /orders good over 30 days, because a failed checkout is a lost sale and a support ticket. The seat map: 99.95 percent of GET /events/{id}/seats under 200 milliseconds, because it is served from Redis 2,600 times a second and it should be, and because a slow seat map is what makes a buyer reload and reload. Organizer reports: 99 percent under 3.2 seconds, because they run on the replica, they can wait, and an organizer who sees a spinner for 3 seconds once in a hundred loads has not been harmed.

Stagedoor's three SLOs, one per path somebody would notice
CheckoutPOST /orders
99.9 percent good over 30 days; good is under 800 ms and not a 5xx. Budget: 4,320 bad checkouts a month.
Seat mapGET /events/{id}/seats
99.95 percent under 200 ms. Cached, hot, and the page a buyer reloads. The strictest of the three.
Organizer reportsserved from pg-replica-a
99 percent under 3.2 seconds. Served from the replica, read by a person at a desk who can wait.

The numbers are not the same because the paths are not the same, and a single "the service is 99.9 percent available" would either be too loose for the seat map or too strict for the reports. Each one has an owner who is not Marek: the head of sales owns checkout's number, because she is the one who decides whether 4,320 lost sales a month is acceptable, and she decided it was, at the price of not demanding 99.99 and the on-call rota that would come with it. The number that an engineer chose alone and nobody else ever saw is not an objective. It is a preference, and it gets ignored the first time it costs a feature.

The Error Budget

An objective of 99.9 percent is a permission to fail 0.1 percent of the time, and that permission has a size. At 100 checkouts a minute, a 30-day month is 4,320,000 checkouts, and 0.1 percent of them is 4,320: that is the error budget, the number of bad checkouts the month may contain before the objective is missed. A Tuesday deploy that returned a 500 on every checkout for 20 minutes burned 2,000 of them, 46 percent of the month, in one evening. That was the signal to stop deploying features for the rest of the month and spend the time on the pool, which is the decision the budget exists to make.

The budget is what turns "reliability versus features" from an argument into arithmetic. When the month's budget is mostly unspent, a risky deploy on a Friday is affordable and the answer is yes. When 46 percent went on Tuesday, the answer is no until the cause is fixed, and nobody has to win a meeting to say so. The budget is also the honest name for what a load test does: the run of Chapter 12 against staging is the budget spent on purpose, in an environment where it costs nothing, to find the pool's limit at 800 requests a second before production spends the real budget finding it on the night.

Alerting on Burn Rate

The wrong alert is "error rate above 1 percent for 5 minutes." It pages at 3 a.m. for a 30-second blip that spent 50 checkouts of the budget, 1 percent of the month, and it sleeps through a slow regression at 0.3 percent that spends the whole budget by the 10th. The right alert is on the rate the budget is being spent, the burn rate: the error ratio divided by the ratio the objective allows. At a burn rate of 1 the budget lasts exactly the 30 days. At 14.4 it is gone in 50 hours, about 2 days. The approach comes from the Google SRE workbook, and it pairs a long window, which makes the alert quiet during a blip, with a short window, which makes it stop as soon as the problem does. The demonstrated rules are the fast one and the slow one.

Two burn-rate alerts on the checkout SLO: fast burn pages, slow burn opens a ticket
# checkout:error_ratio: is 1 - SLI over that window, recorded every minute for 5m, 1h, 6h and 3d

- alert: CheckoutFastBurn
  expr: checkout:error_ratio:1h  > 14.4 * 0.001      # 14.4x: 2% of the month's budget spent in the last hour
    and checkout:error_ratio:5m  > 14.4 * 0.001      # and still burning now: stops paging once it stops
  labels: { severity: page }
  annotations: { summary: "Checkouts are failing or slow: at this rate the month's budget is gone in 2 days" }

- alert: CheckoutSlowBurn
  expr: checkout:error_ratio:3d  > 1 * 0.001         # 1x over 3 days: 10% of the budget spent quietly
    and checkout:error_ratio:6h  > 1 * 0.001
  labels: { severity: ticket }
  annotations: { summary: "Checkout is spending its budget on schedule: find the regression this week" }

The first rule pages when the last hour's error ratio is above 14.4 times the 0.1 percent the objective allows, that is, above 1.44 percent, and the last 5 minutes agree. Two percent of the month's budget in an hour is the workbook's threshold for a page, and a regression that fails 3 checkouts in 200 crosses it. The second rule opens a ticket when the last 3 days averaged above the allowed rate and the last 6 hours did too, which is 10 percent of the budget spent without any single bad hour: a slow leak, a query that got a little worse, a dependency a little slower, found within the week instead of at the end of the month. Between the two, the workbook adds a middle rule, 6 times over 6 hours, which Stagedoor runs as a second page.

The behaviour during a blip is the reason to prefer this shape. Thirty seconds of total failure inside an otherwise healthy hour is 0.8 percent of that hour's requests, under the 1.44 percent line, and the fast-burn rule stays silent, correctly, because 50 checkouts is a bad half-minute and not an incident. Twenty minutes of total failure is 33 percent of the hour, 23 times the line, and the page arrives within 5 minutes of the deploy. A regression to 0.5 percent errors, which a threshold alert would never see, trips the slow-burn rule within a day. Quiet during a blip, loud during a real regression, and never a page for something that spent nothing.

What Feeds the SLO

Every mechanism in this book is a way the checkout budget gets spent, and the postmortem for a burned budget names which. The pool of Chapter 6: 20 connections per loop, and at 800 requests a second the acquire wait alone pushes a checkout past 800 milliseconds. The breaker of Chapter 7: while it is open every checkout is a 503, which is 100 checkouts of budget a minute, so a breaker that flaps for an hour is the month. The stampede of Chapter 9: a seat-map rebuild that lands on the primary at the wrong moment holds the connections checkout needs. The render's CPU from Chapter 11: a container throttled to 2 cores for 4 processes doubles every latency at once. Payrail's own slowness at 3 seconds: a timeout inside the budget, but the buyer still waited 3 seconds, and 3 seconds is a bad checkout by the SLI's definition, whatever the status.

Where the checkout budget went, by the mechanism that spent it
Pool acquire wait above 700 ms at 800 requests a secondslow checkouts: Chapter 6, the pool
Breaker open on payrail for 12 minutes1,200 failed checkouts: Chapter 7, the breaker
Seat-map rebuild landing on the primary every 30 secondsslow checkouts in waves: Chapter 9, the stampede
CPU throttling counter rising after a limit changeevery latency doubled: Chapter 11, the limits
Payrail P50 at 2.8 seconds for an eveningslow checkouts with a 201: Chapter 10, the neighbour

The rows are the reason the SLO is stated on the endpoint the buyer calls and not on any component. A pool SLO, a breaker SLO and a cache SLO would each be green while checkout was red, because each is a cause and the buyer experiences the sum. The gauges of Topic 69 are what a burned budget is explained by, after the fact, and the load test of Chapter 12 is where each row above was seen for the first time, at a controlled rate, with the budget it spent being staging's and not the month's.

SLOs Are Few

Three, for Stagedoor. The rule is one per path a user would notice failing, stated on the endpoint that user calls, and no more. A service with thirty SLOs, one per route and one per dependency, has an alert rule nobody can hold in their head, a dashboard nobody reads, and a page that fires for the reports endpoint at 3 a.m. while checkout is fine. When the thirty were tried, the on-call engineer learned within a week which twenty-seven to ignore, and one of the three that mattered was among them. Fewer objectives, each one watched, is the whole discipline.

The three are reviewed quarterly against what happened: whether the budget was spent, on what, whether 99.9 was too loose or too strict for what the business now expects, and whether a new path has appeared that a user would notice, such as the scanner's GET /tickets/{code} on the night of the door, which became the fourth SLO in the autumn. The review is 30 minutes with the product owners in the room, and its output is a change to a number in a rules file or no change at all. An objective that has never been revised is one that nobody is using.

SLA vs SLO vs SLI

The SLI is the measurement: the ratio of good checkouts to all checkouts over 30 days, computed from the histogram and the counter. It is a number the service produces whether or not anyone has set a target on it.

The SLO is the internal target on that measurement, 99.9 percent, agreed with the product's owner. Missing it has a consequence inside the team: the budget is gone and feature work stops until the cause is fixed. Nobody outside the company sees it.

The SLA is the external contract with a penalty: 99.5 percent, with service credits to the organizers if it is missed. It is set looser than the SLO on purpose, so that the team acts on the objective long before the contract is breached. Stagedoor has SLOs; its SLA with organizers is a business decision built on top of them, and it never appears in an alert rule.

Common Mistakes
  • The SLO on an average — "average checkout under 800 milliseconds" met every month while 1 buyer in 100 waited 4 seconds, because 4 seconds for 1 percent moves an average by 40 milliseconds.
  • 100 percent as the target — no budget, so every failure is an emergency, no deploy is ever justified, and the network the service cannot control misses the target on the first day.
  • Threshold alerts on the error rate — paged at 3 a.m. for a 30-second blip that spent 1 percent of the budget, and silent through a 0.3 percent regression that spent all of it by the 10th.
  • Thirty SLOs — one per route and per dependency, and within a week the on-call engineer has learned to ignore twenty-seven of them, including one of the three that mattered.
  • The SLO nobody agreed to — the number the engineer chose and the product owner never saw, which loses to the first feature that needs the budget it protects.
  • An SLO threshold that is not a bucket boundary — 800 milliseconds stated on a histogram with buckets at 500 and 1,000, so the SLI is an interpolation and the budget is a guess.
Best Practices
  • Define one SLI per critical path from the histogram and the counter, with good stated as a status class and a duration threshold that is a bucket boundary, measured as close to the user as the service can get.
  • Agree each objective with the people who own the product, write it down beside the alert rules, and name the owner.
  • Alert on burn rate with a fast window that pages and a slow window that opens a ticket, and never on a bare error-rate threshold.
  • Spend the error budget deliberately: deploy freely while it is unspent, stop and fix when a deploy burns a large share of it.
  • Keep three to five SLOs, one per path a user would notice, and review them quarterly against what actually happened.
Comparable toolsGoogle SRE book and SRE workbook the chapters on SLOs and on alerting on SLOs, which this topic followsSloth and Pyrra generators that turn an SLO definition into the recording and burn-rate rules aboveNobl9, Datadog SLOs and Grafana SLO the products that hold the objective and draw the budget: Observability Deep Dive

Knowledge Check

Which statement puts the SLI, the SLO and the SLA in their right places for Stagedoor's checkout?

  • The SLA is 99.9 percent, the SLO is 99.5 percent, and the SLI is the alert rule that pages on both
  • The SLI is the good-over-total ratio, the SLO is 99.9 percent, and the SLA is 99.5 percent with credits
  • The SLI is 99.9 percent, the SLO is the histogram it is computed from, and the SLA is the budget
  • The SLO is the promise to organizers, the SLA is the error budget, and the SLI is the P99 of checkout

A Tuesday deploy burned 2,000 of the month's 4,320-checkout budget in 20 minutes. What does the budget decide?

  • That the objective was too strict and should be lowered so the month is still green
  • That failed checkouts during a deploy should be excluded from the indicator's count
  • That feature deploys stop until the cause is fixed, because the arithmetic says so
  • That organizers are owed service credits, since the SLA was missed for the month

Why does a burn-rate alert stay quiet through a 30-second total outage but page within 5 minutes of a deploy that breaks checkout?

  • The short window absorbs the blip and the long window is what catches the broken deploy
  • The rule is suppressed during deploys and only counts errors outside a deploy window
  • Blips are measured on the average latency, while a broken deploy is measured on 5xx counts
  • Over an hour the blip is under the 14.4x line, while a broken deploy is far over it

Why is 100 percent the wrong objective for checkout?

  • It leaves no budget, and the network alone will miss it
  • The histogram cannot compute a ratio against a target of exactly 100
  • Burn-rate alerts cannot be written for an objective above 99.99
  • It would exceed the SLA, and an objective may never be stricter

Stagedoor has three SLOs. What is the rule that keeps the list that short?

  • One SLO per dependency, so that the pool, Redis and Payrail each have a target
  • One SLO per route in the API, so that no endpoint is left without an objective
  • One SLO per path a user would notice failing, stated on the endpoint the user calls
  • One SLO per on-call engineer, so that each person watches a single number

You got correct