Performance Budgets & SLOs

A threshold is a fact about the metric. A budget is a commitment about the product. As framed in Custom Metrics & Business Impact Tracking, the point of measuring anything is to change a decision, and a budget is the mechanism that turns a percentile into a decision someone is accountable for. This page covers how to set per-route budgets that reflect what a screen is worth, how to express them as service level objectives with an error budget, and how to gate releases on field data rather than a lab score.

The distinction that makes this work: a budget is a lab-testable ceiling on a proxy, an SLO is a field commitment about real users. Teams that conflate the two end up either gating on numbers that do not predict user experience, or waiting 28 days to discover a release was bad.

From a threshold to a decision someone ownsWithout the last box this is a reporting exercise. The policy — freeze, prioritise, escalate — is what makes the other four boxes matter.From a threshold to a decision someone ownsEach stage narrows what the number is allowed to meanThresholdLCP 2.5 s, spec-definedBudgetper route, lab-testableSLOshare of good sessions, fieldError budgethow much failure is allowedPolicywhat happens when it burns
Without the last box this is a reporting exercise. The policy — freeze, prioritise, escalate — is what makes the other four boxes matter.

Budgets: a ceiling per route, not per site

A single site-wide budget is either too loose for the landing page or too tight for the dashboard behind the login. Budgets should be per route template and derived from three inputs: the assessed threshold, the current field p75, and the commercial weight of the screen.

Route template Field p75 today Budget Rationale
/ 1.9 s 2.2 s Highest volume; must stay comfortably inside Good
/products/[slug] 2.9 s 2.5 s Directly monetised; needs to reach Good
/search 3.2 s 2.8 s Interaction-heavy; INP budget matters more than LCP
/account/* 3.8 s 3.5 s Authenticated, low volume, not assessed for search

Setting a budget below the current value on purpose is the point — a budget you already meet is a description, not a target. What makes it credible is the second column: a budget more than about 25% below today’s number will be breached on the first deploy and ignored by the second.

Alongside the metric budget, keep a resource budget that predicts it. Bytes and main-thread time are the levers engineers actually pull.

Resource Budget Why this number
JavaScript, initial route 170 KB gzipped Roughly 350 ms of parse and compile on a mid-tier device
Images above the fold 200 KB One hero at correct dimensions in a modern format
Total blocking time (lab, 4× CPU) 300 ms Correlates with field INP better than any single-page metric
Third-party requests before load 4 Each one is an uncontrolled dependency on someone else’s uptime
Budget headroom by routeThree of four routes are over budget. The ranking that matters is not who is worst but who is worst multiplied by traffic — which is the product route.Budget headroom by routeField p75 against the agreed budget, 28 days/ (budget 2200)1,920 ms/products (budget 2500)2,880 ms/search (budget 2800)3,240 ms/account (budget 3500)3,760 ms0 ms1,250 ms2,500 ms3,750 ms5,000 ms
Three of four routes are over budget. The ranking that matters is not who is worst but who is worst multiplied by traffic — which is the product route.

SLOs: a share of good sessions, measured in the field

An SLO is expressed as a proportion, not a percentile: 95% of /checkout sessions have an INP under 200 ms, measured over 28 rolling days. This shape has two advantages over a p75 target. It is directly interpretable (“one session in twenty is slow”), and it produces an error budget — the number of failing sessions you are allowed before the objective is missed.

SLO Window Objective Error budget (at 400k sessions)
/checkout INP < 200 ms 28 days 95% 20,000 slow sessions
/products/[slug] LCP < 2.5 s 28 days 90% 40,000 slow loads
All routes CLS < 0.1 28 days 97% 12,000 shifting loads
Beacon delivery 7 days 98% 8,000 lost sessions

The last row is not a performance objective — it is the one that makes the other three trustworthy. An SLO computed over incomplete data is a story, and delivery loss is not randomly distributed, as the exit-path breakdown in Self-Hosted Beacon Collection shows.

-- SLO attainment and error budget burn, one query per objective.
SELECT
  route,
  countIf(value < 200) / count()                          AS attainment,
  count()                                                 AS sessions,
  count() - countIf(value < 200)                          AS failing,
  greatest(0, toUInt32(count() * 0.05) - (count() - countIf(value < 200))) AS budget_left
FROM rum_events
WHERE metric = 'INP'
  AND route = '/checkout'
  AND ts >= now() - INTERVAL 28 DAY
GROUP BY route;

budget_left is the number every conversation should start from. “We are at 94.2% against a 95% objective” invites debate about measurement; “we have spent our entire error budget for the window with nine days left” does not.

Burn rate: how fast the budget is being spent

A 28-day error budget consumed evenly is fine. The same budget consumed in six hours is an incident. Burn rate — current failure rate divided by the rate that would exactly exhaust the budget over the window — is what turns an SLO into an alert.

Burn rate Budget exhausted in Response
28 days Normal; no action
~9 days Ticket, investigate this sprint
~4.5 days Page during working hours
14× ~2 days Page immediately

Multi-window burn-rate alerting — requiring a high burn over a short window and an elevated burn over a longer one — is what keeps this from firing on every traffic spike. The alert plumbing belongs to Alerting & Regression Detection.

Error budget consumption across a windowThe bad release burned two weeks of budget in four days. The line shape, not the endpoint, is what tells you to act on day 12.Error budget consumption across a windowShare of the 28-day budget spent, two releases0%50%100%150%200%budget exhaustedd1d4d8d12d16d20d24d28Healthy windowWindow with a bad release
The bad release burned two weeks of budget in four days. The line shape, not the endpoint, is what tells you to act on day 12.

Implementation: one definition, three consumers

The failure mode of budget programmes is drift — CI checks one number, the dashboard shows another, and the SLO review uses a third. Keep one machine-readable definition and generate the rest.

// budgets.config.js — the single source of truth. CI, dashboards and the
// SLO report all import this file.
export const budgets = {
  '/': {
    lab: { lcp: 2200, tbt: 300, jsBytes: 170_000 },
    slo: { metric: 'LCP', threshold: 2500, objective: 0.9, windowDays: 28 },
  },
  '/products/[slug]': {
    lab: { lcp: 2500, tbt: 300, jsBytes: 190_000 },
    slo: { metric: 'LCP', threshold: 2500, objective: 0.9, windowDays: 28 },
  },
  '/checkout': {
    lab: { lcp: 3000, tbt: 250, jsBytes: 210_000 },
    slo: { metric: 'INP', threshold: 200, objective: 0.95, windowDays: 28 },
  },
};

export function burnRate({ failing, total, objective, windowDays, elapsedDays }) {
  const allowed = total * (1 - objective);
  if (allowed <= 0) return failing > 0 ? Infinity : 0;
  const expectedByNow = allowed * (elapsedDays / windowDays);
  return expectedByNow > 0 ? failing / expectedByNow : 0;
}

The lab half of each entry is checked in CI on every pull request; the SLO half is evaluated against field data on a schedule. The two are deliberately different numbers for the same route — the lab budget is a proxy, tightened so that meeting it usually means the field objective holds.

Step-by-step: standing a budget programme up

  1. Measure before you commit. Four weeks of field data per route template, segmented by device tier, as covered in Device & Network Segmentation.
  2. Pick the routes that matter. Three to six templates covering most traffic and most revenue. Budgets on forty routes get ignored on all forty.
  3. Set the budget 10–25% below today’s value. Achievable within a quarter; not achievable this week.
  4. Write the SLO with an explicit window and objective, and compute the error budget in sessions so it is a countable thing.
  5. Wire the lab gate into CI so a pull request that blows the resource budget fails before review — the mechanics are in Gating Pull Requests on RUM Budgets.
  6. Agree the policy in advance. What happens at 100% burn: feature freeze on that surface, a mandatory item in the next sprint, or nothing? An SLO with no consequence is a chart.
  7. Review monthly, adjust quarterly. Budgets that never move are either unambitious or abandoned.

Deriving the budget from data rather than opinion

The number in the budget column has to come from somewhere, and “round number slightly below today” is a weak defence when a launch is at stake. Three derivations hold up in a room with a product owner in it.

From the assessment. The route needs to be in Good at p75, so the budget is the Good threshold with a margin for the gap between lab and field. If your lab-to-field ratio on that route is 0.7 — the lab consistently reads 30% faster — then a 2.5 s field target is a 1.75 s lab budget. Measure the ratio; do not assume it.

From the conversion curve. If you have the bucketed conversion data described in Conversion Funnel Correlation, the budget can be set at the point where the curve bends. On many commerce templates the drop between the 2.0–2.5 s bucket and the 2.5–3.5 s bucket is far larger than the drop either side of it, which makes 2.5 s the defensible number for reasons that have nothing to do with the specification.

From the competitive position. If the peer benchmark in CrUX & Public Field Data puts you at the 40th percentile of your market, a budget that would move you to the 70th is a business objective the whole room understands.

Derivation Best when Weakness
From the assessment threshold Search traffic matters commercially Ignores whether users notice
From the conversion curve You have funnel data joined to metrics Correlational; needs re-derivation as the site changes
From the peer benchmark Competitive market, exec sponsorship Moves when competitors move
From today minus 20% Nothing else is available Arbitrary; hard to defend under pressure

Whichever you use, record it next to the number. A budget with a stated derivation survives a re-org; a bare number does not.

Making the error budget a decision, not a chart

An error budget only changes behaviour if something happens when it runs out. The policy needs to be agreed before the first breach, because agreeing it during one is a negotiation nobody wins.

A workable escalation ladder, in the order teams usually adopt it:

  1. Under 50% consumed at the halfway point — nothing. Normal operation.
  2. Over 75% consumed with a week left — the owning team adds a performance item to the next sprint. No approval needed; it is automatic.
  3. Fully consumed — new work on that surface pauses until attainment recovers, or the objective is formally renegotiated with a written reason.
  4. Consumed twice in consecutive windows — the objective itself is wrong, the route needs structural work, or the budget was never achievable. All three are conversations for the product owner, not the on-call engineer.

The renegotiation path in step three is what keeps the system honest. Objectives that can never be missed are decoration; objectives that can never be changed get quietly abandoned. Writing down that a team chose to spend its budget on a launch — with a date and a reason — turns a broken promise into a recorded trade-off.

Reporting cadence

Budgets and SLOs decay without a rhythm to review them in. What works, and what each meeting is for:

  • Per pull request: the lab resource budget. Automated, blocking, no humans involved.
  • Per release, two hours after deploy: field p75 for the affected routes against budget, on beacons carrying the new release marker only. Non-blocking, but a failure here is the fastest signal you will get.
  • Weekly: burn rate per objective, plus the sample counts. Fifteen minutes, owned by the team.
  • Monthly: attainment against every objective, plus any renegotiations. Product owner present.
  • Quarterly: re-derive the budgets from current field data and the current conversion curve. Retire objectives nobody has looked at.

The quarterly step is the one that is always skipped and always matters most: a budget set against last year’s device mix is measuring a population that has moved on.

What a budget review actually looks like

The monthly review is where a budget programme either becomes routine or quietly dies. A workable agenda is four items and twenty minutes.

Attainment and burn per objective, read straight from the query, with sample counts alongside. Anything below the sample floor is skipped rather than discussed.

Any breach since the last review, with the release that caused it and what was done. The point is not blame; it is whether the detection worked and how long it took.

Renegotiations requested. A team that wants to spend budget on a launch says so here, with a date to restore it. Written down, this is a trade-off; unwritten, it is an abandoned objective.

One structural item. Every review should surface a change that would make the next month easier — a route that needs splitting, a cohort that needs oversampling, a budget that has never once been close.

What does not belong in the meeting is a walk through every route’s chart. If the numbers need narrating, the dashboard is wrong.

Failure modes and gotchas

  • Budgets set from the lab number. A CI runner is faster than a mid-tier phone. A budget derived from a lab run will be met while the field objective fails continuously.
  • An SLO on the origin instead of the route. Origin-level objectives are met by the lightest pages and tell the team nothing about the screen that is failing.
  • No error budget, only a target. Without a countable budget every conversation reduces to whether 94.2% is close enough to 95%.
  • Ignoring sample size. An SLO on a route with 300 sessions a month swings wildly for reasons unrelated to the site. Aggregate low-traffic routes into a group objective.
  • Burn-rate alerting on a single window. Short windows fire on spikes; long ones notice too late. Require both.
  • Budgets nobody agreed to. A number an engineer chose alone gets overruled the first time it blocks a launch. Get the product owner to sign the objective, not just the threshold.
  • Never resetting after a deliberate trade. If the business chooses to ship a heavy feature, record the decision and re-baseline. A permanently breached budget teaches everyone to ignore the dashboard.

FAQ

What is the difference between a budget and an SLO here?

A budget is a lab-testable ceiling on a proxy metric, checked on every pull request. An SLO is a commitment about the share of real sessions that meet a threshold, evaluated in the field over weeks. You need both: one is fast and approximate, the other is slow and authoritative.

What objective should I start with?

Ninety percent for a load metric and ninety-five for an interaction metric on your most commercially important route, over 28 days. Both are achievable for most sites and leave an error budget large enough to be meaningful.

Should a breached budget block a release?

Block on the lab resource budget, because it is deterministic and the author can fix it in the same pull request. Do not block on the field SLO — it lags by design. Use burn rate to trigger prioritisation instead.

How do I budget for a route with very little traffic?

Group it with similar routes and set one objective for the group. A per-route SLO needs a few thousand sessions per window before its attainment figure stops moving on its own.

Who owns the number?

The team that owns the route, with the product owner signing the objective. A performance budget owned by a central platform team is advice; one owned by the team shipping the feature is a constraint.

None of this works without agreement. A budget is a negotiated constraint, an objective is a promise, and both are only worth writing down if someone is prepared to act when they are missed. The engineering here is easy; the durable part is the policy attached to it.