Skip to content
The Language of Systems

01.02 · Lecture · Free

The Hockey Stick

Explain why latency scales as 1/(1-utilisation) rather than linearly, so a system that is fine at 70% is vertical at 90%.

The player loads only when you ask for it, so this page stays fast.

Curated for this lesson

The Language of Systems

13. Little, M/G/1, Ensemble Averages

Gallager derives the M/G/1 result the whole course rests on - response time as a function of utilisation, which is why a system is fine at 70% and vertical at 90%.

Latency grows like 1/(1-utilisation) because work waits behind other work once a server is busy. The last spare capacity is disproportionately valuable: a service that feels safe at 70% can become painful near 90%, then unstable as arrivals catch completions and queues stop draining.

What this lesson answers

  • why does latency spike near high utilisation
  • how does queueing create hockey stick latency
  • why is 90% utilisation dangerous in production

Notes

The Hockey Stick — The hockey stick exists because queues amplify small increases in utilization into disproportionate latency, so without spare capacity a system that looks healthy at utilization can become effectively unusable near .

Key Concepts: - In an queue, mean response time is , where is service time and is utilization. - If service time is , then at , , but at , . - The queueing delay portion is , so delay grows hyperbolically rather than linearly. - Utilization is , where is arrival rate and is average service time; with , one worker reaches at requests/second. - Moving from to utilization doubles response time in : . - Moving from to utilization doubles response time again: . - At , the queue is unstable because arrivals are at least as fast as completions, so expected queue length grows without bound. - The “vertical” part of the hockey stick is caused by variance and backlog: even if average capacity equals average demand, bursts create queues that take time to drain.

Watch For: - Treating CPU as only worse than : with , doubles latency from to . - Averaging utilization over 5 minutes hides overload: a service at for 4 minutes and for 1 minute reports average while accumulating a queue during the saturated minute. - Scaling only after can be too late: if autoscaling takes 60 seconds and arrivals exceed service rate by 100 requests/second, the system accumulates about 6,000 queued requests before new capacity arrives. - Ignoring tail latency: if mean response time is at , can be seconds when requests have variable service times or share a saturated dependency.

Production Connection: - Kubernetes Horizontal Pod Autoscaler commonly scales on CPU utilization targets such as to avoid running pods near the queueing cliff at . - AWS Lambda reserved concurrency prevents one function from consuming all regional concurrency and pushing downstream systems such as Amazon RDS or DynamoDB into saturation. - Envoy circuit breakers use limits such as `max_connections`, `max_pending_requests`, and `max_requests` to cap queues before latency enters the hockey-stick region. - Google Cloud Run’s container concurrency setting controls how many simultaneous requests share one instance, directly affecting per-instance utilization and queueing delay.

Common questions

Why does latency not rise linearly with utilisation?
Because each request is affected by both its own service time and the backlog already in front of it. As utilisation increases, there is less idle time available to absorb bursts. The waiting component therefore grows much faster than the work itself, following the 1/(1-utilisation) shape rather than a straight line.
Why can 70% utilisation look healthy while 90% is unsafe?
At 70%, there is still meaningful spare capacity to drain short bursts. Near 90%, that cushion is small, so a modest arrival spike can create a queue that takes a long time to clear. The average CPU number may still look acceptable while users experience rapidly worsening response times.
What happens when utilisation reaches 1?
At utilisation of 1, average arrivals match average completions, so there is no slack for variance. Any burst creates queued work, and the system has no spare capacity to catch up. Above that point, arrivals exceed completions and the expected queue grows without bound until something rejects, sheds, or adds capacity.