Concept · Async work & messaging
Backpressure
Let a slow consumer slow the producer down instead of drowning in a growing queue.
Backpressure is any mechanism by which a component that can't keep up signals upstream to send less. Without it, the excess piles up somewhere — usually in an unbounded queue or buffer — until memory runs out or every item waits so long that it's useless.
Why unbounded queues fail
By Little's law, wait time = queue length / processing rate. If producers send 1,200 messages per second and consumers handle 1,000, the queue grows by 200 per second; after ten minutes, a new message waits two minutes. For a request–response system, that means every request times out — the server is busy, but doing work nobody will receive.
Forms of backpressure
- Bounded queues that block or reject when full. In Go, a buffered channel blocks the sender when it's full: that is backpressure.
- Explicit rejection: return
429or503withRetry-Afterso callers slow down (load shedding). - Flow control in protocols: TCP's receive window, HTTP/2 stream windows and gRPC streaming all stop the sender when the receiver's buffer fills.
- Pull-based consumption: consumers fetch when ready (Kafka consumers, reactive streams with demand signals), so a slow consumer simply falls behind rather than being overwhelmed.
In asynchronous pipelines
With a durable log, falling behind is safe for a while — the backlog is stored — but you must notice it:
- Monitor consumer lag (messages or seconds behind the head) and alert when it grows.
- Scale consumers with lag, up to the partition count.
- Decide what happens if lag exceeds retention: messages are deleted unread.
The principle
Every queue should have a bound and a policy for what happens at the bound: block, reject, drop the oldest, or drop the newest. Choosing that policy deliberately is the difference between graceful overload and collapse.