Backpressure Handling
Overview
Backpressure is a feedback mechanism in an asynchronous system that prevents a fast producer from overwhelming a slow consumer. When the consumer's queue starts to fill up, it signals the producer to slow down, drop messages, or buffer them, ensuring the system doesn't crash from memory exhaustion.
Key Concepts
Without backpressure, a consumer processing messages slower than they arrive will eventually run out of memory (if unbounded queue) or cause cascading timeouts. Backpressure introduces control flow.
Strategies for Handling Backpressure
- Control / Push-back (The Ideal): The consumer explicitly tells the producer its capacity (e.g., Reactive Streams protocol). The producer only sends what the consumer can handle.
- Drop (Shedding Load): If the queue is full, new incoming messages are immediately discarded. Useful for telemetry or metrics where missing a data point is acceptable.
- Buffer (The Shock Absorber): Place a highly scalable message queue (like Kafka) between them. The broker absorbs the spike, and the consumer pulls at its own pace. This converts a push system into a pull system.
- Block (The Synchronous Brake): The producer's thread blocks when attempting to push to a full queue. This naturally slows the producer but can tie up resources and bubble up to the end-user as a timeout.
| Strategy | When to use | Consequence |
|---|---|---|
| Control (Reactive) | When you control both sides and need safety. | Smooth flow, requires protocol support. |
| Drop | When data freshness matters more than completeness. | Data loss. |
| Buffer (Queue) | When dealing with unpredictable, massive spikes. | Increased latency during spikes; requires infrastructure. |
| Block | Internal thread pools; bounded in-memory queues. | Can cause upstream bottlenecks and thread starvation. |
Trade-offs
Buffering via a message broker (Kafka, SQS) is the standard system design solution for backpressure, as it durably saves messages and decouples the systems. However, it trades infrastructure complexity and latency for reliability. Dropping messages is cheap and keeps the system fast, but sacrifices correctness. True reactive backpressure requires end-to-end support (like gRPC or ReactiveX) which isn't always possible when integrating with third-party systems.
Interview Tips
- When an interviewer asks "what happens if a million users hit this API at once?", "we use a message queue to absorb the spike and provide backpressure" is the standard answer.
- Differentiate between a push model (where the broker pushes to the consumer, risking overwhelming it) and a pull model (where the consumer asks for messages when ready, naturally handling backpressure).
- Mention bounded queues. An unbounded queue is a memory leak waiting to happen under load.
Summary
- Backpressure prevents a fast producer from crashing a slow consumer.
- The best systemic solution is placing a durable message broker (like Kafka) between them.
- It converts a system from a dangerous 'push' model to a safe 'pull' model.
- Strategies include buffering, dropping messages, blocking the producer, or reactive control.
- Unbounded queues without backpressure eventually lead to Out of Memory (OOM) errors.