Definition · Meeting a percentile in a runLet a request be in the slowest t fraction of the distribution with probability t, independently across requests. A run of n requests meets that tail if at least one of them falls in it, which happens with probability
1 − (1 − t)ⁿ.
For the p99.9, t = 1/1000; for the p99, t = 1/100; for the p99.99, t = 1/10000. Independence is the assumption, and it fails for a run that ramps, for a cache that is warming and for anything periodic.
t = 1/1000
n = 600 P = 1 − (999/1000)⁶⁰⁰ = 45.14%
n = 693 P = 1 − (999/1000)⁶⁹³ = 50.01% first past even
n = 2 995 P = 1 − (999/1000)²⁹⁹⁵ = 95.00% first past 95%
expected number of top-0.1% requests in 600: 600 × 1/1000 = 0.6
A six-hundred-request run is a minute at ten requests a second, which is a completely ordinary thing to do, and it produces a report with a p99.9 in it. More than half the time that number was computed from a sample containing no request from the top 0.1% at all: rank ⌈0.999 × 600⌉ = 600 returns the maximum, and the maximum of six hundred draws from the body of the distribution is not the p99.9 of anything.
Why the lab scans instead of taking a logarithm
Solving (1 − t)ⁿ ≤ 1 − c for n is one logarithm, and the answer it gives here is n ≥ 2 994.23, so 2 995. The lab does not do that. It computes (999/1000)ⁿ as an exact fraction and compares it to 1/20 directly, increasing n until the comparison flips.
The reason is that the exact answer is a rational number a double cannot hold: (999/1000)²⁹⁹⁵ has a numerator of about nine thousand digits, and converting it to a floating-point number before comparing gives infinity divided by infinity. A scan over exact fractions has no such failure mode, and it also makes the boundary checkable — the lab can show that n = 2 994 does not clear the bar and n = 2 995 does, which a logarithm and a ceiling can only assert.
Example · Three reports of the same serviceA one-minute run at 10 requests a second: 600 requests, p99.9 reported, and a 45.14% chance the run ever met the tail. A five-minute run: 3 000 requests, 95.03% — the number is now probably about something real, estimated from about three observations. A one-hour run: 36 000 requests, certainty of meeting the tail and roughly thirty-six samples above it, which is enough for the reported value to be stable between runs.
The three reports look identical. They differ by the length of the run, which is the one field nobody reads, and the first of the three is where most load-testing conclusions come from.
From meeting a tail to measuring it
The expected number of requests above the q-th percentile in a run of n is n·t. One expected sample is the threshold at which the percentile stops being the maximum; it is nowhere near the threshold at which it is stable. As a working rule, ask for ten samples in the tail — n = 10/t, so 10 000 requests for a p99.9 and 100 000 for a p99.99 — and treat anything less as a lower bound with a wide error around it.
This is also the honest answer to “why did the p99.9 change between runs?” With n·t near one, it changed because it is the maximum of a random sample and maxima are volatile. Nothing regressed, nothing improved, and the difference between the two runs is the same kind of noise the first page of this course put an error bar on — except that a percentile deep in the tail has a far wider one than a ratio near the middle.
The assumption the arithmetic rests on
1 − (1 − t)ⁿ assumes every request is an independent draw from one fixed distribution. A run that ramps load over its first two minutes is drawing from a different distribution every second; a run against a cold cache is measuring the cache filling; a run short enough to sit inside one garbage-collection cycle may draw zero or all of its tail from that one pause. In each case the request count is right and the independence is not, and the practical response is to discard the ramp, warm the system first, and run for several periods of whatever the system does periodically.