Why Distributed Systems Fail in Unexpected Ways
An investigation into cascading failures, partial failure, retry storms, and the second-order behavior of distributed systems.
When a single-process program crashes, the story is usually legible. The process stops, the log shows a stack trace, and the blast radius is often close to the process boundary.
A distributed system fails differently. It can remain alive while becoming unreliable. One service slows down, another retries, a queue grows, a cache expires, and an autoscaler begins reacting to a signal that is already late. Nothing has to be malicious or poorly written for the whole system to drift into failure.
The Illusion of Independent Services
Service boundaries make systems easier to own, deploy, and reason about locally. They do not make services independent. Every network call is a dependency. Every shared database, cache, queue, identity provider, and control plane becomes part of the behavior of the system.
The mistake is to draw boxes and forget the arrows. The arrows are where latency, retries, load, ownership, and failure travel.
Cascades
A cascade begins when a local problem changes the operating conditions for neighboring components.
- A dependency slows down.
- Callers hold resources while waiting.
- Timeouts trigger retries.
- Retries add load to the slow dependency.
- Queues and connection pools fill.
- Healthy components start failing for indirect reasons.
The root cause may be small. The system response is not.
Partial Failure
Partial failure is the defining discomfort of distributed systems. Some machines can communicate while others cannot. One replica can be healthy while another is stale. One region can accept writes while another sees timeouts.
The engineering question is not "how do we prevent all partial failure?" It is "what should the system do when partial failure is happening and nobody has perfect information?"
Retries as a Feedback Loop
Retries are useful when failure is transient. They are dangerous when failure is load-related. In the second case, retry behavior can become a positive feedback loop: failure causes retries, retries cause load, load causes more failure.
The remedy is not simply "never retry." The remedy is to design retries with budgets, jitter, deadlines, backoff, and circuit breakers so the system does not amplify its own pain.
What Better Engineering Looks Like
Reliable distributed systems tend to make pressure visible. They define timeouts deliberately. They limit work in flight. They degrade less important features before critical flows fail. They make dependency health observable. They test failure paths before production discovers them.
The lesson is not pessimism. It is humility: distributed systems reward designs that assume components will fail and that the consequences will not stay neatly inside one box.
References
- The Tail at Scale · Communications of the ACM
