Operations Reviewed

Overload and load shedding

A service is overloaded when it is asked for more work than it can finish before its clients give up. Left alone, it slows down for everyone, then fails for everyone. Load shedding keeps goodput flat instead: serve what fits quickly, reject the rest early and cheaply, and make the rejections visible.

On this page
  1. Throughput and goodput
  2. Shedding at the server
  3. Queues
  4. Rejections aren’t free
  5. Worked example
  6. Graceful degradation
  7. Shedding, autoscaling and load balancing together
  8. Make shedding visible
  9. Overload runbook

Throughput and goodput

Throughput is every request sent to the server. Goodput is the part answered without error and soon enough for the client to use. Past its limit a server does not keep its throughput: it spends more time on contention, context switching and garbage collection, latency climbs past client timeouts, and work finishes after the caller has gone. AWS’s test of a good design is that goodput rises to a plateau and stays flat however much more load arrives. A design that needs fixing shows goodput falling towards zero.

Overload feeds itself. Requests that time out waste the work already done on them, clients retry and add load, and crashing servers push their share onto the rest; see timeouts, retries and backoff. Recovery is harder than prevention: the SRE book notes that a service which started crashing just above its capacity will not recover when load drops back to just below it, because only the few tasks that are up have to absorb all of it.

Shedding at the server

Each server should protect itself, because load balancers and quotas react late and imperfectly. Fail early and cheaply:

Queues

A queue in front of a worker pool turns overload into latency: a request waits behind everything ahead of it. With 40 workers taking 100 ms each, a full queue of 400 adds 1,000 ms before work starts. Against a 1-second client timeout, every request at the back is wasted.

Rejections aren’t free

Rejecting a request still costs parsing, a response and a connection. If rejecting costs 10% of serving, a server facing 10 times its capacity spends everything on rejections. Two remedies from Google’s SRE book:

reject locally = max(0, (requests − K × accepts) ÷ (requests + 1))

With Google’s preferred K = 2, an overloaded backend ends up rejecting about one request for each one it serves. A lower K wastes less backend capacity on rejections but notices recovery more slowly. It works poorly for clients that send requests only occasionally, because each has too little recent history.

Worked example

How much does shedding save at five times capacity?

A service completes 1,000 requests a second when it does nothing else. Rejecting a request costs 10% of serving one. A news story sends it 5,000 requests a second.

  1. Without shedding, latency passes the client timeout and goodput heads towards zero.
  2. With server-side shedding only, rejected requests use capacity too:served + 0.1 × (5,000 − served) = 1,000 → served = 556/s
  3. With adaptive throttling at K = 2, the backend receives about 2 requests per one it accepts:accepts × (1 + 0.1 × 1) = 1,000 → accepts ≈ 909/s
  4. The clients reject the rest locally:(5,000 − 2 × 909) ÷ (5,000 + 1) = 63.6%, leaving 1,819/s to reach the backend

Throttling at the client keeps about 909 requests a second succeeding instead of 556. Server-side shedding alone reaches zero goodput at 10,000 requests a second, 10 times capacity. The SRE book’s aim is that a task keeps serving its provisioned rate when sent somewhere above 2× or even 10× that rate.

DemandCompleted, free rejectionsCompleted, rejections cost 10%
1.5× (1,500/s)1,000/s944/s
3× (3,000/s)1,000/s778/s
5× (5,000/s)1,000/s556/s
8× (8,000/s)1,000/s222/s
10× (10,000/s)1,000/s0/s

Graceful degradation

Instead of rejecting a request, do less work for it: search a cached subset, skip pictures or recommendations, return slightly stale data. It must be designed in, and it fails in the ways rarely used code fails:

Shedding, autoscaling and load balancing together

The three are usually configured separately, by different people, and can work against each other. The Workbook and AWS describe the main traps:

Make shedding visible

Overload runbook

Steps drawn from the SRE book; the order is this guide’s. A starting point to adapt into a runbook:

1. Confirm it is overload: offered load above tested capacity, latency near the
   client timeout, saturation (CPU, threads, queue age) high on most tasks.
2. Add capacity if you have idle resources and tasks are not crash-looping.
3. Stop the feedback: turn off retries you control, pause batch and background
   load (index builds, copies, reports), block known bad or abusive traffic.
4. Shed or degrade on purpose: lowest criticality first, then degraded responses.
5. If tasks crash as soon as they become healthy: fix the trigger, cut traffic
   hard (for example to 1%), let tasks recover and caches warm, then ramp up.
6. Check health checks are not killing tasks that are only busy; restart only
   tasks that are wedged, gradually, after finding why.
7. Afterwards: load test past the breaking point and record where goodput fell.

Sources: Google, Site Reliability Engineering, 1st ed., ch. 21 “Handling Overload” and ch. 22 “Addressing Cascading Failures”; The Site Reliability Workbook, ch. 11 “Managing Load”; David Yanacek, Using load shedding to avoid overload (Amazon Builders’ Library).