Backpressure
Backpressure lets a slow consumer signal producers to slow down, keeping buffers bounded and preventing memory exhaustion when demand outpaces capacity. It is central to Reactive Streams and TCP flow control, and degrades into load shedding when producers cannot be paused.
When a producer generates work faster than a consumer can process it, the excess has to go somewhere. If it accumulates in an unbounded buffer, memory grows until the system crashes; if it is silently dropped, data is lost. Backpressure is the mechanism by which a slow consumer signals upstream that it cannot keep up, so producers slow down, pause, or buffer until capacity is available. It is flow control for data pipelines and asynchronous systems.
How It Works
Backpressure replaces "push as fast as you can" with a negotiated rate. Common mechanisms:
- Demand signaling (pull): the consumer requests N items; the producer sends at most N. This is the core of the Reactive Streams specification (used by Project Reactor, RxJava, Akka Streams) and of TCP flow control's receive window.
- Bounded queues with blocking: a full buffer blocks the producer until space frees up, propagating slowness backward.
- Bounded queues with rejection: a full buffer rejects new work (overlapping with load shedding) when blocking is unacceptable.
- Credit-based flow control: the consumer grants credits the producer must spend to send, as in HTTP/2 and many message brokers.
The shared principle is bounded buffers plus a feedback path so pressure travels from consumer to producer.
When to Use It
Use backpressure in streaming and event-processing pipelines, in producer/consumer systems where rates can diverge, in reactive applications, and anywhere an unbounded queue could exhaust memory. It is essential for ingestion systems, log/metrics pipelines, and any service consuming from a firehose it cannot pause at the source.
Trade-offs
Backpressure that blocks producers can propagate slowness all the way to the user, turning a throughput problem into a latency problem. It can also deadlock if cycles exist in the flow graph. When producers cannot be slowed (e.g. external sensors, market data), the only options are buffering with limits or dropping — backpressure then degrades into load shedding. Implementing true end-to-end backpressure across service boundaries is harder than within one process.
Related Patterns
Backpressure is the upstream-signaling counterpart to load-shedding (downstream dropping), and relates to rate-limiter and throttling (bounding rate proactively) and to queue-based-load-leveling (absorbing bursts in a buffer).
Example
Reactive Streams demand:
subscriber.onSubscribe(s):
s.request(10) // I can handle 10
subscriber.onNext(item):
process(item)
s.request(1) // ask for one more as capacity frees
The publisher never sends more than the subscriber has requested, so the buffer stays bounded under any producer speed.