LESSON 9.2
Producer-consumer
Decouple components producing work from components processing work via a shared bounded queue, enabling backpressure and smooth rate leveling.
6 min read
Consider video ingestion, log aggregation, or order processing. External webhooks produce events in sudden, unpredictable spikes. If worker processes handle every event synchronously upon arrival, the backend server runs out of database connections and thread resources, collapsing under the surge.
The problem
// Synchronous direct processing: burst causes server exhaustion
app.post("/webhook", async (req, res) => {
// If 5,000 webhooks arrive in 1 second, 5,000 simultaneous DB queries fire!
await heavyDatabaseInsert(req.body); // Server crashes!
res.send("OK");
});The pattern
The Producer-Consumer pattern introduces a synchronized Bounded Buffer between the producers and consumers.
- Producers push items into the buffer. If the buffer is full, producers wait (backpressure).
- Consumers pull items from the buffer. If the buffer is empty, consumers wait.
The intent: Decouple components producing work from components processing work via a shared bounded queue, enabling backpressure and smoothing bursty workloads.
In code
When to use it, and when not to
Use Producer-Consumer when:
- Production of items and consumption of items proceed at varying, bursty rates.
- You want to smooth out load spikes and protect downstream databases from being overwhelmed.
- You want to decouple the identity, lifecycle, and number of producers from consumers.
Don't use Producer-Consumer when:
- Callers require immediate, synchronous results in the same HTTP turn: an asynchronous queue model requires polling or WebSockets to return results.
Where you have already seen it
Message brokers like Apache Kafka, RabbitMQ, and AWS SQS are enterprise distributed producer-consumer queues. In Node.js, stream.Readable and stream.Writable with .pipe() implement streaming backpressure. In the JVM, logging frameworks like Logback use asynchronous AsyncAppender with a BlockingQueue to prevent file writes from blocking business threads.
Related patterns
- Producer-Consumer vs Observer: Observer is synchronous and pushes notifications to all listeners immediately. Producer-Consumer buffers messages for consumption at a controlled pace.
- Producer-Consumer and Thread Pools: Thread pools use an internal producer-consumer queue where tasks are submitted by clients and pulled by worker threads.
Try it
Bounded Blocking Queue LLD
Implement a bounded queue that supports timeouts on put() and take() operations.