Skip to main content

Missing Back-Pressure

Missing back-pressure lets a fast producer outpace a slow consumer with no way to signal slow down, so queues grow unbounded until memory or downstreams collapse. Use bounded queues, reactive-streams demand, and load shedding so overload degrades gracefully.

Back-pressure is the signal a slow consumer sends upstream to tell a fast producer to slow down. Missing back-pressure means there is no such signal: the producer keeps emitting work regardless of whether the consumer can keep up, and the excess piles into an unbounded buffer or queue. As long as the queue can grow, the system looks healthy — until it can't.

Why It Happens

Fire-and-forget is the easy default: enqueue work, return immediately, let something else deal with it. Unbounded in-memory queues, channels, or message buffers absorb mismatches invisibly during normal operation, so the missing flow control is never noticed. Asynchronous and event-driven designs make it especially easy to accept work faster than it can be processed. The gap only matters when input outpaces processing for a sustained period — a traffic burst, a slow dependency, a batch job.

Why It Hurts

When the producer is faster than the consumer, the buffer grows without bound and memory is consumed until the process is OOM-killed — a hard crash instead of graceful slowdown. Even before that, deep queues mean stale work and ballooning latency: by the time an item is processed, it may be irrelevant. Without back-pressure the overload propagates downstream, overwhelming the next service and cascading. The system has no way to say "I'm full"; it can only fall over.

Warning Signs

  • Unbounded queues, channels, or buffers between stages.
  • Queue depth or lag that grows steadily under load and never drains.
  • Memory climbing during bursts, ending in OOM.
  • Latency that increases the longer the system runs hot.

Better Alternatives

  • Bounded queues that block or reject when full, forcing the producer to wait or shed.
  • Reactive-streams back-pressure: consumers request N items; producers send no more than requested (Reactive Streams, RxJava, Project Reactor, Akka Streams).
  • Pull-based / demand-driven processing instead of push.
  • Load shedding and rate limiting: reject or drop excess at the edge to protect the core.
  • Flow control at the protocol level (TCP windows, HTTP/2 flow control, Kafka consumer lag-based pause).

How to Refactor Out of It

Replace unbounded buffers with bounded ones and decide an explicit policy for "full": block the producer, drop oldest/newest, or reject with an error the caller can handle. Where stages are connected, adopt a demand-driven model so consumers pull (or request) work at their own rate and producers cannot outrun them. At system edges, add rate limiting and load shedding so excess is refused fast rather than queued. Monitor queue depth and lag as first-class signals. Test with a sustained burst and confirm the system slows or sheds gracefully instead of growing memory until it crashes.