The lab’s preset is a hundred measured calls: forty-five at 8 ms, twenty-five at 11 ms, fifteen at 16 ms, eight at 24 ms, four at 45 ms, two at 90 ms and one at 400 ms. Its median is 11 ms, its p95 is 45 ms, its p99 is 90 ms and its mean is 18.27 ms. The budget has given this call 250 ms.
Definition · Timeout and retry budgetFor a timeout T and a retry count r, the worst-case latency of the call is (r + 1) × T: every attempt may run to the timeout before the next begins. The kill fraction is 1 − F(T), where F is the measured distribution of call latency — the proportion of calls that would have succeeded and are abandoned. The call fails entirely with probability (1 − F(T))ʳ⁺¹ if attempts are independent.
sample 8:45 11:25 16:15 24:8 45:4 90:2 400:1 (100 calls)
p50 = 11 p95 = 45 p99 = 90 mean = 18.27
T worst case worst case killed all lost
(r = 1) (r = 2) 1 − F(T) (r = 1)
18 36 ms 54 ms 15.00% 2.25%
45 90 ms 135 ms 3.00% 0.09%
90 180 ms 270 ms 1.00% 0.01%
125 250 ms 375 ms 1.00% 0.01%
400 800 ms 1200 ms 0.00% 0.00%
budget for this call: 250 ms
Read the 250 ms budget across that table and the feasible choices appear immediately. T = 90 ms with one retry costs 180 ms in the worst case, kills 1% of good calls and loses the call entirely once in ten thousand. T = 90 ms with two retries needs 270 ms and does not fit. T = 125 ms with one retry uses the whole 250 ms exactly and kills the same 1%, because there is nothing in the distribution between 90 ms and 400 ms for the extra 35 ms to rescue.
The mean is the worst place to put a timeout
It is a common instinct: the calls take 18.27 ms on average, so anything much past that has clearly gone wrong. On this distribution that setting kills 15% of good calls. Fifteen in every hundred requests that were going to return a correct answer are turned into errors, retried, counted as failures, and — if the retry goes to the same overloaded place — used to generate more load.
The reason is the shape from “Percentiles from a Sample”. The mean of a latency distribution sits well above the median and well below the tail, in the sparse region between the bulk and the outliers. A timeout there is close enough to the bulk to catch a great deal of ordinary variation and far enough from the tail to be no protection against the calls that are genuinely stuck. It is the worst of both, and it is the most common setting there is.
Example · What the retry actually buys, and what it costsAt T = 90 ms a single attempt is abandoned 1% of the time. With one retry, and assuming the two attempts are independent, both are abandoned 1/100 × 1/100 = 1/10 000 of the time — the call now fails once in ten thousand instead of once in a hundred. The cost is that the worst case doubles from 90 ms to 180 ms, and the budget has to hold the worst case rather than the typical one. Two decimal places of reliability for a doubling of the reservation is usually a good trade; a third retry, at 270 ms, is one this budget cannot make.
The independence assumption in that example deserves the same scepticism it got in “Hedged Requests”. If the call timed out because the far side is overloaded, the retry is a second request to an overloaded service and its probability of timing out is not 1/100; it is close to 1. Retries against a saturated dependency are not a reliability mechanism, they are a load multiplier, which is why the retry count is capped and why the cap matters more than its value.
Two figures should therefore be quoted together whenever a timeout is proposed: the worst case (r + 1) × T against the allocation, and the kill fraction 1 − F(T) against the measured distribution. A timeout chosen without the second is a number chosen without knowing what it throws away, and on this sample the difference between the p99 and the mean is a factor of fifteen in exactly that quantity.