The Ping Comes First: Amazon's Load-Shedding Doctrine
A field guide to load shedding: deliberately rejecting some requests so a server can finish the rest instead of collapsing under all of them. AWS's David Yanacek opens with a confession. His team spent years hunting the right default for max connections, the setting meant to cap how much work a server takes on, and concluded it can't be made right: too low rejects work the server could have handled, too high lets it drown, and any value that fits today is wrong once traffic shifts. What replaced the knob is a set of rules for overload, which left alone feeds itself: a slow server misses deadlines, clients retry, and the retries pile on more load. The fix is to shed the excess and keep goodput steady, where goodput means the requests served fast enough to be useful, not every request sent. The hard part is choosing what to keep, and that ranking is the article's real subject: what an overloaded server protects first, and why dropping the wrong thing (starting with the load balancer's health check) makes the overload worse.
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.
Problem
The article opens with the AWS Service Frameworks team hunting a sensible default for max connections, the setting meant to stop a server from taking on too much work. They figured that if a human could pick the right value, software could learn to pick it too, and then found the right value doesn't exist. Set it too low and the load balancer turns away requests while the server still has room. Set it too high and the server goes slow and unresponsive. Set it just right and the next traffic shift or slow dependency makes it wrong again. The setting was simply too blunt to be the answer.
The article then explains what overload actually does. As load rises, a server spends more and more of its time on overhead (switching between tasks, garbage collection, waiting on disk and network) until it hits a point where performance drops off a cliff. This is the Universal Scalability Law in action: adding more work helps less and less, because the parts that can't run in parallel become the bottleneck, and past a point throughput actively falls as the machine thrashes. In a distributed system the client's patience turns that slowness into outright failure: once the server is slower than the client's timeout, requests fail outright. If the typical request is exactly as slow as the timeout, half of them fail, so availability is 50%. The cleaner way to say it is goodput versus throughput: throughput is every request sent to the server, goodput is the subset it answers correctly and fast enough to still be useful.
Then the loop closes on itself. A request that times out wastes every bit of work the server already did on it, which is the worst thing a system short on capacity can do. The client retries, adding more load, and in a deep chain of services where each layer retries, one overload at the bottom multiplies upward into far more load than it started with. Overload becomes its own cause: a state the system can't work its way out of, because working is exactly what's sinking it.
Solution
Load shedding is the way out: as a server nears overload, it rejects the excess so it can keep the requests it accepts fast enough to beat their clients' timeouts. Accepted requests stay healthy, only the rejected excess suffers, and goodput stays flat as offered load climbs, until, the post admits, the act of rejecting requests itself costs enough that throughput eventually gives anyway. The discipline around this is empirical. Load test past the breaking point and well beyond it, or assume the service will fail in the worst possible way. The ideal test result is a goodput plateau: a flat line that holds as offered load keeps rising. Measure availability as the client sees it, not just as the server reports it. And keep the false-positive rate (requests rejected while the server actually had room) at zero, treating anything higher as a tuning or load-balancing bug.
The heart of the doctrine is triage: deciding what to keep. The single most important request an overloaded server gets is the health-check ping from its load balancer. Miss it and the balancer assumes the server is down and stops sending traffic, so in the middle of an overload the fleet shrinks exactly when it can least afford to. Past the ping, priority is specific to each service but follows clear principles. Crawler traffic building a search index can run off-peak; a person waiting for a page cannot. A service with start() and end() calls must favor end(), or clients begin work they can never finish, which manufactures brownout (a state where the service is up but too degraded to be useful) out of half-done tasks. Pagination follows the same rule: failing on page N wastes the N-1 pages already fetched, so later pages outrank first pages. Priority works together with throttling (bursts above a client's quota are admitted behind other clients' normal traffic), and it belongs at the front door: in a cooperating system like amazon.com, priority rules that disagree deep in the stack waste work, so the shaping happens as early as possible rather than in scattered decisions throughout.
Two mechanisms find the work that's already doomed. The first is deadline propagation. A client attaches a hint saying how long it's willing to wait, and each service passes the remaining time along to the next hop, so a service at the end of the chain knows whether its answer can still be useful. The server checks that deadline as it pulls each request off its queue and drops the ones whose clients have already left, rather than politely finishing answers nobody is listening for. The post is candid about the plumbing this needs: absolute deadlines require clocks that agree across machines, durations require timers that never run backward, and a flood of requests can sit in network buffers long enough that the client is already gone before the server even starts its stopwatch. The second mechanism is bounding queue age. Every layer hides a queue (framework thread pools, socket buffers, the load balancer's own surge queue), and each one quietly turns overload into staleness, because a request finally pulled off the queue may be long dead. So Amazon caps how long a request may sit waiting, throws out the too-old, and prefers last-in-first-out under pressure where the protocol allows it, since the newest request is the one most likely to still have a waiting client. AWS baked this lesson into its own products: the older Classic Load Balancer queued excess requests, and its successor the Application Load Balancer rejects them instead.
Around all of this, protection stacks in layers: WAF and API Gateway at the edge, iptables at the operating system, then the framework, then the code. Each outer layer is cheaper to reject at but knows less about the request, and each inner layer knows more but can least afford the work of rejecting, so they cooperate to shield the server that knows the most from volumes it couldn't even say no to. Throughout, the system logs who got dropped and why, so the cheap early rejections don't cost all visibility. The article closes by turning the whole frame around: per-request isolated environments like AWS Lambda, where each call runs with its own resources and can't contend with another's, sidestep much of the contention that makes shedding necessary in the first place. After years of tuning thread pools and connection limits, the winning move can be to stop needing the configuration at all.
Tradeoffs
- Max connections was the wrong kind of answer: a fixed, importance-blind cap whose right value doesn't exist. Too low rejects work the server could have served (false positives by construction); too high lets the server drown; exactly right stops being right at the next traffic shift or slow dependency. The opening confession sets up the whole doctrine: overload protection can't be a single number, because a number can't see the things that actually matter, which are importance, deadlines, and how long a request has been waiting.
- Triage has a ranking, and the top of it is not obvious: the load balancer's health check outranks every customer request, because dropping it turns one server's overload into a shrinking fleet, the drop budget spent so badly it destroys the capacity that would have absorbed the load. Below that, the orderings are all about not wasting work: finishing over starting (end() before start(), last page before first), normal traffic over burst, shiftable crawlers below people who are waiting. Priority here isn't favoritism, it's an accounting of which drop wastes the least.
- Deadline propagation turns a client's patience into a budget the server can act on, and the plumbing is real work: deadlines given as absolute times need clocks that agree across machines, deadlines given as durations need timers that never run backward, and trying to predict whether a request can still finish in time can backfire (the predictor can't tell a fast cache hit from a slow miss up front). The post's call: enforce the deadline as you pull each request off the queue anyway, because complicated triage still beats finishing work for clients who already gave up.
- Queues are where overload hides. Every layer keeps one (thread pools, socket buffers, the load balancer's surge queue), and each one quietly turns excess load into staleness, because the request finally pulled off the queue may already be dead and the server can't tell. So bound how long a request may wait, not just how many can wait; throw out the too-old; and prefer newest-first under pressure where the protocol allows. AWS turned this into a product change: the Classic Load Balancer queued excess traffic, and the Application Load Balancer rejects it instead.
- Layered protection trades away visibility for cheapness, in both directions. The outer layers (WAF, API Gateway, iptables) reject most cheaply but know the least about each request; the server knows the most but can least afford the work of rejecting, and in the worst overloads is too busy even to say no. The answer is layers cooperating plus good logging: drop early, record who and why, keep false positives at zero. Two named traps come with it: shedding that holds CPU low can quietly switch off automatic scaling, and a fleet running near its shed point has less spare capacity for a zone failure than its metrics suggest.
- The closing move turns the article on its head: after all the thread-pool tuning, deadline plumbing, and queue discipline, the strongest protection described is architectural, not configured. Per-request isolated environments like Lambda and Fargate, where one call's resources never contend with another's, hold goodput flat past saturation with no knob set at all. The team that spent years chasing the perfect default ends by recommending the design where the default doesn't need to exist. It isn't a cure-all, and the post says so: dependencies can still slow down, and concurrency can still climb.
Patterns in this article
- Priority-Aware Load Shedding
When a server has to drop load, it shouldn't drop at random: it should rank requests and drop the least important first. The rankings here are all about not wasting work: the load balancer's health check above everything (drop it and the whole fleet shrinks), requests that finish work above ones that start it, later pages of a result above the first, normal traffic above bursts, shiftable crawlers below real users. And the ranking belongs at the front of the system, because if different layers deep in the stack disagree about what's important, they waste the very work they're trying to save. Uber, Netflix, and Stripe each built a mechanism for this; this article writes down the rules they all follow.
- Deadline Propagation
A client says how long it's willing to wait, each service passes the remaining time along to the next one, and a server checks that deadline as it pulls each request off its queue, dropping the ones whose clients have already given up instead of finishing answers nobody is listening for. The article is honest about what this costs to build: clocks that agree across machines, timers that never run backward, and the awkward fact that a request can sit in a network buffer so long the client is gone before the server even starts timing. The payoff is that a late reply is a success only from the server's point of view, so catching doomed work early is worth the plumbing.
- Layered Admission Control
Protection stacks in layers: WAF and API Gateway at the edge, iptables at the operating system, then the framework, then the code. Each outer layer is cheaper to reject at but knows less about the request; each inner layer knows more but can least afford the work of rejecting. They cooperate so the server that understands the most is shielded from volumes it couldn't even say no to. The catch this article names: cheap early rejection costs visibility, so every layer has to log what it drops and why, and keep false rejections at zero. Stripe's four stacked limiters are the same idea built out.
Also solving this
Other systems in behindscale's Priority-blind load shedding class: