Skip to content
intermediate Phase 6 · Distributed Systems Fundamentals

Quantifying Latency and Bandwidth

Estimate round-trip costs to justify co-location and batching decisions.

45m
0 problems
Topic Progress 0%

From Physics to Deadline: Computing a Request's Network Budget

A product manager writes "the page must load in 500 ms" on the ticket. Someone replies "the API is fast, it's like a millisecond" — and the deploy misses the target by 300 ms. The gap is arithmetic nobody performed. A same-datacenter round trip costs hundreds of microseconds while an ocean crossing costs ~150 ms, and the design's job is to turn a user-facing number into a per-hop deadline before the code is written.

Start with the reference numbers. Latency scales over eight orders of magnitude, and the only useful question is always "which order of magnitude is this operation?"

Operation Order of magnitude
Memory access, cache hit ~100 ns
Same-host IPC (loopback) ~50–100 µs round trip
Same-datacenter RPC (one hop) ~200–500 µs round trip
SSD random read ~100 µs
Cross-AZ round trip ~0.5–2 ms
Cross-continent round trip ~35–45 ms (US domestic)
Ocean crossing round trip ~70–150 ms

Now the worked budget. The 500 ms page includes the browser: DNS, TLS, HTML, the API call chain, render. Allocate before designing:

user budget            500 ms
browser overhead       100 ms  (DNS 20–40 + TLS 1–2 RTT + HTML parse/first byte)
usable for APIs        400 ms
  API gateway          40 ms   (entry, auth)
  service hop 1        120 ms  (its own downstreams live inside this)
  service hop 2        120 ms
  serialization/render 120 ms
rule: every downstream's deadline < caller's remaining budget — strictly

Two disciplines make the table real. The deadline shrinks hop by hop (covered as policy earlier — here it is simply math: a hop given more than its share can spend time the user no longer has). And the budget is a p99 statement: planning on the mean guarantees missing the target for one request in twenty, which on a busy site is thousands of disappointed users an hour.

The counter-move to a big number is not "buy faster hardware" — it is to reduce round trips, because each RTT is paid serially when the next call depends on the previous answer. A 140 ms transpacific floor times three sequential queries is 420 ms of pure waiting, and no amount of server CPU helps. The three levers, in the order they are cheap: batch (one query returning three rows instead of three queries: 3 RTT → 1), fan out (independent calls in parallel: sum → max), and co-locate (move compute to the data, or cache the data near the compute — a cached read in-region at 300 µs versus an ocean crossing at 150 ms is a 500,000× difference, which is why CDN and read-replica placement decisions dominate everything else in the latency conversation).

Interview framing: when asked to "make this faster," resist the instinct to profile loops first. Ask what the end-to-end budget is, count the sequential round trips against it, and show where the time is structurally going. Candidates who can do this estimate live — "three sequential cross-AZ calls, that's your 6 ms floor right there" — demonstrate the quantitative reflex architects are hired for, and they are ready for the harder question: what happens when there are ten hops, not three.

Ten Hops, One User: Where the Waterfall Actually Goes

A checkout request fans out through an API gateway → BFF → checkout → pricing → promotions → risk → loyalty → inventory → payments → notifications. Ten sequential network hops. Each one individually "takes a millisecond." The p99 for the whole path is 600 ms and nobody can find the bug, because there is no bug — there is a waterfall.

Ten sequential network hops turn a small delay into noticeable user-facing latency. Do the arithmetic with the reference table: at 300 µs per same-datacenter hop, ten hops is only 3 ms — fine. But real chains mix distances: two cross-AZ calls (2 ms each), one call to an external fraud API at 60 ms, and the rest in-AZ. Waterfalls in trace form:

gateway        |--|
bff              |--|
checkout            |--------|        (waits on all below)
  pricing              |---|
  promotions           |---|  parallel (good)
  risk                   |--|--|      (cross-AZ: 2 ms)
  loyalty                  |--|       (40 ms external, parallel)
  inventory              |----|
payments                        |-----|  (60 ms external, MUST be last)
                        total ≈ 40 + 3 + 2 + 40 + 60 + ... ≈ 155 ms of transport+externals

Three corrections, in decreasing order of payoff:

1. Flatten the depth, widen the width. Parallel fan-out converts additive latency into the maximum: pricing + promotions + risk together cost the slowest (2 ms), not 4 ms. The rule — any call whose result does not gate another call runs concurrently — usually removes half the waterfall without moving a single deployment.

2. Cut a layer. The BFF and checkout service do not both need to orchestrate; a forwarding layer that adds a hop plus a serialization is a real 300–600 µs and a second failure mode. Every layer you delete is a round trip returned to the budget — the same "forwarding is not a responsibility" lesson from Layered Architecture, now payable in milliseconds.

3. Move what cannot be parallelized. Two sequential external calls (loyalty then payments, because payments needs the quote) are the hardest 100 ms in the path. Options, in order: ask whether the first call's full result is really needed (often a cached loyalty tier suffices — stale by an hour, nobody cares), then whether payments can quote asynchronously before the user hits "Place order" (many checkout flows pre-authorize), and only then accept the cost and spend it explicitly in the deadline table.

What makes this debuggable in production rather than in your head: trace every request and record per-hop p99 contribution. The first time you plot it, one hop is usually 40–60% of the path — the fraud API, the unbatched query, the missing cache — and it becomes the single highest-leverage change in the system. Alert on budget consumed (trace total vs the 500 ms allocation) rather than per-service latency, because a service can be individually fast and still be structurally in the wrong place.

The interview version of this skill: given a system description, ask three questions — how many sequential round trips, what distances are they, which of them have an answer dependency — then name the reshuffle. That is a complete answer to "how would you reduce latency here?" and it needs no profiler.

Payload Budgets: Why a 10 Gbps Link Does Not Save Fat JSON

The dashboard requests "all orders for the last 30 days" and receives a 42 MB JSON document — 118 MB on the wire after the API's pretty-printing, uncompressed. On a 10 Gbps link that sounds like nothing: 95 ms. On the user's 50 Mbps connection it is 19 seconds, and the browser's main thread spends 400 ms parsing it. The link was never the bottleneck the team thought it was. Bandwidth limits shape payload budgets more than raw link speed suggests, because the budget is set by the narrowest shared segment and by bytes that someone must serialize, transmit, and parse.

Do the conversion first — bandwidth is a rate, size is a size:

time = (size_in_bits + overhead) / effective_throughput

42 MB on 10 Gbps datacenter link   ≈  34 ms  (before TCP slow start, shares, parsing)
42 MB on 50 Mbps home link         ≈   6.7 s
1 MB  on 50 Mbps                   ≈ 160 ms
200 KB on 50 Mbps                  ≈  32 ms   ← the realistic budget for one response

Four multipliers hide in that block. TCP slow start means the first megabyte of a fresh connection moves far slower than the link rate — large one-shot responses on new connections underperform the arithmetic, which is why keep-alive and HTTP/2 multiplexing matter as much as compression. The link is shared: the 10 Gbps figure is split with every other flow in the account, and the true ceiling is often a NAT gateway, an LB's connection table, or a path-MTU quirk. The wire multiplies JSON: a pretty-printed JSON document can be 3–5× the size of the equivalent binary encoding and costs 3–10× the parse CPU. And bytes are paid at both ends — the server that serialized 42 MB spent CPU it could have spent serving other requests.

The design moves, each with its arithmetic:

Move Effect Caveat
Pagination / limits (?limit=200) Response size bounded independent of data volume Choose a cursor, not offsets, at depth
Field selection (?fields=) Clients pay for what they render Contract tests: default stays additive
Compression (gzip/brotli, level 4–6) 5–10× on text; ~1–3 ms CPU per MB Threshold ~1 KB; skip for already-compressed binaries
Binary encoding (Protobuf/MessagePack) 2–4× smaller than JSON, faster parse Loses curl-debuggability at the edge
Pre-aggregation Dashboard fetches the rollup, not 900k rows Freshness becomes a stated SLO
CDN/edge caching Bytes served near the user, origin untouched Cache key and invalidation discipline required

The decision heuristic to state in review: compute a payload's transfer time on the user's connection and its parse cost on the weakest device you support, then compare against that endpoint's slice of the latency budget. If the response does not fit, the endpoint is broken — regardless of how fast the datacenter link reads on the invoice. This same arithmetic is what justifies GraphQL-style field selection, why list endpoints carry default limits in every mature API, and why "just add a field to the response" is a performance review, not a one-line change. You now have both halves of the quantification habit: round trips set the floor, payload budgets set the ceiling — and the next topic adds what happens when the components themselves start failing.

Quiz

1. A page's budget is 500 ms and the browser overhead is 100 ms. Your API path has three sequential service hops. What is the correct deadline discipline?

Question 1 options

2. A chain does pricing, promotions, and risk — none needs another's result — followed by payments, which needs pricing. Total transport today: 40 ms serial. Best structural fix?

Question 2 options

3. A 42 MB response is fine on the 10 Gbps datacenter link. What is the correct analysis?

Question 3 options

4. Where does TCP slow start show up in these numbers?

Question 4 options

Flashcards

Question

The RTT ladder you should be able to recite, from memory access to ocean crossing.

Answer

~100 ns memory · 50–100 µs loopback · 200–500 µs same-datacenter RPC · 0.5–2 ms cross-AZ · 35–45 ms US coast-to-coast · 70–150 ms ocean crossing. Eight orders of magnitude — every latency bug is a question of which rung an operation sits on.

Question

How do you convert a user-facing SLO into per-hop deadlines?

Answer

Subtract browser/DNS/TLS/render overhead first, then allocate the remainder across the API path with each downstream's deadline strictly smaller than the caller's remaining time (e.g., 500 → 400 usable → gateway 40, hops 120/120, serialize 120), and state it as a p99 budget — the mean is not a promise.

Question

What are the three latency levers, in order of cheapness, and what does each convert?

Answer

Batch (N round trips → 1: one query, three rows), fan out (sum → max: independent calls concurrently), co-locate/cache (compute near data or data near compute: 150 ms remote → 300 µs in-region). Each attacks round-trip count, which dominates server CPU in distributed paths.

Question

Why does a 10 Gbps link not rescue a 42 MB JSON response?

Answer

The budget is the narrowest shared segment (user's 50 Mbps ≈ 6.7 s), plus TCP slow start under-delivers on a fresh connection, JSON's wire/parse overhead multiplies (3–5× vs binary, 3–10× parse CPU), and the link is shared with everything else in the account. Fix: limits, field selection, compression (threshold ~1 KB), aggregation, CDN.

Question

What is the correct alert for latency SLOs — per-service latency or budget consumed?

Answer

Budget consumed: total trace time against the end-to-end allocation (and per-hop contribution within it). A service can be individually fast and still sit structurally in the wrong place; alerting on the budget consumed per request catches the waterfall that per-service dashboards average away.

Revision Notes

Key Takeaways

  • 1. Latency spans eight orders of magnitude — the design skill is placing every operation on that ladder and refusing to leave the budget in prose
  • 2. Round trips are the currency: batch, fan out, and co-locate before optimizing CPU, because sequential hops sum and parallel hops take the max
  • 3. Deadlines are allocations with strict shrinkage hop by hop, stated at p99 — a mean-based budget is a promise you break one request in twenty
  • 4. Bandwidth is a rate at the narrowest shared segment; payload budgets follow from the client's link, parse cost, and slow start — not the link invoice
  • 5. Measure with traces: per-hop contribution finds the structural hotspot, and budget-consumed alerting catches waterfalls that per-service metrics average away

Interview Tips

  • • 'How would you make this endpoint faster?' — ask for the budget, count sequential round trips, name distances, then give the reshuffle (batch/fan-out/co-locate); profiler talk comes after the waterfall
  • • 'Give a back-of-envelope latency estimate' — recite the ladder (µs in-DC, 1–2 ms cross-AZ, ~150 ms ocean), then multiply by hops; live arithmetic is what separates architects from describers
  • • 'Our page takes 800 ms — where do you look?' — trace first, plot per-hop p99 contribution, find the hop eating 40%+; then check whether it is distance, serialization, or an unbatched query
  • • '42 MB response — is that OK?' — compute transfer on the user's link and parse on the weakest device; if it exceeds the endpoint's slice of the budget, the endpoint is broken regardless of the datacenter link

Cheat Sheet

Latency & Bandwidth, Quantified

RTT ladder: memory 100 ns · loopback 50–100 µs · same-DC RPC 200–500 µs · cross-AZ 0.5–2 ms · US transcon 35–45 ms · ocean 70–150 ms.

Budget derivation (500 ms example):

500 total − 100 browser (DNS/TLS/parse) = 400 for APIs
gateway 40 · hop1 120 · hop2 120 · render/serialize 120
rule: downstream deadline STRICTLY < caller's remaining time · state as p99

Chain math: serial hops SUM (10 hops × mixed distances = waterfall); independent hops → parallel (sum → max); external sequential calls = hardest ms → cache-stale / pre-authorize / accept explicitly. Cut forwarding layers: each deleted hop = one RTT back.

Payload math:

time = bits / effective throughput
42 MB @ 10 Gbps ≈ 34 ms · @ 50 Mbps ≈ 6.7 s (and slow start under-runs it)
200 KB @ 50 Mbps ≈ 32 ms   ← realistic one-response budget

Payload levers: limit/cursor pagination · field selection · gzip/brotli (threshold ~1 KB, 5–10× on text) · binary encoding (2–4×) · pre-aggregation · CDN. Check against the client's link and weakest device, not the datacenter invoice.

Observe: trace per-hop p99 contribution; alert on budget consumed, not per-service latency.

Continue Learning