Back to archive

Engineering

The Difference Between Latency Budgets and Latency Wishes

A practical model for converting performance goals into owned budgets across a request path.

The Difference Between Latency Budgets and Latency Wishes

Most performance goals start as a sentence that sounds reasonable: the page should feel fast, the API should be responsive, the job should finish quickly enough that nobody notices. That sentence is useful as a desire. It is not yet engineering.

The gap between a latency wish and a latency budget is ownership. A wish says what the system should do. A budget says which part of the system is allowed to spend how much time, who owns each spend, and what happens when the math no longer works.

The thesis

Latency is rarely fixed by making everything faster. It is fixed by deciding which work deserves time, which work must move out of the path, and which owners are accountable for keeping their slice inside a shared budget.

This is why performance work that begins with "optimize the slow endpoint" so often stalls. The slow endpoint is usually an invoice for decisions made across product, platform, data, network, storage, and dependency boundaries.

The production pattern

A familiar pattern: an important workflow becomes slower over time. Nobody intentionally made it slow. A little validation was added. A downstream lookup became synchronous. A convenience call joined the hot path. A feature flag did an extra read. A personalization step waited on data that used to be best effort. Each change was locally reasonable.

Then a senior engineer asks for a latency target, and the answer is a single number. That number is not enough. A request path has stages, and stages have owners. Without allocation, every owner assumes somebody else will absorb the cost.

The result is latency inflation. It looks like technical drift, but it is actually a budgeting failure.

The model

I use a five-part latency budget review.

First, define the user-visible promise. Not every path needs the same promise. Interactive reads, writes with irreversible side effects, background reconciliation, and analytical exports all deserve different expectations. If the user cannot perceive the wait, do not spend principal-engineering energy pretending it is a page load.

Second, draw the critical path as a timing ledger. The ledger should include caller work, gateway time, service execution, database calls, cross-region hops, third-party dependencies, queue handoffs, retries, and serialization. Do not hide "small" waits. Small waits become the tax nobody accounts for.

Third, assign owners to budget slices. A slice without an owner is not a budget. It is a hope. The owner does not need to own every line of code in the slice, but they must own the tradeoff: reduce work, cache, parallelize, degrade, move async, or renegotiate the user promise.

Fourth, separate hard latency from optional latency. Some work must finish before the response is correct. Some work only makes the response richer. Some work is operational convenience disguised as product value. Mark these explicitly: required, deferrable, best effort, or removable.

Fifth, define enforcement. A budget without measurement is decorative. The minimum useful enforcement is a dashboard that shows budget spend by stage, deploy markers, and regressions by owner. Better enforcement includes review gates for new synchronous dependencies and load tests for the highest-risk paths.

Where this goes wrong

The counterpoint is that budgets can become bureaucracy. If the system is small, traffic is modest, or the workflow is still being discovered, a heavy budgeting process may slow learning more than it protects users. Early in a product, measuring the wrong path precisely is worse than accepting rough latency and learning from usage.

Budgets also fail when they become punishment. If every regression turns into blame, teams learn to hide costs inside shared layers or classify work as outside the critical path. The principal-engineer job is to make the budget a decision tool, not a courtroom.

Another failure mode is optimizing the median while the business problem lives in the tail. A path can feel good for most users and still be unacceptable for users with larger accounts, slower networks, colder caches, or more complex permissions. The budget has to match the promise, not the dashboard that makes the team feel better.

What I do now

When I review a performance-sensitive design, I ask for a budget table before I ask for implementation details. The table does not need false precision. It needs enough allocation to expose conflicts.

I look for synchronous calls that do not protect correctness. I ask which data can be stale. I ask where retries happen and whether they compound tail latency. I ask whether a dependency owner knows they are on the critical path. I ask what the response does when enrichment fails. I ask whether the proposed cache moves risk from latency into correctness, freshness, or operational complexity.

Most importantly, I make teams state what they will not do in the request path. Performance improves when the system has a spine. Without a spine, every useful idea becomes another blocking step.

Closing takeaway

A latency target becomes real only when it is decomposed into owned budgets, measured on the critical path, and defended against locally reasonable work that does not belong there.