Skip to content
Knowledge Hub

SLI, SLO and error budget: measuring reliability from the user side Intermediate

Estimated reading time: 10 min read

The CPU and memory dashboards are green, Pods are not restarting, yet users still report checkout failures. A machine-level metric only tells you the machine is running; it does not answer “how many user transactions completed correctly and fast enough?”.

Without an agreed way to measure the service from the user’s point of view, operations and product teams easily argue by feel: one side says the system is fine, the other says the experience is bad.

SLIs, SLOs and error budgets turn that conversation into a measurable contract. They do not promise zero failures. They help choose the reliability level you need, detect when quality is degrading, and decide when to prioritize stability over new features.

An SLI (service level indicator) is a ratio or measurement of what users receive.

  • An availability SLI for the checkout endpoint over 28 days: the number of eligible requests that returned a successful result divided by the total eligible requests.
  • A latency SLI can be the proportion of requests completed under 500 ms, not necessarily just a p95 number.

A metric name is not enough; you must define eligible requests, good requests, where they are measured, the unit and the time window.

An SLO (service level objective) is the target set on an SLI: at least 99.9% of eligible checkout requests must be “good” over a rolling 28-day window. “Good” can require both a valid status and latency below a threshold, but it must be stated explicitly.

An SLO is an internal reliability target; an SLA is usually a contractual commitment with commercial consequences. Do not use the three terms interchangeably.

The error budget is the portion allowed to miss the SLO within a window. With 10,000 eligible requests and a 99.9% SLO, at most 10 requests may be “bad” in this illustrative calculation:

  • 7 bad so far: 3 left.
  • 5 more bad in the same window (assuming total eligible stays the same): the budget is exceeded by 2.

In a real system the denominator grows with traffic, the rolling window slides continuously, and integer/rounding arithmetic has to be defined consistently.

10,000 eligible requests
├─ 9,993 good
└─ 7 bad
Target 99.9% → budget 10 bad → 3 bad left

The error budget here is a count/ratio of bad events, not automatically a number of downtime minutes. You can convert it to minutes only if the SLI is defined in time, or if you add assumptions about traffic.

An hour with 10 requests and an hour with 100,000 requests affect a request-based SLI very differently.

Start with one important user journey, such as “order placed successfully”, rather than measuring every endpoint the same way. Determine where the user sees the result: the load balancer, the API gateway or a synthetic probe.

  • Measuring too deep inside the service: may miss DNS, TLS and gateway failures and truncated responses.
  • Measuring at the client: closer to the user, but you have to handle sampling, user networks and missing data.

You can use several measurement sources, but each SLO must state its primary source.

Loading diagram…

The diagram shows why two teams both claiming “99.9%” can be talking about different things: for the same system, measuring at the Service handler will never see a request that died at TLS or the gateway. That is why every SLO must state its measurement point, not just a number.

Next, define eligible events. Do you exclude health checks, internal traffic or requests cancelled by the client? If so, why, and does that hide real failures?

A 4xx caused by bad input usually does not count as a server error, but a 4xx caused by a broken auth service can be a failed experience.

Do not choose the denominator to make the dashboard look good; check traces and user tickets to verify that the definition of “bad” is right.

Then choose a target that is both valuable and achievable. 100% sounds attractive but leaves no room to change the system; a target that is too low does not protect the experience. Use historical data, user expectations and business risk to pick the SLO.

Some features may need their own SLO, for example checkout versus reading articles. Combining everything into one ratio can hide failures in an important flow beneath the heavy traffic of a less important endpoint.

Finally, track the burn rate: how fast the budget is being consumed compared to the average allowed pace.

burn_rate = error_ratio / (1 - SLO)
time to exhaust budget = SLO window / burn_rate
  • Burn rate 1: the budget runs out exactly at the end of the window.
  • Burn rate 14.4 on a 30-day SLO: the budget runs out after about 2 days.
  • A high burn rate over a short window signals an acute incident; a moderate but sustained burn rate shows gradually degrading quality.

A common alert configuration (SRE Workbook, 30-day SLO) combines a long window to avoid false alarms with a short window so the alert clears quickly once errors stop:

Burn rate Long / short window Budget consumed Action
14.4 1 hour / 5 minutes 2% Page immediately
6 6 hours / 30 minutes 5% Page
1 3 days / 6 hours 10% Open a ticket

The alert condition is both windows over the threshold: the long window prevents false alarms, the short window lets the alert clear quickly once errors stop. The SRE Workbook formula: budget consumed = burn rate × alert window / SLO window. For example 14.4 × 1 hour / 720 hours = 2%.

PromQL alert for a 99.9% SLO (budget 0.001), following the Workbook’s sample expression:

(
sum(rate(http_requests_total{job="checkout", status=~"5.."}[1h]))
/ sum(rate(http_requests_total{job="checkout"}[1h])) > (14.4 * 0.001)
and
sum(rate(http_requests_total{job="checkout", status=~"5.."}[5m]))
/ sum(rate(http_requests_total{job="checkout"}[5m])) > (14.4 * 0.001)
)

In practice, create a recording rule for each window (1h, 5m, 6h, 30m…) to keep alerts concise and cheap. An alert should include the SLI, the SLO, the remaining budget and a runbook.

Converting to time is only for intuition, because real failures are usually a fraction of requests failing, not a complete outage:

SLO “Full outage” allowed in 30 days In 28 days
99% 7.2 hours 6.72 hours
99.9% 43.2 minutes 40.3 minutes
99.95% 21.6 minutes 20.2 minutes
99.99% 4.32 minutes 4.03 minutes

A service calls three dependencies sequentially, each at 99.9%, and needs all three. The availability ceiling is 0.999³ ≈ 0.997, or 99.7%, lower than the SLO of each dependency.

So you cannot set a 99.9% SLO for this service without retries within the deadline, fallbacks, or removing dependencies from the critical path. See timeout budget and retry, backoff, jitter. (The multiplication assumes independent failures; correlated failures change the result.)

Error budget policy: agree before the incident happens

Section titled “Error budget policy: agree before the incident happens”
Budget consumed (SLO window) Action
Below 50% Release as normal
50-100% More risk review; prioritize fixing recurring failure causes
Budget exhausted (100%) Freeze feature releases; accept only reliability fixes until the budget recovers
One incident consumes more than 20% of the budget Mandatory postmortem with action items

The thresholds above are a template to tune for your organization; what matters is that product and engineering sign off in advance, and that exceptions are deliberate.

Use SLOs and error budgets Not needed yet
A service with real users and stable traffic A prototype with no users yet
You need to balance release speed against reliability Traffic too low for ratios to mean anything (1 error is 10%)
Many teams depend on each other No data yet to define good/eligible

Run the simple calculation:

Terminal window
npx --prefix examples ts-node examples/src/slo-budget/slo-demo.ts

The demo uses 10,000 requests, a 99.9% target, 7 bad initially and then 5 more bad. It holds the denominator fixed so the subtraction is easy to see; with a real rolling window, each new request both increases total eligible and may push old requests out of the window.

Try changing the target to 99.95%: the budget drops from 10 to 5, so the same 7 bad already exceed the target. The target number is therefore a product/operations decision, not just a way to color a dashboard.

To verify real metrics, take a sample of requests from the access log, classify good/bad yourself according to the contract, and compare with the dashboard query. Also test these cases:

  • a 200 response with the wrong business outcome;
  • a timeout at the gateway;
  • a client cancel;
  • a flow with very low traffic.

A syntactically correct Prometheus query does not prove the SLI definition matches the user experience.

  1. Each SLO states the journey, eligible, good, measurement point and window (28 or 30 days).
  2. The SLI is computed with recording rules from metrics at the gateway or load balancer.
  3. Two-window burn-rate alerts (14.4 and 6 for pages, 1 for tickets), with a runbook.
  4. The error budget policy is signed off in advance by product and engineering.
  5. The service’s SLO is less than or equal to the ceiling computed from critical-path dependencies.
  6. Review the definition of good against real tickets and traces every quarter.

Benefits

  • Moves reliability debates onto a measurable definition tied to the user journey.
  • The error budget helps balance the pace of change with operational risk.
  • Burn rate helps prioritize incidents by how fast they consume the target, not by a single metric point.

Limits

  • Choosing the wrong eligible/good can keep the dashboard green while users still fail.
  • Rolling windows, low traffic and sampled data make interpreting ratios tricky.
  • An SLO does not replace root-cause investigation or business prioritization.

Self-check: (1) With 10,000 requests and a 99.9% SLO, what is the maximum number of bad requests? (2) Why is a 200 not always good? (3) Why does a green CPU dashboard not prove the checkout SLO is met?