BOOK DEEP READ · CHAOS ENGINEERING · CH 7
Chaos Engineering: System Resiliency in Practice · Ch 7 · LinkedIn · 2020
The previous three chapters were all about how to push failure into production. This one asks the question everybody steps around: production is full of real people.
You are not shaking a pile of machines. You are shaking hundreds of millions of people, some of whom are submitting a job application or talking to a recruiter right now. LinkedIn's answer is not "then don't do it in production." It is: make "who gets affected" a field you fill in.
No drug goes straight from the lab to everyone. First a handful of people, checked for safety; then dozens, then hundreds. And every phase is written down in advance with a stopping rule — "if this happens, halt immediately" — plus someone whose only job is watching that line.
Running failure experiments on real people follows the same three rules: start with a tiny group, widen one rung at a time, and define the automatic stop before you start. Only one thing differs: in software, "stopping the dose" takes seconds, so you can afford to be a little bolder than medicine.
The default move is to kill a random machine. The problem with it isn't that it's risky — it's that you don't get to choose who suffers.
Who happens to be on that machine depends on how traffic was being split at that instant. It might be all idle browsing, or it might include the one person pressing "Submit application." "Only one request in a thousand" sounds small, but that one in a thousand isn't a number — it's a specific person's specific action.
And there is a sneakier layer. If the system habitually sends the same person to the same machine, then "one request in a thousand fails" really means "one person in a thousand has a broken session end to end." One person failing a hundred times and a hundred people failing once each are the same number on a dashboard and two different things in real life — the first one uninstalls the app.
The question changes from "should we take this risk?" to "how much of it?" Most defects surface at the shake-yourself rung and never touch a real user; when you genuinely need real traffic, you climb one rung at a time and can always pull back.
One honest cost: a per-request failure is simulated — it only makes that one request believe the downstream is broken. A real outage lands on every caller at once, and that kind of global congestion only shows up at scale.
Measure the blast radius of production chaos in people: attach the failure to a single request and let it propagate, shake yourself first and then widen cohort by cohort, and write the automatic stop condition before you begin — an experiment with no automatic abort has no business running on real people.
Want the mechanism, the numbers and the diagrams? → switch to the deep read
You think "minimize the blast radius" means making the experiment smaller — kill fewer machines, route less traffic. This chapter changes the unit: blast radius is measured in people, not machines. LinkedIn has hundreds of millions of members, and one of those sessions may be an interview invitation; so the real defect in "kill a machine" is not that it is risky but that you have no say in who gets hurt. The engineering answer is to push injection granularity down to a single request: the failure instruction propagates down the call graph with the request (LinkedOut), turning the affected set from "whoever happened to be routed there" into an explicit input of the experiment — then trade information up a ladder (your own session → employees → a targeted cohort → cluster level), with every rung wired to an abort that runs faster than the harm.
Chapter 7, the fourth entry in Part II ("the principles in practice"), told from LinkedIn. The previous three cover how to run one, how big, and which one first: Ch4 Slack scripting experiments for legacy systems, Ch5 Google scaling DiRT to company size, Ch6 Microsoft solving prioritization. This one covers on whom — turning Ch3's fifth principle, "minimize blast radius," from a slogan into a mechanism. Ahead, Ch8 (Capital One) is the same problem under heavy regulation, and Ch16 (continuous verification) automates this chapter's ramping and verdicts. In the real world it speaks to any team whose tooling can already inject failure but who dare not press the button in production.
Start with the constraint. Around the time this book was written, LinkedIn's publicly stated membership was in the 600–700 million range, and what makes the product unusual is how unevenly weighted a single session is: in the same minute, somebody is idly scrolling the feed and somebody else is pressing "Submit application" or answering a recruiter. A hiccup for the first costs nothing; one failure for the second may genuinely change something.
Now look at the default move. Killing 1 instance out of 1,000 affects roughly 0.1% of requests. Counted in machines, that is "small." Counted in people, two more questions are owed: who are those 0.1%, and what were they doing? Killing an instance does not answer either — the answer is decided by whatever the load balancer was doing that second.
Worse is the amplification sticky routing performs. If routing hashes on member, "1% of requests" is really "100% of the requests of 1% of members." One person failing a hundred times and a hundred people failing once each are the same number on an availability dashboard, and one is tolerable while the other is an incident.
What happens if you don't solve it? Two degenerations: you never dare — the tool and the permissions exist but nobody presses the button, so chaos stays in a staging environment that has neither real load nor real dependencies; or you force it and get shut down — one experiment becomes a real incident and chaos is unmentionable for six months. The restraint in this chapter exists precisely to keep the long-term license to experiment.
So the question is: how do you turn "who gets affected" from a bet into a parameter?
The one thing to take away is a change of units: stop asking "how many hosts, what percentage," and ask how many people, which people, what were they doing, for how long, and is it reversible.
The last three get dropped most often. For how long decides whether a person hits one retry or five minutes of blank screen. Reversible or not splits "a module is missing from the page" from "the application submission was lost, the message half-sent" — and no percentage is small enough to justify experimenting on the second kind. That is also why shaking a read path and shaking a write path are not the same class of risk here.
Once the units change, experiment design points somewhere else: the goal is not to shrink the scale but to make the affected set something you can write down. Killing instances cannot do that — its affected set is only knowable at runtime.
LinkedIn's framework is LinkedOut, and one sentence places it: the injection point is a request filter in the Rest.li stack, and whether this hop should fail is decided by context handed down with the request (the Invocation Context).
It injects three modes: error (throw an exception, mimicking an unavailable resource), delay (add latency before forwarding) and timeout (expire after a set period). Triggering comes in two forms: target a population through LiX, or put the instruction in a cookie — which makes the granularity as fine as "this one page load of mine."
Why this is not easy: a page fans out to dozens of downstreams, and the failure must land only on "this member's this request on its hop into Service X" — not leaking to other members, not leaking to Service X's other callers. That requires the framework to carry the context intact on every hop. Flip it around and you have the implicit cost: paths that never joined the propagation scheme cannot be injected (third-party SDKs, offline and asynchronous jobs, consumers behind a message queue).
What you buy is an order-of-magnitude shift in blast radius: from "every in-flight request of every member on that host for the length of the restart window" down to "one person's one click."
Once injection is precise to one request, the cheapest subject is the engineer. LinkedOut ships a browser extension for exactly that: an engineer flips a switch in their own session, makes one downstream on the page "break," and looks at whether the page still works. Blast radius: one consenting person.
One rung up is automation: walk every downstream of a page, break each in turn, and check for graceful degradation. These experiments can run fully automatically and often because the only account affected is the one that launched them — their marginal harm is zero, so they can be used as tests.
The whole point of the ladder is to squeeze the most defects out of the cheapest rung: missing timeouts, missing fallbacks, a whole page going blank when one module dies — all of that surfaces on rung one, with no member involved. What actually justifies a ramp is only the class of problem that needs real traffic to be visible.
And the honest ceiling: some failures only exist at scale — connection-pool saturation, retry storms, mass cache expiry, a downstream hammered by every caller at once. Single-request injection cannot see them; those need a ramp or a cluster-level experiment.
Once real members are in scope, "are we hurting anyone?" cannot hang on a human watching a screen. Three points:
A criterion you can take straight to a review: an experiment with no automatic abort condition should not run on real people. That is not a moral posture, it is cost arithmetic — the latency of "human notices plus human responds," multiplied by the ramped population, is the harm you prepay for the experiment.
The first table makes the ladder concrete: what each rung buys, and what it costs.
| Rung | Blast radius (people) | Finds | Cannot find | Abort |
|---|---|---|---|---|
| 1. Engineer's own session | 1 (consenting) | Fallback paths, whether timeouts exist, error pages | Anything scale-dependent | Turn the extension off |
| 2. Employees / dogfood | 10²–10³ | Degradation consistency across pages and clients | Real user behaviour mix | Flip the ramp switch |
| 3. Targeted member cohort | 10⁴ and up | Metric shift under real traffic; whether alerts fire | Site-wide congestion | Auto-pull on metric breach |
| 4. Cluster / whole region | 10⁶+ | Pool saturation, retry storms, capacity and failover | — | Runbook plus human in the loop |
Most defects die on rung 1 — that is the ladder's real return, and the precondition for daring to do any of this in production.
The second table cuts the same question differently: what each harm-limiting technique costs you in fidelity.
| Technique | Harm to members | Fidelity | Blind to | Best for |
|---|---|---|---|---|
| Request-level targeted injection | Very low; down to one person, one request | Medium: simulates what a broken downstream looks like | Global effects that hit every caller at once | High-frequency, everyday degradation checks |
| Diverting to experiment clusters (with control) | Low, by percentage | High: real traffic, real dependencies | Needs an architecture that can divert | Ramp verdicts, continuous verification |
| Staging / shadow environment | Zero | Low: neither load nor data is real | Scale behaviour and real dependency behaviour | First run of a dangerous failure mode |
| Killing instances / regions in production | High, and you cannot choose who | Highest | — | Capacity and failover drills, with runbook and window |
Concretely:
Three sentences that can change a design review. One: blast radius is measured in people, not machines. Two: "only 1% of traffic" means "1% of people, continuously" under sticky routing. Three: an experiment with no automatic abort condition has no business running on real people.
Asked in an interview how you do chaos engineering in production: "we kill random machines at night" is one level; "injection is request-level, experiments default to the initiator's own session, ramps go through a targeted cohort wired to an automatic abort on member-visible metrics" is another — the second shows you know the hard part is who gets affected, not whether you can inject.
EVIDENCE FROM INDUSTRY
randomly terminates: that approach never answers who is affected. The point is not that it is useless (it is the starting point) but that granularity decides whether you can be accountable for the harm. Netflix · Chaos Monkey repository README (github.com/Netflix/chaosmonkey, wording verified during this run)0.1% landing on feed scrollers and landing on people submitting applications are two different events.1. The thesis: blast radius is measured in people, not machines. The defect in "kill one machine" is not the risk but that you have no say in who gets hurt.
2. Change the units: not how many hosts or percentage points, but how many people, which ones, doing what, for how long, and is it reversible — the last two are the ones routinely dropped.
3. The mechanism: the failure instruction propagates down the call graph. LinkedOut = a Rest.li request filter plus Invocation Context, three modes error / delay / timeout, triggered by LiX targeting or by a cookie precise to one page load.
4. Its engineering value reduces to one line: it turns the affected set from "whoever happened to be routed there" into an explicit input of the experiment.
5. The ladder: your own session (1 person) → employees (10²–10³) → targeted member cohort (10⁴+) → cluster level (10⁶+). Most defects die on the first rung, which is what makes production work defensible.
6. Sticky routing amplifies "1% of requests affected" into "1% of people affected throughout" — one number on the dashboard, two different realities. Target by member and cap the exposure window.
7. Three rules for automatic abort: member-visible business metrics, harm visible within the rung's time scale, firing automatically within seconds. Criterion: no automatic abort condition, no running on real people.
8. The honest ceiling: request-level injection is blind to global effects (pool saturation, retry storms); it divides labour with cluster-level experiments rather than replacing them. And what restraint really buys is the standing permission to keep experimenting at all.