Most engineers meet backpressure as a performance concept. A queue is filling up faster than it drains, so you add a limit, shed some load, and move on. It shows up in postmortems about slow databases and overloaded workers, filed next to connection pooling and cache tuning.

That framing misses what backpressure actually is. Backpressure is the mechanism a system uses to decide, under load, who gets served and who gets told to wait or go away. That is not a tuning question. That is an access control question, answered continuously, under conditions an attacker gets to choose.

A system with no backpressure has no opinion about how much work it will accept. It will simply accept work until something breaks — a queue exhausts memory, a thread pool starves, a downstream dependency falls over from being called too many times too fast. The failure mode is not graceful degradation. It is collapse, and collapse is a resource an attacker can trigger on demand, often more cheaply than any other kind of compromise.

What backpressure actually is

Backpressure is the set of decisions a system makes when incoming work exceeds its safe processing rate: reject it, delay it, degrade it, or shed less important work to make room for more important work. The alternative — accepting everything and hoping capacity keeps up — is not the absence of a decision. It is a decision to let whichever client sends the most traffic determine how the system behaves for everyone else.

Unbounded queues are the clearest version of this mistake. A queue with no maximum size looks generous. In practice it converts a rate problem into a memory problem, and it delays the failure just long enough to make it worse when it arrives — instead of rejecting the ten-thousandth request cheaply, the system accepts all ten thousand, keeps them in memory, and then fails while holding the entire backlog, which now has to be recovered, replayed, or discarded under worse conditions than if it had been rejected upfront.

The correct instinct is the opposite of generous: define a safe processing rate, and make every path that adds work respect it. Not “process everything and hope,” but “process what we can guarantee, and have an explicit, cheap answer for the rest.”

Why unlimited acceptance is an attack surface

A service without backpressure treats its own capacity as a secret an attacker has to discover empirically, which is exactly the kind of secret attackers are good at extracting. Send more requests. Watch latency climb. Send more. Watch it climb further. Eventually you find the point where the system stops behaving like a service and starts behaving like a pile of exhausted resources — an out-of-memory kill, a connection pool that never frees a slot, a downstream database pinned at 100% CPU by queries that never finish because they are competing with ten thousand others just like them.

None of this requires a vulnerability in the traditional sense. No memory corruption, no injection, no logic flaw. The system is doing exactly what it was built to do — accept requests and try its best — and that is precisely the property being exploited. This is why denial-of-service planning belongs in the same conversation as authentication and authorization, not in a separate “availability” bucket that gets less scrutiny. A system’s willingness to do unlimited work on request is a permission, whether or not anyone wrote it down as one.

The attack does not even need to be adversarial. A legitimate client with a retry loop and no backoff, a batch job scheduled at the top of every hour alongside a hundred other batch jobs, a mobile app that all restarts and reconnects the moment a network blip ends — these produce the same failure mode as a deliberate flood. Backpressure does not need to distinguish attacker from well-meaning client to be worth having. It needs to protect the system’s own ability to keep functioning regardless of why the load arrived.

The retry storm is where isolated failures become outages

The most expensive backpressure failures are rarely the first spike. They are the second one, caused by everyone retrying at once.

Here is the pattern. A service gets briefly overloaded and starts responding slowly or with errors. Every client that was waiting on a request now retries — usually immediately, because immediate retry is the easiest thing to implement and the first thing most people ship. That flood of retries arrives on top of whatever caused the original slowdown, which makes it worse, which triggers more timeouts, which triggers more retries. The system does not recover during the gap between waves. There is no gap. The waves are the same wave, amplified by the exact mechanism that was supposed to add resilience.

flowchart TD A["Clients send requests"] --> B{"Service under capacity?"} B -- "Yes" --> C["Process normally"] B -- "No, no backpressure" --> D["Queue grows unbounded"] D --> E["Latency climbs, requests time out"] E --> F["Clients retry immediately"] F --> A B -- "No, backpressure active" --> G["Reject or shed excess with 429/503"] G --> H["Client backs off with jitter"] H --> I["Retries later at reduced rate"]
The same overload event either amplifies into a retry storm or drains into a controlled backoff, depending on whether the system has an explicit backpressure signal.

This is why the fix for retry storms is not “add more retries” or even “add more capacity.” Capacity added to absorb a retry storm just raises the threshold at which the next storm forms; the dynamic is unchanged. The fix is an explicit signal — a 429 or 503 with a Retry-After hint, a circuit breaker that fails fast instead of queuing indefinitely — paired with clients that respect it: exponential backoff, jitter, and a cap on retry attempts. The system has to tell clients “not now” cheaply and clearly, and clients have to believe it instead of hammering through it.

Three places backpressure has to live

Backpressure is not one control. It is a property that needs to exist at each boundary where work crosses from one part of a system into another, because each boundary has a different failure mode if it is missing.

At ingress, backpressure looks like rate limits and quotas — per-client, per-API-key, per-IP, whatever identity the system can reasonably attach to a request. This is the boundary an attacker touches directly, so it is also the boundary that most needs a cheap rejection path. A 429 returned in microseconds by an edge proxy is nearly free. A request that gets accepted, queued, partially processed, and then times out has already spent real resources losing.

Inside the system, backpressure looks like bounded queues and circuit breakers between internal services. A worker pool with a fixed size and a bounded input queue fails predictably: once the queue is full, new work is rejected immediately rather than accepted and left to rot. A circuit breaker in front of a struggling dependency stops sending it traffic once its error rate crosses a threshold, which protects the dependency from being finished off by exactly the load that is already hurting it, and protects the caller from spending its own resources waiting on calls that are unlikely to succeed.

At egress, backpressure looks like restraint toward the things a system calls. Blind, immediate retries against a downstream dependency turn a downstream provider’s bad day into your outage, and your outage into a worse day for everyone else calling that same dependency. Respecting a downstream Retry-After, backing off with jitter, and capping total retry attempts are not favors to the downstream service. They are protection for the calling system’s own resources, which get spent whether or not the retry succeeds.

Miss any one of these three and the other two do not compensate. Ingress limits without internal bounded queues just mean the flood arrives at a fixed rate that is still greater than what internals can drain. Internal circuit breakers without egress discipline just mean the system protects itself while still hammering everyone downstream of it.

Where the discipline typically breaks down

Capacity planned against average load, not peak plus failure. A system sized for its median traffic looks fine in every dashboard until the day traffic spikes and a dependency degrades at the same time — which is precisely the combination an incident produces. Backpressure is what a system does in that combination, and it has to be designed for that combination, not for the average day when it is never needed.

“Just add more workers” as a substitute for a decision. Scaling out capacity delays the point where limits get hit; it does not remove the need for an explicit answer once they are hit. Every system has a ceiling somewhere — a database connection limit, a shared cache, a rate limit imposed by a third party. Backpressure has to exist at that ceiling regardless of how high it has been pushed.

Failing slow instead of failing fast. A struggling authentication service that takes eight seconds to return an error is worse than one that takes eighty milliseconds to return the same error, because the eight-second version ties up a caller’s thread, connection, and timeout budget for eight seconds per request, multiplying the damage of the original slowdown across every caller waiting on it. Backpressure is partly about accepting less work, and partly about failing the work you reject as cheaply and as fast as possible.

Retries with no ceiling. A retry policy that keeps trying “until it succeeds” is a retry policy with no relationship to the system’s actual capacity to absorb retries. Every retry policy needs a maximum attempt count and a backoff curve, or it is not a resilience feature — it is a load multiplier with good intentions.

A design test for backpressure

For any boundary where work enters a system, or crosses from one component to another:

  1. What happens when work arrives faster than this boundary can safely process it — is there an explicit answer, or does the queue just grow until something else breaks?
  2. Is rejection at this boundary cheap? A 429 in microseconds and a timeout after eight seconds of held resources are not the same control, even if both eventually say no.
  3. Do clients on the other side of this boundary respect backpressure signals, or will they retry immediately regardless of what the system tells them?
  4. Is capacity here planned against peak load combined with a degraded dependency, or only against an average day?
  5. If this component is the one degrading, does it fail fast and cheap, or does it hold caller resources hostage while it fails slowly?

None of these questions require an attacker in the scenario. They are true of accidental overload, deliberate overload, and everything in between, which is exactly why backpressure belongs with the rest of a system’s security posture rather than off to the side as a performance concern. A system that has thought through what it does when it is asked to do too much has already answered one of the more consequential security questions it will ever face: who gets to decide how much of this system’s capacity belongs to them.