Definition · Coordinated omissionCoordinated omission is the bias in which a measurement process suspends its own sampling during the events it exists to measure. In a closed-loop load generator, the next request is issued only after the previous reply, so a stall of duration S on a schedule of one request every I suppresses about S/I requests — and every one of them would have been slow.
The name says the important part: the omission is coordinated with the fault. Requests are not dropped at random from the sample, which would be harmless; they are dropped precisely when the system is behaving worst, which is where the percentile you are reporting lives.
Consider a generator configured to send one request every 10 ms with a healthy service time of 2 ms. It sends a hundred requests and during one of them the service stalls for a full second. The trace it produces is ninety-nine requests at 2 ms and one at 1 000 ms.
trace as recorded 99 × 2 ms 1 × 1 000 ms n = 100
naive p99 rank ⌈0.99×100⌉ = 99 of 100 → 2 ms
schedule during the stall: one request due every 10 ms
requests due inside 1 000 ms, not sent: 99
the k-th of them was due at k·10 ms into the stall and
waited until the stall cleared: 1 000 − 10k ms
k = 1 → 990 ms k = 50 → 500 ms k = 99 → 10 ms
corrected sample 99 × 2 ms + 990, 980, …, 10 + 1 × 1 000 ms
n = 199
corrected p99 rank ⌈0.99×199⌉ = 198 of 199 → 990 ms
990 ÷ 2 = 495
The naive p99 is 2 ms, which is the healthy service time. That is not a coincidence and it is the signature of the bug: with one slow request in a hundred, rank 99 never reaches it, so the reported 99th percentile is a measurement of the system when nothing was wrong. A report of “p99 = 2 ms” on a run that contained a one-second outage is arithmetically correct and completely uninformative.
The median moves too, and the direction is worth noticing: 2 ms naive against 10 ms corrected. A bias that is usually described as a tail problem reaches the middle of the distribution as soon as the stall is long relative to the schedule.
Theorem · What the correction assumesImputing the missing requests at their scheduled times is exactly right when arrivals are open: generated by a process indifferent to how long the system is taking. It is exactly wrong when arrivals are genuinely closed — a fixed population of clients that each wait for their own reply before acting again, such as a bounded thread pool or a human at a terminal.
In the closed case the requests really were not made, the users really did wait once, and the naive number is what happened. Applying the correction there double-counts. Deciding which model your workload follows is a modelling judgement the arithmetic cannot make for you, and it is the assumption this page rests on.
Why more threads is not the fix
The usual first reaction is to raise the generator’s concurrency: a hundred threads instead of one, so a stall blocks only one of them. It moves the number and it does not remove the bias. Each thread is still a closed loop, each still stops issuing during a stall, and a fault that affects the whole service stalls all of them at once — which is the case you are trying to measure. What removes the bias is generating requests on a clock rather than on replies, and recording each request’s latency from its scheduled start rather than from when a thread got round to sending it.
Example · Two generators, one service, one stallA clock-driven generator issues a request every 10 ms whether or not the last one returned, and timestamps each from its due time. Through the same one-second stall it records a hundred slow requests with waits from 1 000 ms down to 10 ms, and its p99 is 990 ms with no correction applied at all.
The closed-loop generator records one. The corrected sample above is an after-the-fact reconstruction of what the clock-driven generator would have seen, which is why it lands on the same number: the correction is not a heuristic, it is the missing half of the experiment.
The same bias outside load testing
A monitoring agent that scrapes its own metrics and blocks while the process is stalled omits exactly the samples that matter. So does a client library that records latency only for requests that completed and drops the ones the user abandoned, and so does any dashboard whose denominator quietly excludes timeouts. In each case the sample is missing the events the measurement exists to detect, and in each case the reported number is a true statement about a sample nobody wanted.
The check is one question: could this measurement have been prevented from being taken by the very fault it is supposed to record? If yes, the number is biased in a direction that flatters, and the size of the bias is what the lab computes.