Queueing and Latency §
Queueing and latency models how load translates into response time, when a system saturates, and how to protect against overload. It is relevant whenever a system’s throughput, latency, or tail-latency SLO must be predicted or guaranteed under variable demand.
Authoritative sources
Queueing theory §
A queue forms whenever work arrives faster than it is served, even temporarily. Little’s law: where is the average number of items in the system, is the arrival rate, and is the average time in the system.
M/M/1 queue (Poisson arrivals, exponential service times, 1 server): the utilization is . The average time in the system is . The average waiting time in the queue (excluding service) is . As , both and diverge to .
Details
Little’s law in practice: if a server handles 1000 requests/second with an average response time of 10 ms, the average number of in-flight requests is : the server only needs 10 concurrent request slots, no matter how fast individual requests are. Little’s law is remarkably general — it holds for any stable queue, regardless of the arrival or service distributions, which is why it is the first tool to reach for when sizing connection pools, thread pools, or worker counts.
Why you must scale before saturation: as , . This is the mathematical reason you must add capacity before utilization reaches a critical threshold, typically 70–80%. At , (5× the service time); at , (20× the service time). The growth is non-linear — the curve below shows (in units of service time) against :
Waiting until 95% utilization already means users experience 20× the baseline latency. When this happens, the fix is to add capacity — vertically, horizontally, or automatically. The concrete techniques (bottleneck analysis, vertical vs horizontal scaling, load balancers, caching, sharding, cost models, and autoscaling) are covered in Scaling Strategies.
Where latency comes from: the end-to-end response time a user sees is the sum of network transit time (propagation, transmission, and congestion delays in Networks), queueing delay at each hop ( above), and service time (). Queueing theory only models the last two; a 200 ms round trip across an ocean cannot be improved by adding servers, only by moving content closer (CDNs) or reducing round trips.
When M/M/1 is a good model — and when it breaks: M/M/1 assumes Poisson arrivals and exponential service times. It is a good first-order model for stateless request handling with roughly uniform service times, and it correctly captures the qualitative non-linear blow-up near saturation. It breaks down when arrivals are bursty (diurnal patterns, flash crowds — bursts see much worse latency than the average suggests) or when service times are heavy-tailed (database queries, GC pauses — the mean then understates the tail). In those cases use M/G/1 (arbitrary service distribution, where the Pollaczek–Khinchine formula adds a variance term to ), or measure and simulate, as the simulation below does.
Mini-example: a disk serves 80 I/O operations/second with a mean service time of 10 ms (/s), and requests arrive at /s. Then and ms: each operation spends on average 30 ms waiting and 10 ms being served. If the disk is upgraded to /s, ms — doubling the speed halved the latency, because dropped to 0.6.
Mini-problem
A web cache answers in 2 ms of service time (/s) and receives 450 requests/second. (a) What is , , and ? (b) A marketing campaign doubles the traffic. What happens?
Solution
(a) . ms — ten times the service time, even though the server is only at 90% utilization. By Little’s law, requests in the system.
(b) At , the queue grows without bound: the system is unstable. This is why capacity planning targets – rather than 1.0 — it leaves headroom for bursts and traffic growth.
Tail latency §
Tail latency (p99, p999) is the response-time threshold that 99% (resp. 99.9%) of requests beat. Averages hide the tail: a system with 99% of requests at 10 ms and 1% at 500 ms has an average of 14 ms but a p99 of 500 ms.
Fan-out amplification: when a request makes sequential sub-calls, the total latency is the sum of the sub-call latencies. By the union bound, the sum of the per-call -quantiles is an upper bound on the -quantile of the total. To bound the p99 of a request that makes 10 sequential calls, sum the per-call p99.9 values — not the per-call p99s, which only bound the p90 of the total. A system with 10 sequential calls at p99.9 = 15 ms each has a p99 of at most 150 ms.
Details
Why averages hide the tail: the mean is dominated by the bulk of requests. A single slow request (a GC pause, a disk seek, a network retransmission) inflates the p99 without visibly moving the mean. SLOs are therefore stated in percentiles — “99% of requests complete in under 200 ms” — because the mean says nothing about the worst user experience, and in a service with millions of users even the p99.999 describes thousands of real requests per day.
The union-bound argument: let be the sub-call latencies. For any , by the union bound:
where is the -quantile of . Hence is an upper bound on the -quantile of the sum. The converse trap: summing per-call p99s (each with ) gives only — for that bounds the p90 of the total, not the p99. Each extra “nine” of end-to-end guarantee pushes the required per-call quantile further out, which is why tail SLOs get expensive fast as fan-out grows.
Tail latency and saturation: tail latency is the primary reason to scale before the mean suggests trouble. In an M/M/1 queue the total time in system is exponentially distributed with rate , so and the p99 of the response time is:
At , : the p99 response time is about 92× the service time, while the mean is only 20×. The tail degrades roughly five times faster than the mean as the server saturates — percentiles are the sensitive overload detector.
Mitigations: hedging (send a duplicate request if the first is slower than the p95, and keep the faster reply — cheap when the tail is rare), prioritized scheduling of short requests, and per-request deadlines with early abandonment. These are described in “The Tail at Scale” (authoritative source above).
Mini-example: an API gateway calls 3 services in parallel and returns when all respond. Each service has p99 = 30 ms and p999 = 80 ms. The response time is the max of the three. By the union bound, : the p99.7 of the gateway response is at most 80 ms. To guarantee a p99.9, each service would need p99.967 ≈ p99.97.
Mini-problem
A page load fires 20 parallel requests to independent backends, each with p99 = 100 ms. At least how many of the 20 requests can you expect to exceed 100 ms, and what does that do to the page load time?
Solution
By the union bound, : up to 20% of page loads have at least one straggler. Since the page completes only when the slowest request does, roughly 1 in 5 page loads is gated by a >100 ms request even though every backend individually meets its p99. This is the fan-out amplification that motivates hedging and tail-tolerant design (“The Tail at Scale”).
Backpressure and rate limiting §
Backpressure is the mechanism by which a downstream component signals an upstream one to slow down or stop sending work. Rate limiting caps the rate at which a system accepts new requests, protecting it from overload.
Details
Token bucket: a bucket holds at most tokens; tokens are added at a constant rate (e.g. 100 tokens/second). Each request consumes one token; a request that finds the bucket empty is rejected or queued. This allows short bursts of up to requests while enforcing the long-term rate — exactly the knob you need to keep below the that queueing theory says you must not exceed.
Leaky bucket: requests enter a fixed-size buffer that drains at a constant rate; a request that arrives when the buffer is full is dropped. Unlike the token bucket, the output rate is constant regardless of the input pattern — useful when the downstream (a database, a third-party API) can only absorb a fixed rate. The cost is added latency: requests wait in the buffer, so the leaky bucket is itself a queue with .
Load shedding: when the system is at capacity, it deliberately rejects low-priority work to protect high-priority work: drop analytics events before transactions, serve stale cached responses instead of hitting the database, or return HTTP 429 (Too Many Requests) with a Retry-After header. Well-behaved clients back off exponentially with jitter, avoiding the thundering-herd retry storm that would otherwise re-create the burst.
Interaction with queueing: backpressure is the practical implementation of the queueing-theory insight that as . Rather than letting the queue grow without bound (degrading latency for everyone, including the requests you could have served), the system rejects the excess, keeping below the saturation threshold and latency low for accepted requests. A bounded queue plus rejection is strictly better than an unbounded queue under overload.
Where it applies: at the edge (reverse proxy, API gateway), between tiers (a fixed-size database connection pool is a leaky bucket), and inside a service (bounded worker-pool queues). At the network level, TCP congestion control is backpressure in its purest form — the receiver’s window and dropped packets tell the sender to slow down (see Networks).
Mini-problem
An API has a token bucket with tokens/second and . A client that has been idle sends a burst of 300 requests at once, then 60 requests/second forever. How many of the burst are accepted, and what happens to the steady stream?
Solution
The bucket holds tokens, so 200 of the 300 burst requests are accepted instantly and 100 are rejected (429). Afterwards, tokens arrive at /s but the client offers 60/s: only 50 requests/second pass in steady state, and 10 requests/second are rejected. The bucket converts an overload of 120% into a clean 100% with explicit rejection of the remainder — the client should back off or reduce its rate rather than retry all failures.
From saturation to capacity §
When measurement shows approaching the danger zone, the response is to add capacity. The decision tree — identify the bottleneck (IO-bound vs compute-bound), scale vertically or horizontally (load balancers, reverse proxies, caching, CDNs, sharding), automate with autoscaling (metric-driven scale-out with cooldowns, e.g. Kubernetes HPA or AWS Auto Scaling groups), and weigh the cost per request — is covered in Scaling Strategies. The simulation below shows the simplest such decision quantitatively: how response time falls as load is split across servers.
Simulation §
The simulation below is a discrete-event M/M/1 simulator. Arrivals are Poisson at rate ; each arrival is sent to one of servers chosen uniformly at random, so each server runs its own independent M/M/1 queue fed at rate (splitting a Poisson stream keeps each sub-stream Poisson, which is what makes the per-server M/M/1 formulas exact). Adjust the sliders to see the measured average time in system track the theoretical , and watch it diverge as — then increase to see horizontal scaling pull the system back from saturation.
Example problem §
Problem: A microservice makes 5 sequential calls to downstream dependencies. Each call has a p99 latency of 20 ms. What is the upper bound on the p99 of the total request? What if you want to bound the p99.9 instead?
Solution
Bounding the p99: to bound the p99 of the total (), each sub-call must be taken at its -quantile with , i.e. the -quantile = p99.8. If each call has p99.8 = 25 ms, the p99 of the total is at most ms.
If you only know the per-call p99s (20 ms each), their sum bounds the -quantile = p95 of the total — not the p99, which could be significantly higher.
Bounding the p99.9: now , so each sub-call must be taken at its -quantile = p99.98. If each call has p99.98 = 40 ms, the p99.9 of the total is at most ms.
Key insight: the tighter the end-to-end SLO (lower ), the further into each dependency’s tail you must look. A composite service needs each dependency to meet a stricter per-call SLO than the overall target — and if a dependency cannot meet it, the remedies are architectural: reduce the fan-out, parallelize the calls (sum becomes max), add hedging, or put the dependency behind a cache so most requests never pay its tail at all.