SLI selection and SLO targets
What it is
An SLI is a measurement of service health from the user's point of view. An SLO is a target for that measurement, derived from what users actually tolerate, not from what the system currently achieves and not from a round number.
SLI "the proportion of checkout requests that returned a
2xx or 4xx within 800ms, measured at the edge"
SLO "99.5% of checkout requests over a rolling 28 days"
The two design questions, and both are usually answered badly:
WHAT do you measure, and WHERE?
WHAT TARGET, and derived from what?
What this is confused with: an SLI as a system metric. CPU utilisation, queue depth and cache hit rate are useful and they are not SLIs, because a user cannot experience them. An SLI must be something a user would notice if it got worse.
Also confused: an SLO as an aspiration. An SLO is a decision about how much unreliability you will tolerate in exchange for velocity, and it is only real if breaching it changes what the team does (see the error budget policy). A target that produces no consequence is a number on a dashboard.
The problem it solves
Measuring in the wrong place produces a green dashboard during an outage.
An SLI measured at the application server:
"99.98% of requests returned 200"
What the user experienced that hour:
- the CDN was serving 502s for 8 minutes for one region,
which never reached the application
- TLS handshakes were failing for a subset of clients after
a certificate rotation
- the JavaScript bundle 404'd, so the page rendered blank
while every API call it never made stayed at 100%
Every one of those is invisible to a server-side success-rate
SLI, and all three are total outages from the user's seat.
The general rule: THE FURTHER FROM THE USER YOU MEASURE, THE
MORE FAILURE MODES YOU EXCLUDE BY CONSTRUCTION.
And picking a target without deriving it produces one of two failures:
TOO HIGH: "99.99% because that sounds serious"
= 4.4 minutes of downtime per month.
Every deploy, every dependency blip and every certificate
rotation consumes it. The team is permanently over budget,
the policy is permanently suspended, and the SLO now means
nothing.
TOO LOW: "99.5%, we hit that easily"
= 3.6 hours per month, and you will never notice a real
degradation because the budget absorbs it. Users churn while
the SLO stays green.
Both come from choosing a number first and reasoning afterwards.
Mechanics
Choosing what to measure
Start from what the user is trying to do, not from what the system emits.
For each critical user journey, ask: what does "working" mean?
checkout the order is placed, and it feels immediate
search results come back, and they are relevant
data pipeline yesterday's numbers are there when I look
streaming the video plays without stalling
Then find the smallest set of measurements that would go bad if
any of those stopped being true.
The four SLI families, and which one fits:
AVAILABILITY / CORRECTNESS
proportion of valid requests served successfully
fits: request-response APIs, page loads
LATENCY
proportion of valid requests served faster than a threshold
fits: anything interactive
NOTE: expressed as a PROPORTION UNDER A THRESHOLD, not as a
percentile value. See below.
FRESHNESS / CORRECTNESS OF DATA
proportion of records updated more recently than X, or
proportion of outputs that match a reference
fits: pipelines, caches, search indexes, replicas
THROUGHPUT / COVERAGE
proportion of expected work actually processed
fits: batch, streaming, anything with an input queue
Latency as a proportion rather than a percentile is the detail that matters most in practice:
BAD SLI: "p99 latency < 800ms"
- percentiles cannot be averaged or aggregated across
instances or windows (see the percentiles page)
- it produces no error budget: how much of "p99 was 830ms"
have you spent?
GOOD SLI: "proportion of requests completing in under 800ms"
- it is a ratio of good events to valid events, so it
aggregates correctly by summing numerators and
denominators
- a 99.5% target directly yields an error budget: 0.5% of
requests may be slow
- the threshold is a product decision you can defend
Every SLI should be expressible as good events / valid events, because that form aggregates, it
produces a budget, and it is comparable across services.
Where to measure: as close to the user as you can afford
Measurement point Catches Misses
--------------------------------------------------------------------
Real user monitoring everything the user bot traffic
(client-side, RUM) experiences: DNS, TLS, needs care;
CDN, JS, render client clock
skew
Edge / CDN logs CDN failures, TLS, most client-side
network issues render and JS
Load balancer app failures, LB failures CDN, DNS, TLS
at the edge
Application server app failures only everything in
front of it
Downstream dependency nothing user-facing almost all of
metrics it
The practical answer for most services: measure at the load balancer or edge as the primary SLI, and add RUM for user-facing surfaces. Server-side is the fallback when you control nothing in front.
Synthetic probes are a complement, not a substitute:
Synthetics give you: coverage during low traffic, a consistent
baseline, and detection of total outages where real traffic
goes to zero (and a ratio of 0/0 is not a signal).
Synthetics miss: the real input distribution, the user's
network, real client diversity, and anything driven by
specific accounts or data.
Use both. A ratio SLI goes blind at zero traffic, which is
exactly when the outage is total, and that is the specific gap
a synthetic probe fills.
Defining "valid events" carefully
The denominator is where SLIs are quietly gamed, usually by accident.
Decisions that must be explicit and written down:
Do 4xx responses count as failures?
Usually NO for 400/404 (the client asked for something
wrong) and YES for 429 (you refused to serve them) and
YES for 401 caused by your own token service failing.
"All 4xx are the client's fault" is how a broken auth
service reports 100% availability.
Do health checks and internal probes count?
NO. They inflate the denominator with traffic that never
fails and dilute real failures.
Does bot traffic count?
Usually no, and the filter must be stable, because a
change to bot classification silently moves the SLI.
What about requests the user abandoned?
A client disconnect after 30 seconds is a failure from the
user's perspective even though the server logged nothing.
Count it if you can see it.
Long-tail endpoints?
A single SLI over all endpoints lets a high-volume healthy
endpoint mask a low-volume broken one. Either scope the
SLI to a journey or add per-journey SLIs.
The masking problem is worth stating as arithmetic:
/api/feed 10,000,000 req/day, 99.99% success
/api/checkout 50,000 req/day, 92% success
Combined SLI: (9,999,000 + 46,000) / 10,050,000 = 99.95%
The SLO is met. Checkout is broken for one user in twelve.
An SLI aggregated over a whole service is an average over
journeys with wildly different importance, and averages hide
exactly the case you care about.
Deriving the target from tolerance
The target answers: how much unreliability do users tolerate before it changes their behaviour? Four sources of evidence, in descending order of quality:
1. OBSERVED BEHAVIOUR CHANGE
Look at historical periods of degradation and what users
did. Did sessions drop? Did support tickets rise? Did
conversion move?
"In the two hours we were at 98%, checkout conversion fell
9% and support volume tripled" is the strongest possible
input, and most companies have this data and never look
at it.
2. CURRENT PERFORMANCE AND COMPLAINT LEVEL
If you have been at 99.7% for a year and nobody has
complained, 99.9% is not obviously justified. If you have
been at 99.7% and the account team raises it monthly, it is.
3. CONTRACTUAL AND COMPETITIVE FLOOR
The SLA is the floor and the SLO must be strictly tighter
(see SLA vs SLO vs SLI). What competitors publish sets an
expectation independent of what users would tolerate.
4. THE DEPENDENCY CEILING
You cannot durably exceed the composite availability of
what you depend on. If three serial dependencies are each
99.9%, your ceiling is about 99.7% before any of your own
failures.
Then do the arithmetic before committing, because the numbers are less intuitive than they look:
Error budget per 28 days:
99% 6h 43m and 1 in 100 requests
99.5% 3h 21m
99.9% 40m and 1 in 1,000 requests
99.95% 20m
99.99% 4m 2s and 1 in 10,000 requests
99.999% 24s
Read that as: at 99.99%, a single 5-minute incident consumes
more than a month's budget. If your deploy process can cause a
2-minute blip, you can afford two deploys a month.
This is the calculation that converts "four nines sounds
right" into a conversation about what you would have to stop
doing.
Set the target from the tolerance, then check it against the cost of achieving it, and if the two disagree, that is a business conversation rather than an engineering one.
The latency threshold, derived rather than picked
Do not pick 200ms because it is a round number. Derive it:
1. What is the user's task? An autocomplete has a different
threshold from a monthly report.
2. What does the current distribution look like? Plot it. A
threshold inside the body of the distribution produces a
noisy SLI; a threshold on the shoulder is stable.
3. Where does behaviour change? If you have session data,
find the latency at which abandonment rises.
4. Consider TWO thresholds rather than one:
"99% of requests under 500ms" (the good experience)
"99.9% of requests under 3s" (the tolerable ceiling)
Two thresholds catch two different failures: a general
slowdown, and a heavy tail affecting a small group badly.
The two-threshold form is under-used and it is what catches the "most users are fine and 0.5 percent are timing out" pattern, which a single p99-under-500ms target will report as a small budget spend rather than as a group of users who cannot use the product at all.
The window and the review
ROLLING WINDOWS (28 days) rather than calendar months, because a
calendar month resets the budget on the 1st and produces an
incentive to ship recklessly on the 30th.
28 DAYS rather than 30, so the window always contains exactly
four of each weekday and weekend traffic patterns do not
shift the baseline.
REVIEW QUARTERLY. An SLO that has never been changed is
usually not being used. If you have been at 99.99% against a
99.9% target for a year, either tighten it or admit the
target is not driving decisions.
A worked example: a green dashboard during an outage
A retail platform. Existing SLO: "99.9% availability", measured as non-5xx responses at the application servers, aggregated across all endpoints, on a calendar month.
The incident that started the redesign:
14:10 a CDN configuration change causes the JS bundle to
return 404 for one region.
14:10 the page loads, renders a blank container, and makes no
API calls.
14:52 a customer tweet is escalated internally.
15:05 fixed.
The SLO dashboard for that day: 99.99%.
Because: the application server saw FEWER requests, all of
which succeeded. The SLI improved during the outage.
"The SLI improved during the outage" is the clearest possible demonstration of a measurement-point problem, and it made the redesign uncontroversial.
The redesign, per journey rather than per service:
Four critical journeys identified with the product team:
BROWSE user can see products
SEARCH user can find products
CHECKOUT user can pay
ACCOUNT user can see orders
SLIs, all in good/valid form:
BROWSE availability
RUM: proportion of page views where the product grid
rendered, measured client-side
valid: page views excluding known bots, excluding
user-initiated aborts under 1s
CHECKOUT availability
edge: proportion of POST /checkout returning 2xx or a
4xx OTHER THAN 429, within 30s
valid: all requests reaching the edge, excluding health
checks
CHECKOUT latency, TWO thresholds
proportion under 800ms (target 99%)
proportion under 4s (target 99.9%)
SEARCH freshness
proportion of catalogue updates visible in search within
5 minutes
ACCOUNT availability
edge, same form as checkout
Deriving the checkout target from tolerance:
STEP 1: observed behaviour change.
Pulled the last 18 months of degradation events and joined
them to conversion.
availability in the hour conversion vs baseline
99.9%+ no measurable change
99.5% - 99.9% -1.2%
99.0% - 99.5% -7%
< 99.0% -31%, and support volume 4x
The knee is between 99.5% and 99.9%.
STEP 2: current performance and complaints.
Trailing 12-month checkout availability: 99.86%.
Account escalations mentioning checkout: 3 in 12 months, all
during events below 99%.
STEP 3: contractual floor.
The enterprise SLA promised 99.5% monthly with credits.
The SLO must be tighter than the SLA, and by enough that
breaching the SLO is a warning rather than a bill.
STEP 4: dependency ceiling.
Checkout depends serially on auth (99.95%), inventory
(99.9%) and the payment gateway (99.95% per its own SLA).
Composite ceiling: 0.9995 x 0.999 x 0.9995 = 99.80%.
THIS WAS THE FINDING. A 99.9% target was arithmetically
unreachable while those dependencies were serial, regardless
of the team's own reliability.
The dependency ceiling of 99.80 percent against a 99.9 percent target explained a year of unexplained budget exhaustion, and no amount of work on the checkout service itself could have fixed it.
What was decided:
CHECKOUT SLO: 99.8% over a rolling 28 days.
Justification, written into the SLO document:
- the tolerance knee is between 99.5% and 99.9%; 99.8% sits
above the level at which conversion measurably moves
- it is tighter than the 99.5% SLA by 0.3 points, which is
~2 hours a month of warning before credits
- it is at the current dependency ceiling, which makes the
ceiling visible as a decision rather than a mystery
AND a companion decision, which is the point of doing the
arithmetic: to go above 99.8% requires removing a serial
dependency. Inventory was made non-blocking (checkout proceeds
optimistically and reconciles), raising the ceiling to 99.90%.
That work was scoped and funded because the SLO arithmetic made
it the only available lever.
The SLO calculation produced an architecture decision, which is the strongest argument for deriving targets rather than picking them.
Results after two quarters:
before after
SLIs 1 (service- 6 (per journey,
wide, server- edge + RUM)
side)
outages invisible to the SLO 3 in prior year 0
budget exhausted 9 of 12 months 3 of 6 months
target justification "it sounded written, with the
right" conversion data
error budget policy invoked never (always twice, both times
exhausted, so acted on
ignored)
Going from "exhausted 9 months in 12" to "exhausted 3 in 6" was not a reliability improvement, it was a target that was achievable, which is what made the error budget policy start functioning at all.
One thing that was harder than expected:
Defining "valid events" for BROWSE took three attempts.
Attempt 1: all page views. Included bots, which were 34% of
volume and never failed, diluting real failures by a third.
Attempt 2: excluded bots by user-agent. A bot-classification
update in month two moved the SLI by 0.15 points with no
change in reliability, which triggered a false investigation.
Attempt 3: excluded bots, AND pinned the classification rules
as a versioned artifact reviewed alongside the SLO.
The lesson recorded: any filter in the denominator is a
dependency of the SLI, and changing it silently changes your
reliability history.
Production evidence
Google's SRE Book and the SRE Workbook specify the good-events-over-valid-events SLI form, the four SLI families (availability, latency, quality, freshness), the practice of measuring as close to the user as possible, and the argument that latency SLIs should be expressed as a proportion under a threshold rather than as a percentile value.
Google's published SLI menu by service type (request-driven, pipeline, storage) is the standard starting point for choosing which family applies, and the SRE Workbook's worked example explicitly warns about denominators that include health checks and internal traffic.
Rolling windows over calendar windows is documented SRE practice, with the stated reason that a calendar reset creates an incentive to spend the remaining budget before it expires and to be reckless immediately after a reset.
Composite availability arithmetic for serial dependencies is standard reliability engineering, and it is the basis for the dependency-ceiling check: a service cannot durably exceed the product of its serial dependencies' availabilities.
Real user monitoring as the measurement point for user-facing surfaces is standard practice in web performance (and is what Core Web Vitals field data is), and the class of failure it catches that server-side monitoring cannot, CDN, TLS, DNS and client-side rendering failures, is well documented.
The SLA-tighter-than-SLO relationship is universal published practice: the internal target must be strictly tighter than the contractual commitment so that breaching the internal target is a warning rather than a financial event.
The debate
Server-side or client-side SLIs? Client-side for anything user-facing, because the failures that server-side measurement excludes by construction (CDN, TLS, DNS, bundle, render) are total outages from the user's seat. The counter-argument is real: RUM data is noisier, requires bot filtering, depends on client clocks, and only reports from clients that successfully loaded enough JavaScript to report. The practical answer is edge measurement as the primary SLI with RUM as a second, and synthetics to cover the zero-traffic case.
Should 4xx count as failures? Not by default, and the blanket rule is dangerous. 429 is your refusal to serve, and a 401 caused by your own token service failing is your outage reported as the client's fault. Decide per status code, write it down, and revisit it when a new failure mode appears.
One SLO per service or per journey? Per journey. A service-wide SLI averages over journeys of wildly different importance, and the arithmetic is unforgiving: ten million healthy feed requests and fifty thousand checkout requests at 92 percent still produce a 99.95 percent aggregate. The cost is more SLOs to maintain, which is real and is the correct price.
Is 99.99% ever the right target? For infrastructure that many things depend on, sometimes. For a typical product service it is usually a decision nobody has costed: 4 minutes a month means a single 5-minute incident blows the budget, so the deploy process, the dependency set and the on-call response all have to be built for it. The honest test is whether you would fund what it requires.
Should the SLO be set to current performance? No, though current performance is an input. Setting the target at what you already achieve guarantees it never drives a decision, and setting it far above guarantees permanent exhaustion, after which the policy is suspended and the SLO is decoration. Derive from tolerance, sanity-check against the dependency ceiling, and accept a target you can actually defend.
Is deriving a target from conversion data over-engineering? It is usually a day of analysis on data the company already has, and it converts the target from a preference into a finding. When a target is challenged in a planning meeting, "the knee in conversion is between 99.5 and 99.9 percent" ends the conversation and "it felt right" does not.
Follow-up Q&A
"What makes a good SLI?"
It is something a user would notice if it got worse, it is measured as close to the user as you can afford, and it is expressed as good events over valid events so that it aggregates correctly and produces an error budget. CPU and queue depth are useful metrics and not SLIs, because a user cannot experience them. Latency in particular should be a proportion under a threshold rather than a percentile value, because percentiles cannot be aggregated across instances or windows and give you no way to say how much budget a slow period consumed.
"Why does measurement point matter so much?"
Because the further from the user you measure, the more failure modes you exclude by construction. A server-side success-rate SLI cannot see a CDN outage, a TLS failure after a certificate rotation, or a JavaScript bundle 404 that renders a blank page. In one case a CDN misconfiguration produced a blank page for a whole region and the SLI improved during the outage, because the application server received fewer requests and all of them succeeded. Measure at the edge as the primary SLI, add real user monitoring for user-facing surfaces, and keep synthetic probes for the case where real traffic goes to zero and a ratio becomes 0 over 0.
"How do you decide what counts as a valid event?"
Explicitly, in writing, because the denominator is where SLIs get gamed by accident. Exclude health checks and internal probes, which never fail and dilute real failures. Decide per status code rather than by class: 400 and 404 are usually the client's problem, 429 is your refusal to serve, and a 401 caused by your own token service failing is your outage. Filter bots, and pin the classification as a versioned artifact, because in one case a bot-classification update moved the SLI by 0.15 points with no change in reliability and triggered a false investigation. Any filter in the denominator is a dependency of the SLI.
"How do you derive an SLO target?"
Four inputs, in descending order of quality. Observed behaviour change: join historical degradation periods to conversion, session or support data and find the knee. Current performance against complaint level. The contractual floor, since the SLO must be strictly tighter than the SLA so that breaching it is a warning rather than a bill. And the dependency ceiling, because you cannot durably exceed the product of your serial dependencies' availabilities. Then do the budget arithmetic: 99.99 percent is four minutes a month, so a single five-minute incident exhausts it, which turns "four nines sounds right" into a conversation about what you would stop doing.
"What happens if the target is above the dependency ceiling?"
You exhaust the budget permanently for reasons no amount of work on your own service can fix. In one case checkout depended serially on auth at 99.95, inventory at 99.9 and a payment gateway at 99.95, for a composite ceiling of 99.80 percent against a 99.9 percent target, which explained a year of unexplained exhaustion. The useful outcome is that it makes the lever explicit: raising the target required removing a serial dependency, so inventory was made non-blocking with optimistic checkout and reconciliation, which raised the ceiling to 99.90. The SLO arithmetic produced an architecture decision.
"Why two latency thresholds instead of one?"
Because they catch different failures. "99 percent under 500 milliseconds" catches a general slowdown. "99.9 percent under 3 seconds" catches a heavy tail where a small group of users is timing out completely. With a single threshold, the pattern where most users are fine and half a percent cannot use the product reports as a small budget spend rather than as an outage for those users. The thresholds themselves should be derived from the shape of your latency distribution and, where you have the data, from where abandonment rises, rather than picked as round numbers.
Common misconceptions
"CPU and queue depth are SLIs." They are useful system metrics. An SLI is something a user would notice.
"p99 latency under 800ms is an SLI." Percentiles do not aggregate and produce no budget. Use the proportion of requests under the threshold.
"All 4xx are the client's fault." 429 is your refusal to serve, and a 401 from your own broken token service is your outage reported as theirs.
"One SLO per service is enough." It averages over journeys of different importance, so ten million healthy feed requests can hide a checkout that is failing for one user in twelve.
"Higher targets are safer." 99.99 percent is four minutes a month, so one incident exhausts it, the policy gets suspended, and the SLO stops meaning anything.
"Set the SLO where we are today." Then it will never drive a decision. Derive it from tolerance and check it against the dependency ceiling.
Interview delivery note
Say this verbatim: "An SLI has to be measured where the user is, because the further back you measure the more failure modes you exclude by construction. We had a CDN misconfiguration render a blank page for a whole region and the SLI improved during the outage, because the app servers saw fewer requests and all of them succeeded." One sentence of principle and one unforgettable example.
The senior-versus-staff separator is checking the target against the dependency ceiling. A senior engineer sets an SLO from historical performance. A staff engineer computes the composite availability of the serial dependencies, finds the ceiling is 99.80 percent against a 99.9 percent target, and concludes that a year of budget exhaustion had an arithmetic cause no amount of work on the service could fix. Then the SLO produces an architecture decision, making one dependency non-blocking, which is the target becoming a design constraint rather than a dashboard.
The second signal is deriving the target from observed behaviour change. Saying "we joined 18 months of degradation events to conversion and the knee is between 99.5 and 99.9 percent, so we set 99.8" converts the target from a preference into a finding, and it is a day of analysis on data most companies already have and never look at.
Further reading
- Google's SRE Workbook, "Implementing SLOs," for the good/valid event form, the SLI menu by service type, and worked denominators.
- Google's SRE Book chapter on service level objectives, for the argument on measuring close to the user and on latency as a proportion under a threshold.
- The error budget policy page, for what makes a target consequential.
- The composite SLOs and dependency availability math page, for the ceiling calculation.
- The percentiles page, for why a percentile cannot be an SLI.