The Ping Comes First: Amazon's Load-Shedding Doctrine

David Yanacek opens with a confession from Amazon's Service Frameworks team: years spent hunting the ideal default for maximum connections — the knob meant to keep servers from taking on too much — ended in the conclusion that the concept was too imprecise to be the answer at all. Too low cuts off capacity the service actually has; too high produces brownout; just right becomes wrong when the workload shifts. What replaced the knob is a doctrine. Overload is anatomized as a feedback loop — latency crosses client timeouts, completed work goes to waste, clients retry and multiply the offered load through every layer of a deep call graph — and load shedding is the counter: reject the excess so that accepted requests finish inside their clients' patience, keeping goodput flat as throughput climbs. But the doctrine's heart is what to keep. The most important request an overloaded server receives is the load balancer's ping, because dropping it shrinks the fleet itself; completion outranks initiation (end() before start(), page N before page one); within-quota outranks burst; humans outrank crawlers; and doomed requests — identified by transitively propagated deadline hints and queue-age bounds — get dropped at dequeue before they waste another cycle. Protection stacks in layers from WAF and iptables down to the server that knows the most and can afford the least, with the false-positive rate held at zero and the pitfalls named: shedding that silences autoscaling, surge queues that hide staleness, and latency metrics polluted by fast-failing rejections.

Interactive

Run a server into its inflection point and watch goodput collapse while retries multiply the load — then arm shedding blind and shrink your own fleet by dropping the health checks. Add the priority ladder and keep the pings, the humans, and the completions; propagate deadlines and drop the doomed at dequeue; and hold the goodput plateau flat past the break.

Open the visualization ↓

Problem

The framing story is the Service Frameworks team's hunt for a sensible default: maximum connections, the setting designed to prevent a server from taking on too much work, configured on load balancers and mirrored in the frameworks. The team decided that if human judgment could pick the value, software could emulate the judgment — and then discovered the judgment doesn't exist. Set too low, the balancer cuts off request growth while the service still has capacity. Set too high, servers go slow and unresponsive. Set just right, the workload shifts or a dependency's performance changes and the value is wrong again, producing unnecessary outages or overloads. The concept was too imprecise to be the answer.

What overload actually does is then anatomized. Under load, thread contention, context switching, garbage collection, and I/O contention grow until performance hits an inflection point and degrades rapidly — the Universal Scalability Law's territory, where throughput is bounded by the serial parts and then actively decays as the machine spends itself on context switching. In a distributed system the client's patience converts that latency into failure: when server latency exceeds the client timeout, requests fail — and if median latency equals the timeout, availability is 50 percent. The cleaner vocabulary is goodput versus throughput: throughput is everything sent to the server; goodput is the subset answered without error and fast enough to be useful.

Then the loop closes. A timed-out request wastes every cycle the server already spent on it — and the last thing a capacity-constrained system should do is waste work. Clients retry, multiplying offered load; in a deep call graph where each layer retries, an overload at the bottom amplifies exponentially through cascading retries. Overload becomes its own cause: a steady state the system cannot exit by working harder, because working harder is the mechanism of the collapse.

Solution

Load shedding is the exit: as a server approaches overload, it rejects excess requests to focus on the ones it admits, keeping their latency under the client timeout. Accepted requests stay available; only the excess suffers; goodput holds flat as offered throughput climbs — until, the post concedes, the act of rejection itself consumes enough resources that Amdahl's law collects anyway. The discipline around it is empirical: load test past the breaking point and far beyond, or assume the service fails in the least desirable way possible; the ideal test result is a goodput plateau that stays flat as throughput keeps rising; measure client-perceived availability, not just the server's own; and hold the false-positive rate — rejections issued while capacity existed — at zero, treating anything else as a tuning or load-balancing defect.

The doctrine's core is triage. The most important request an overloaded server receives is the ping from its load balancer: miss it and the balancer stops sending traffic, idling the server — and in a brownout, the last thing to do is shrink the fleet. Beyond pings, priority is service-specific but principled. Crawler traffic serving a search index can shift off-peak; a human waiting for a page cannot. A service exposing start() and end() must prioritize end(), or clients can begin work they can never finish — brownout manufactured from half-completed sagas. Pagination obeys the same law: a failure at page N wastes N-1 successful calls, so later pages outrank first pages. Prioritization pairs with throttling — burst traffic above a client's quota is admitted below other clients' within-quota traffic — and it belongs at the front door: in a cooperating architecture like amazon.com's, conflicting priority heuristics deep in the stack waste work, so shaping happens as early as possible rather than as global decisions throughout.

Two mechanisms find the doomed work. Deadline propagation: clients attach timeout hints — how long they're willing to wait — and services propagate the remaining time transitively across every hop, so a downstream service knows whether its answer can still be useful; servers evaluate the deadline at dequeue and drop requests whose clients have already left, choosing per-request time-to-live over politely completing answers nobody is listening for (the post is frank about the plumbing: absolute times need synchronized clocks, durations need monotonic timers, and TCP buffers can swallow the stopwatch's start). Queue-age bounding: every layer hides a queue — framework executors, socket buffers, load balancer surge queues — and each converts overload into invisible staleness, so Amazon bounds how long work may sit queued, throws out the too-old, prefers LIFO under pressure where the protocol permits, and encodes the lesson generationally: the Classic Load Balancer's surge queue became the Application Load Balancer's spillover rejection. Around it all, protection stacks in layers — WAF, API Gateway, iptables at the OS, the framework, the code — each cheaper and blinder than the last, cooperating so the server that knows the most is protected from volumes it can't afford to even reject, with instrumentation preserving who was dropped and why. The coda inverts the whole frame: per-request isolated environments like Lambda sidestep the on-host contention that makes shedding necessary, goodput staying flat past saturation — after years of tuning thread pools and connection knobs, the winning move can be to stop needing the configuration at all.

Tradeoffs

  • Max connections was the wrong species of answer: a static, importance-blind admission knob whose correct value doesn't exist. Too low rejects traffic the service could serve — false positives by construction; too high permits brownout; exactly right decays into wrong at the next workload shift or dependency change. The opening confession sets the doctrine's terms: overload protection cannot be a number, because a number sees none of what matters — importance, deadlines, queue age.
  • Triage has a hierarchy, and its apex is non-obvious: the load balancer ping outranks every customer request, because shedding it converts one server's overload into fleet shrinkage — the drop budget spent so wrongly it destroys capacity. Below the apex, the orderings are about wasted work: completion over initiation (end() before start(), page N before page one), within-quota over burst, shiftable crawlers below unshiftable humans. Priority is not favoritism; it is an accounting of which drop wastes the least.
  • Deadline propagation converts client patience into a server-side budget, and the plumbing is honest work: absolute deadlines require synchronized clocks (hence the Time Sync Service), durations require monotonic timers and a truthful stopwatch start that TCP buffering can defeat, and latency estimators that predict whether a request can still finish are cleverness with backfire potential (the estimator can't know a cache hit from a miss). The post's verdict: enforce the deadline at dequeue anyway — better complicated triage than polite work for clients who already left.
  • Queues are where overload hides. Every layer keeps one — executors, socket buffers, surge queues — and each converts excess load into invisible staleness: the request finally dequeued may be long dead, and the server can't tell. Bound queue age, not just depth; throw out the too-old; prefer LIFO under pressure where the protocol allows it. The organizational receipt is generational: Classic ELB's surge queue was replaced by ALB's fast-fail spillover, a product encoding of the lesson that queueing excess traffic is brownout on layaway.
  • Layered protection trades visibility for cheapness in both directions. The earliest layers — WAF, API Gateway, iptables — reject most cheaply and know least; the server knows the most and can least afford the rejection work (in extremes, too overloaded even to say no). The resolution is cooperation with instrumentation: drop early, log who and why, keep false positives at zero — and mind the two named traps: shedding that holds CPU low can silence reactive autoscaling entirely, and a fleet running near its shed-point carries less AZ-failure headroom than its system metrics imply.
  • The coda inverts the article: after a doctrine's worth of thread-pool tuning, deadline plumbing, and queue discipline, the strongest overload protection described is architectural — per-request isolated execution environments (Lambda; Fargate) where one call's resources never contend with another's, goodput staying flat past saturation with no knob configured at all. The team that spent years chasing the perfect default ends by recommending the paradigm where the default doesn't need to exist. Timestamped candor, same as ever: it isn't a panacea — dependencies can still slow, and concurrency still climbs.

Patterns in this article

  • Priority-Aware Load Shedding

    The class doctrine stated as a field manual: under overload, triage explicitly — the load balancer ping above everything (drop it and the fleet shrinks), completion above initiation, later pages above first pages, within-quota above burst, shiftable crawler traffic below humans — and push the shaping to the front of the architecture, because conflicting priority heuristics deep in a cooperating stack waste the work they mean to save. The classmates built the mechanisms; this piece writes their ranking rules down.

  • Deadline Propagation

    Minted from the keeping-an-eye-on-the-clock section: clients attach timeout hints, services propagate remaining time transitively across every hop, and servers enforce the deadline at dequeue — dropping doomed requests instead of completing answers nobody is listening for. The post supplies both the plumbing honesty (synchronized clocks or monotonic timers; TCP buffers defeating the stopwatch) and the wasted-work arithmetic that justifies it: a late reply is a success only from the server's perspective.

  • Layered Admission Control

    Second company, and the generalized form of what Stripe built as four stacked limiters: protection in layers from WAF and API Gateway through OS-level iptables to the framework and the code, each layer cheaper and blinder than the one below, cooperating so the server that knows the most is never asked to reject volumes it can't afford. The addition this piece makes explicit: early rejection's cheapness is bought with visibility, so every layer's drops must be instrumented and false positives held at zero.

Also solving this

Other systems in behindscale's Priority-blind load shedding class: