Common causes of cascading failure
coreintermediateA cascading failure is one where a small, initially contained problem in one component spreads to bring down other, otherwise-healthy components — the defining feature is that the system ends up in a much worse state than the size of the original trigger would suggest. Five causes account for most real-world cascades. A retry storm happens when a dependency slows down or briefly fails, every caller retries (often simultaneously), and the retries themselves add enough extra load to keep the dependency down or push previously-healthy dependencies down too — the "fix" (retrying) becomes the cause of continued failure. Shared resource exhaustion happens when multiple unrelated call paths draw from the same pool (connections, threads, memory) and one path's slowdown consumes the entire shared pool, starving every other path that happens to share it. An overloaded database is a common single point where many independent services converge, so a spike or slow-query problem in the database becomes everyone's problem simultaneously, rather than being contained to whichever service caused it. Queue backlogs occur when a consumer falls behind a producer's rate; the backlog itself starts consuming memory or disk, and as the backlog grows, message processing and lookups against it get slower, which slows the consumer further and grows the backlog even faster — a self-reinforcing loop. Synchronized clients happen when many independent clients end up doing the same thing at the same moment (all retrying on the same fixed schedule, all refreshing a cache on the same TTL, all waking from the same cron), turning what should be smoothly-distributed load into a sharp, simultaneous spike.
Think of it as
A cascading failure is a traffic jam that started with one stalled car. The stalled car alone (the original, small failure) blocks one lane; but cars behind it brake hard (a retry storm — everyone reacting to the same event at once), the merging traffic backs up into an earlier intersection that had nothing to do with the original stall (shared resource exhaustion — an unrelated route now blocked too), and within twenty minutes an entire section of the highway network is gridlocked from a single stalled car that could have been towed in five minutes if nothing else had reacted to it so aggressively. The size of the eventual jam has almost no relationship to the size of the original problem — that mismatch is the actual signature of a cascading failure, as opposed to an outage that stays proportional to its cause.
What we're doing: Trace how a brief database blip becomes a full outage through retry amplification and shared resources.
- 3
- Immediate, unbounded retry is what converts a 4-second slow query into a doubled load spike within one minute — the retry storm is not a separate incident, it is the mechanism that turns a contained slowdown into a load problem.
- 7
- This is the moment the cascade crosses from "the slow-query services are degraded" to "unrelated services are down too" — a shared connection pool is what makes that crossing possible; without it, the four unrelated services in the next line would have stayed healthy.
Why this works: Every individual step in this timeline is a small, locally reasonable behavior — retrying a timed-out call, sharing a connection pool to save resources, letting a queue absorb temporary slowness — and the outage is not caused by any single bad decision, but by the fact that none of those locally reasonable behaviors had a limit, so each one amplified rather than absorbed the original problem.
Retrying immediately and unconditionally on any failure
Wrong
Better
What you see: A database that was recovering from a brief slow-query spike never actually recovers, because every caller's unconditional retry loop keeps resending failed queries as fast as they fail, holding the database at saturation indefinitely instead of letting it drain the backlog and return to normal.
Why: An unbounded, immediate retry loop has no mechanism to reduce load even when load is exactly the problem — it treats every failure as "try again right now," which is the correct instinct for a single caller in isolation and the exact wrong aggregate behavior when thousands of callers all reach the same conclusion about the same struggling dependency simultaneously.
- DB slow-query spike — small, isolated
- leads to Overloaded database
- Overloaded database — every caller feels it
- leads to Retry storm
- Retry storm — retries add more load
- leads to Shared resource exhaustion
- on error, leads to Overloaded database (adds more load)
- Shared resource exhaustion — unrelated calls starved too
- leads to Queue backlog
- Queue backlog — self-reinforcing slowdown
- leads to System-wide outage
- System-wide outage — far larger than the original spike
Five causes and the amplification mechanism each one relies on
Remember: Five causes account for most cascading failures — retry storms, shared resource exhaustion, overloaded databases, queue backlogs, and synchronized clients — and every one of them works by amplification: a locally reasonable reaction (retry, share a pool, let a queue absorb slack) turns a small, contained problem into a much larger one because nothing limited how far the reaction could go.
See also: mitigating cascading failures · retry amplification · what is backpressure · bulkhead isolation

