Technopedia

The technologist’s encyclopedia

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: L=λWL = \lambda W where LL is the average number of items in the system, λ\lambda is the arrival rate, and WW is the average time in the system.

M/M/1 queue (Poisson arrivals, exponential service times, 1 server): the utilization is ρ=λ/μ\rho = \lambda / \mu. The average time in the system is W=1/(μ−λ)W = 1 / (\mu - \lambda). The average waiting time in the queue (excluding service) is Wq=ρ/(μ−λ)=λ/(μ(μ−λ))W_q = \rho / (\mu - \lambda) = \lambda / (\mu(\mu - \lambda)). As ρ→1\rho \to 1, both WW and WqW_q diverge to ∞\infty.

arrivals rate λ FIFO queue Lq waiting server rate μ departures time in system W = Wq (queueing) + S (service)
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 L=1000×0.01=10L = 1000 \times 0.01 = 10: 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 ρ→1\rho \to 1, W→∞W \to \infty. This is the mathematical reason you must add capacity before utilization reaches a critical threshold, typically 70–80%. At ρ=0.8\rho = 0.8, W=5/μW = 5 / \mu (5× the service time); at ρ=0.95\rho = 0.95, W=20/μW = 20 / \mu (20× the service time). The growth is non-linear — the curve below shows WW (in units of service time) against ρ\rho:

utilization ρ W (× service time) 1 11 21 0 0.5 1 ρ = 0.8 → 5× ρ = 0.9 → 10×

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 (WqW_q above), and service time (SS). 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 λ\lambda suggests) or when service times are heavy-tailed (database queries, GC pauses — the mean 1/μ1/\mu then understates the tail). In those cases use M/G/1 (arbitrary service distribution, where the Pollaczek–Khinchine formula adds a variance term to WqW_q), 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 (μ=100\mu = 100/s), and requests arrive at λ=75\lambda = 75/s. Then ρ=0.75\rho = 0.75 and W=1/(100−75)=40W = 1/(100 - 75) = 40 ms: each operation spends on average 30 ms waiting and 10 ms being served. If the disk is upgraded to μ=125\mu = 125/s, W=1/50=20W = 1/50 = 20 ms — doubling the speed halved the latency, because ρ\rho dropped to 0.6.

Mini-problem

A web cache answers in 2 ms of service time (μ=500\mu = 500/s) and receives 450 requests/second. (a) What is ρ\rho, WW, and LL? (b) A marketing campaign doubles the traffic. What happens?

Solution

(a) ρ=450/500=0.9\rho = 450/500 = 0.9. W=1/(500−450)=20W = 1/(500 - 450) = 20 ms — ten times the service time, even though the server is only at 90% utilization. By Little’s law, L=450×0.02=9L = 450 \times 0.02 = 9 requests in the system.

(b) At λ=900>μ=500\lambda = 900 > \mu = 500, the queue grows without bound: the system is unstable. This is why capacity planning targets ρ≤0.7\rho \le 0.7–0.80.8 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 nn sequential sub-calls, the total latency is the sum of the sub-call latencies. By the union bound, the sum of the per-call (1−ε/n)(1 - \varepsilon/n)-quantiles is an upper bound on the (1−ε)(1 - \varepsilon)-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 X1,…,XnX_1, \ldots, X_n be the sub-call latencies. For any ε>0\varepsilon > 0, by the union bound:

P ⁣(∑i=1nXi>∑i=1nxi)≤∑i=1nP(Xi>xi)≤∑i=1nεn=εP\!\left(\sum_{i=1}^{n} X_i > \sum_{i=1}^{n} x_i\right) \leq \sum_{i=1}^{n} P(X_i > x_i) \leq \sum_{i=1}^{n} \frac{\varepsilon}{n} = \varepsilon

where xix_i is the (1−ε/n)(1 - \varepsilon/n)-quantile of XiX_i. Hence ∑ixi\sum_i x_i is an upper bound on the (1−ε)(1 - \varepsilon)-quantile of the sum. The converse trap: summing per-call p99s (each with P(Xi>xi)=0.01P(X_i > x_i) = 0.01) gives only P(total>∑xi)≤0.01nP(\text{total} > \sum x_i) \le 0.01n — for n=10n = 10 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 WW is exponentially distributed with rate μ(1−ρ)\mu(1 - \rho), so P(W>t)=e−μ(1−ρ)tP(W > t) = e^{-\mu(1-\rho)t} and the p99 of the response time is:

W99=ln⁡100μ(1−ρ)≈4.6μ(1−ρ)W_{99} = \frac{\ln 100}{\mu (1 - \rho)} \approx \frac{4.6}{\mu(1-\rho)}

At ρ=0.95\rho = 0.95, W99≈92/μW_{99} \approx 92/\mu: 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, P(max⁡iXi>80)≤3×0.001=0.003P(\max_i X_i > 80) \le 3 \times 0.001 = 0.003: 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, P(some request>100 ms)≤20×0.01=0.2P(\text{some request} > 100\text{ ms}) \le 20 \times 0.01 = 0.2: 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.

tokens drip in rate r capacity b 1 token / request requests in bursts up to b accepted 429 + Retry-After long-term rate ≤ r
Details

Token bucket: a bucket holds at most bb tokens; tokens are added at a constant rate rr (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 bb requests while enforcing the long-term rate rr — exactly the knob you need to keep λ\lambda below the μ\mu 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 Wq>0W_q > 0.

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 W→∞W \to \infty as ρ→1\rho \to 1. 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 ρ\rho 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 r=50r = 50 tokens/second and b=200b = 200. 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 b=200b = 200 tokens, so 200 of the 300 burst requests are accepted instantly and 100 are rejected (429). Afterwards, tokens arrive at r=50r = 50/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 ρ\rho 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 WW falls as load is split across NN servers.

Simulation

The simulation below is a discrete-event M/M/1 simulator. Arrivals are Poisson at rate λ\lambda; each arrival is sent to one of NN servers chosen uniformly at random, so each server runs its own independent M/M/1 queue fed at rate λ/N\lambda/N (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 W=1/(μ−λ/N)W = 1/(\mu - \lambda/N), and watch it diverge as ρ=λ/(Nμ)→1\rho = \lambda/(N\mu) \to 1 — then increase NN 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 (ε=0.01\varepsilon = 0.01), each sub-call must be taken at its (1−ε/n)(1 - \varepsilon/n)-quantile with n=5n = 5, i.e. the (1−0.002)(1 - 0.002)-quantile = p99.8. If each call has p99.8 = 25 ms, the p99 of the total is at most 5×25=1255 \times 25 = 125 ms.

If you only know the per-call p99s (20 ms each), their sum bounds the (1−0.01×5)(1 - 0.01 \times 5)-quantile = p95 of the total — not the p99, which could be significantly higher.

Bounding the p99.9: now ε=0.001\varepsilon = 0.001, so each sub-call must be taken at its (1−0.001/5)(1 - 0.001/5)-quantile = p99.98. If each call has p99.98 = 40 ms, the p99.9 of the total is at most 5×40=2005 \times 40 = 200 ms.

Key insight: the tighter the end-to-end SLO (lower ε\varepsilon), 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.