Definition · Histogram, and the percentile read from oneA histogram over boundaries e₀ < e₁ < … < e_k stores, for each bucket [eᵢ, eᵢ₊₁), the count of observations falling in it, plus an underflow count below e₀ and an overflow count at or above e_k. The bucketed q-th percentile is the bucket containing rank ⌈q·n⌉ in the cumulative counts. Its value is that bucket, not a number inside it.
Boundaries are chosen once, before any data arrives, and they are chosen geometrically because latency spans orders of magnitude: a millisecond and a second belong in the same picture, and equal-width buckets that resolve the first give thousands of empty ones before the second. The lab’s preset starts at 25 ms and doubles.
edges 25, 50, 100, 200, 400, 800, 1 600 ms base 25, ratio 2
sample 30×40 45×25 60×15 85×10 120×6 137×3 400×1
n = 100
bucket count cumulative
[25, 50) 65 65 30×40 and 45×25
[50, 100) 25 90 60×15 and 85×10
[100, 200) 9 99 120×6 and 137×3
[200, 400) 0 99
[400, 800) 1 100
p99 rank ⌈0.99×100⌉ = 99 first reached in [100, 200)
The exact p99 of that sample is 137 ms: sort the hundred values and take the 99th. The histogram cannot say 137. It can say that the 99th value is at least 100 and less than 200, which is true, useful, and a hundred milliseconds wide.
Theorem · The relative width of a geometric bucketLet the boundaries be eᵢ = b·rⁱ with r > 1. A value reported as lying in [eᵢ, eᵢ₊₁) is known to within an absolute width eᵢ₊₁ − eᵢ = eᵢ(r − 1), so relative to the smallest value it could be, the width is
(eᵢ₊₁ − eᵢ)/eᵢ = r − 1,
independent of i. At r = 2 every bucketed answer is known to within a factor of two — 100% — and at r = 5/4 to within 25%. The price of the smaller r is buckets: covering the same range needs ln 2 ÷ ln(5/4) ≈ 3.1 times as many.
That factor of 3.1 is not free, and the bill arrives in a different lesson. Every bucket is a time series with its own label value, so tripling the buckets triples the series count and the storage, which is the arithmetic of “Metric Cardinality”. Resolution in a histogram is bought with cardinality, and the exchange rate is known in advance.
Example · Two histograms of the same requestsOn the sample above with r = 2, the p99 is [100, 200) — a width of 100 ms around a true value of 137. Refine to r = 5/4 starting at the same 25 ms and the boundaries near the answer become …, 119.21, 149.01, …; the same rank now lands in [119.21, 149.01), a width of about 30 ms. The true value did not move and no new requests were collected. The report got narrower because the boundaries did.
Interpolating inside the bucket
It is tempting, and most metrics systems do it: assume the observations are spread evenly across the bucket and read off a point. On the sample above, linear interpolation inside [100, 200) returns a confident 200 ms — the top of the bucket, since rank 99 is the last of the nine observations in it — against a true 137. The assumption of uniformity is wrong in the direction that matters, because latency inside a bucket is not uniform; it is concentrated near the bottom, which is why the bucket boundaries were geometric in the first place.
Interpolation does not add information. It converts an honest interval into a point whose error you can no longer see, and the point is not even guaranteed to be inside the range of the data. Report the interval; if it is too wide to act on, change the boundaries rather than the arithmetic.
What the histogram is still good for
Everything the previous page needed. Counts add, so histograms from many hosts merge exactly, and a percentile read from the merged histogram is a real fleet percentile with a known interval around it. That is a strictly better object than an averaged per-host percentile, which has no interval around it because it is not an estimate of anything.
It is also the right place to be honest about what a dashboard can promise. A p99 of “about 150 ms” from r = 2 buckets means somewhere in [100, 200); a regression from 137 ms to 195 ms would not move the reported bucket at all, and a regression from 199 to 201 would move it a whole bucket. Alerting on a bucketed percentile inherits both behaviours, and choosing boundaries so that the values you care about are not near an edge is part of the design.