Message Queues: Async Processing at Scale
How message queues decouple producers from consumers, survive worker crashes with retries and reassignment, and form the basis of pub-sub and event-driven systems.
With multiple servers processing jobs in parallel, a server can crash mid-job. The jobs it was holding are now unfinished — they must not be lost, and they must be reassigned.
The Reassignment Pattern
A monitor/notifier watches server health. On crash detection:
- Fetch the crashed server’s unfinished jobs from shared storage.
- Redistribute them to alive servers.
- Distribution uses consistent hashing via the load balancer.
Because consistent hashing maps the same job ID to the same server, jobs already running on healthy servers don’t get duplicated — only the dead server’s slice moves.
This pattern — durable job list, health checks, retry, redistribution — is essentially how a message queue works.
What a Message Queue Gives You
- Producers enqueue work; consumers process it. Neither knows about the other directly.
- Messages are persisted until acknowledged, so crashes don’t lose work.
- Consumers scale independently of producers (add workers at peak, drain backlog off-hours).
- Failed messages can be retried or routed to a dead-letter queue.
The Pub-Sub Model
Pub-sub generalizes the queue from one consumer per message to many:
- Publishers emit events (“order placed”).
- A broker (Kafka, RabbitMQ) routes them to topic subscribers.
- Subscribers consume independently — notifications, analytics, fraud checks all react to the same event without touching each other.
Key properties:
| Property | Effect |
|---|---|
| Services never call each other directly | Loose coupling; add consumers without touching producers |
| Asynchronous delivery | Producers aren’t blocked by slow consumers |
| Broker owns persistence/retries | Reliable delivery even when consumers are down |
The Trade-offs
- Slower than direct calls — an extra network hop and serialization layer.
- Eventual consistency — consumers lag producers; wrong fit when you need strong transactional guarantees.
- Operational complexity — brokers need setup, monitoring, capacity planning.
- Failure handling is your job — retries, duplicates, and ordering must be designed for (see Event-Driven Architecture for idempotency).
When to Reach For It
- Work that doesn’t need an immediate response (emails, thumbnails, analytics).
- Spike absorption (enqueue now, process later).
- Fan-out to multiple independent consumers.
Avoid it for request/response paths that need a synchronous answer.
Part of the Messaging series.
Related Notes
Event-Driven Architecture
Communicating through immutable, persisted events — replayability, idempotency, event sourcing, CQRS, and the trade-offs of eventual consistency.
Monolith vs Microservices
A practical comparison of monolithic and microservice architectures — coupling, deployment, scaling, team context — and how to choose between them.
Load Balancing & Consistent Hashing
How load balancers distribute traffic across servers, why naive hashing breaks on scale events, and how consistent hashing with virtual nodes solves it.