Module F-4·20 min read

p50 vs p99 vs p999, why averages hide outages, and Little's Law — the equation that tells you how many servers you actually need.

JJS
Written by Jatin Jain Saraf · Senior Software Engineer

Module F-4 — Latency, Throughput, and Tail Latency

What this module covers: The average response time is the most quoted and least useful number in production. This module replaces it: what percentiles actually mean, why a p99 that "only affects 1% of requests" affects a large fraction of users, how fan-out amplifies the tail until the rare becomes routine, the real sources of tail latency, why you can't average percentiles, Little's Law as the bridge from QPS to server count, and the queueing result that explains why a system at 90% utilisation has 10× the latency of the same system at 50%. Plus latency budgets — how to divide a 300ms promise across the hops that have to keep it.


The Average Is the Number That Hides the Outage

Consider two services. Both report a mean response time of 120ms.

Service AService B
p50115 ms40 ms
p90130 ms90 ms
p99150 ms2,400 ms
p99.9180 ms14,000 ms
Mean120 ms120 ms

Service A is healthy and predictable. Service B has a subset of requests taking fourteen seconds — timeouts, abandoned carts, retry storms, support tickets — and its dashboard says it's identical to A. The mean is not slightly misleading here; it's actively concealing the only thing worth knowing.

This happens because latency distributions are not symmetric. There's a hard floor (the work genuinely takes some minimum time) and no ceiling (a request can queue, retry, hit a GC pause, or wait on a lock for effectively unbounded time). The distribution is right-skewed with a long tail, and an average of a long-tailed distribution tells you almost nothing about its shape.

Analogy: the mean is a group photo where everyone's face is blended into one composite. It looks like a person. It tells you nothing about whether anyone in the group is having a bad day.

Why this matters in production: every incident of the form "the dashboards were green but customers were complaining" is this. Averages stay green while the tail burns, because the tail is by definition a small share of requests — and a small share of requests is a large share of users, for reasons the next section makes uncomfortable.


Percentiles, Stated Precisely

p99 = 250ms means: of all requests in the window, 99% completed in 250ms or less, and 1% took longer. It says nothing about how much longer — that's what p99.9 and the maximum are for.

Which percentiles to actually watch:

  • p50 (median) — the typical experience. Good for capacity trends, bad for user-visible quality.
  • p90/p95 — where degradation first becomes visible to real people.
  • p99 — the standard SLO target for user-facing APIs. Your unluckiest requests, but frequent enough to be a routine event at any real volume.
  • p99.9 — where infrastructure problems hide: GC pauses, failovers, single bad nodes, noisy neighbours.
  • max — usually one weird thing, but occasionally the early warning for a systemic problem. Never alert on it alone.

The trap: 1% of requests is not 1% of users. If loading one page issues 20 API calls, a user needs all twenty to be fast. With an independent 1% chance of each being slow:

text

Nearly one in five page loads hits the p99 path. Your "1% problem" is an 18% problem in the only unit that matters. And at higher fan-out it gets worse fast:

Backend calls per pageChance at least one is a p99 request
11%
1010%
2018%
10063%
50099.3%

At 100 calls per request — an entirely normal number for a microservices page assembly or a feed hydration — the p99 case is the majority case. This is the core argument of Dean and Barroso's "The Tail at Scale," and it's the reason tail latency is treated as an architectural concern rather than a tuning detail: at scale, you don't get to treat rare events as rare. Modules A-6, A-7, and A-8 are collectively the response to this table.


Where the Tail Actually Comes From

Tail latency is rarely "the code is slow." The usual causes, roughly in order of how often they turn out to be the answer:

  • Queueing. The dominant cause, and the subject of the utilisation section below. The request wasn't slow; it waited — for a thread, a connection from the pool, a database lock, a disk I/O slot.
  • Connection pool exhaustion. A request that waits 2 seconds for a free database connection has 2 seconds of latency with no slow query anywhere in the trace. Module P-4.
  • Garbage collection and runtime pauses. A stop-the-world pause freezes every in-flight request on that instance. This is a classic p99.9 signature: periodic, correlated, invisible in the mean.
  • Cold caches. A cache miss costs the full backend path. After a deploy or an eviction wave, the miss rate spikes and so does the tail — Module P-12.
  • Noisy neighbours. On shared infrastructure, someone else's workload steals CPU, disk, or network. You'll see it as unexplained variance you cannot reproduce.
  • Retries and timeouts. A client that retries after 1s turns one slow request into two, adding load precisely when the system is struggling. Module A-6.
  • Network-level events. A TCP retransmit adds hundreds of milliseconds; TLS renegotiation, DNS re-resolution, and connection re-establishment all appear as inexplicable single-request spikes (Modules F-5, F-6).
  • Skew in the data, not the system. The user with 40,000 followers, the tenant with 12 million rows, the order with 900 line items. Same code path, 100× the work. Module P-8's whale accounts.

Sign in to keep reading

The rest of this module is free — sign in with Google to unlock it and track your progress.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.