← All resources
Cloud Platform

Everything Is a Queue

Most outages are not failures. The system just accepts more work than it can finish.

Executive summary The most damaging cloud outages are rarely a clean component failure. They are a slow component that never got to say it was overwhelmed, while everything upstream kept sending work, retrying, and scaling into the wall. This is a flow-control problem, and the discipline that solves it, backpressure, is largely absent from how teams design systems. This paper explains why cascading failure is a queueing phenomenon, why common resilience patterns can accelerate it, and how to build systems that slow down instead of falling over.

The outage that is not a failure

Picture a typical cascade. A downstream service gets slow, not down, just slow. The service calling it holds its requests open longer, so its own threads and connections fill up. The service calling that one does the same. Retries kick in, multiplying the load on the thing that was already struggling. Autoscaling adds more instances, all of which pile onto the same slow dependency. Within minutes a single slow component has taken down a chain of healthy ones, none of which actually failed.

No component in that story broke. Every one of them did exactly what it was told: accept work and pass it on. The failure was systemic, and its cause was that no part of the system was allowed to say the one thing that would have saved it: I am overwhelmed, slow down.

The core idea Every connection between two components is a queue, whether you designed it as one or not. If the producer can put work into that queue faster than the consumer can take it out, the queue grows without bound, and unbounded queues are how systems die. Backpressure is the mechanism that lets the queue push back.

Why your architecture is already full of queues

The reason this is missed is that most of the queues in a system are invisible. Teams think they have queues only where they deployed a message broker. In fact, every boundary is one.

  • A thread pool is a queue of work waiting for a thread.
  • A connection pool is a queue of requests waiting for a connection.
  • A socket buffer is a queue of bytes waiting to be read.
  • An in-memory list of pending tasks is a queue, whether or not anyone called it that.

Once you see that every boundary is a queue, cascading failure becomes obvious: it is what happens when queues you did not know you had fill up faster than they drain, and nothing in the design tells the producer to stop. The system was implicitly assuming the consumer could always keep up. Under load, it cannot, and the assumption becomes the outage.

Why the usual resilience patterns can make it worse

The uncomfortable part is that several patterns teams adopt for resilience accelerate this specific failure.

  • Retries. A retry is more load aimed at a component that is already failing to keep up. Naive retries turn a slowdown into a self-inflicted denial of service. The system attacks itself in the name of reliability.
  • Autoscaling. Scaling the struggling tier can help; scaling the tier in front of a struggling dependency is catastrophic, because it increases the rate of requests hitting the bottleneck. Autoscaling without flow control scales the pressure, not the relief.
  • Generous timeouts. Long timeouts feel forgiving. They also mean each stuck request holds resources longer, so the caller fills up faster. Patience upstream is pressure downstream.

None of these patterns is wrong. Each becomes dangerous when applied without a way for the system to signal that it is saturated. They are throttle without a governor.

The distinction that matters Resilience patterns like retries and autoscaling assume the problem is a component that failed and needs to be routed around or replaced. Backpressure assumes the problem is a component that is fine but slower than the demand, and needs the demand to relent. Most cascades are the second kind, which is why the first set of tools can make them worse.

The four moves of flow control

Backpressure is not one feature; it is a small set of design habits that together let a system regulate its own load.

  • Bound every queue. An unbounded queue is a memory leak with a latency problem. Every queue, explicit or implicit, needs a limit, because a bounded queue that rejects work is recoverable and an unbounded one that exhausts memory is not.
  • Make saturation a signal, not a silence. When a component is at capacity, it should say so, by rejecting fast, returning a slow-down signal, or refusing to accept more, rather than quietly accepting work it cannot do and failing later. Fast rejection is a feature; silent acceptance is the trap.
  • Shed load deliberately, worst work first. Under overload, a healthy system drops the least important work to protect the most important. Shedding load on purpose, low-priority requests, retries, speculative work, is how you stay up for the traffic that matters instead of falling over for all of it.
  • Retry with respect. Retries must back off, add jitter, and stop when the signal says the target is saturated. A retry budget and a circuit breaker turn retries from an amplifier into a controlled recovery.

The through-line is that the system must be able to say no, and everything upstream must be built to hear it. A system where no component can refuse work is a system that can only fail all at once.

Designing for the answer "not right now"

The mental shift is to stop designing systems that assume every request will be served and start designing systems that can gracefully answer "not right now." That answer, delivered fast and cheaply, is what keeps a slowdown from becoming an outage. It preserves capacity for the work that can be done, protects the struggling component from the pile-on, and gives the system room to recover on its own.

This is the opposite of the instinct to make systems more accommodating under stress. The accommodating system, the one with generous timeouts, eager retries, and unbounded queues, is the one that cascades. The system that pushes back, that bounds its queues and sheds load and signals saturation, is the one that bends without breaking.

The takeaway Cascading outages are a flow-control failure, not a component failure: invisible queues fill faster than they drain because nothing in the design lets a saturated component say slow down. Treat every boundary as the queue it already is, bound them, make saturation a fast and explicit signal, shed load worst-first, and retry with restraint. Build systems that can say "not right now," and a slow dependency stops being an outage waiting to happen.

Simcha Solutions designs systems with flow control as a first-class concern, so a slow component degrades gracefully instead of taking its healthy neighbors down with it.