Messaging patterns comparison
Message Queue vs Pub/Sub
Two fundamental patterns for asynchronous communication between services. Getting this distinction right is the first step to designing a reliable event-driven architecture — and it comes up in virtually every microservices system design discussion.
TL;DR
- Message Queue — point-to-point. One producer, one consumer processes each message. Message is removed once acknowledged. Best for task distribution and work queues. Examples: RabbitMQ (default queue), SQS.
- Pub/Sub — broadcast. One publisher, many subscribers each receive a copy. Best for event notification and fan-out. Examples: Kafka topics, SNS, Google Pub/Sub.
- They complement each other. The SNS → SQS pattern (fan-out to isolated queues) is one of the most common patterns in AWS-based microservices.
Side-by-side comparison
| Aspect | Message Queue | Pub/Sub |
|---|---|---|
| Delivery model | Point-to-point: one consumer per message | Broadcast: all subscribers receive a copy |
| Ordering | Typically FIFO within a queue | Not guaranteed across subscribers |
| Persistence | Message waits until consumed (durable) | Varies — Redis drops, Kafka retains by offset |
| Fan-out | One message → one consumer | One message → all subscribers |
| Coupling | Producer knows there is a queue | Publisher knows nothing about subscribers |
| Load balancing | Natural — competing consumers share work | Each subscriber does all the work independently |
| Use case | Task queues, email sending, job workers | Event streaming, notifications, audit logs |
| Examples | RabbitMQ, Amazon SQS, Celery | Apache Kafka, Amazon SNS, Google Pub/Sub, Redis Pub/Sub |
Code side-by-side
Publishing an order event and consuming it:
Message Queue (SQS — one worker)
// Producer: send task to queue
await sqs.sendMessage({
QueueUrl: ORDER_QUEUE_URL,
MessageBody: JSON.stringify({
orderId: 'ord_123',
action: 'send_confirmation_email',
}),
}).promise();
// Consumer: exactly one worker picks this up
// Other workers get different messages Pub/Sub (SNS → multiple SQS queues)
// Publisher: broadcast event
await sns.publish({
TopicArn: ORDER_EVENTS_TOPIC,
Message: JSON.stringify({
orderId: 'ord_123',
event: 'order_placed',
}),
}).promise();
// SNS fans out to:
// → SQS queue for email service
// → SQS queue for fulfilment service
// → SQS queue for analytics When to use a Message Queue
- Task distribution. You have a pool of workers and want to share work evenly — image resizing, email sending, PDF generation.
- Exactly-once processing required. Each job must be done by exactly one worker; duplicate processing would cause errors (double charge, double send).
- Rate limiting downstream services. Queue depth acts as a natural buffer; consumers process at a sustainable rate even when producers burst.
- Retry with backoff. Failed messages go to a dead-letter queue for inspection and reprocessing without blocking healthy messages.
When to use Pub/Sub
- Event notification to multiple services. A user signs up → auth service, email service, analytics service, and onboarding service all need to know.
- Audit logging and observability. Every service publishes events; a central subscriber records them all without the publishers knowing.
- Decoupling producers from consumers. New subscribers can be added without changing the publisher — true open/closed principle at the architecture level.
- Event sourcing and stream processing. Kafka topics retain all events; consumers replay history to rebuild state or run analytics.
English phrases engineers use
Message Queue conversations
- "Messages pile up in the queue when consumers are slow."
- "Failed messages go to the dead-letter queue (DLQ)."
- "We have competing consumers — 5 workers share the same queue."
- "The consumer must acknowledge the message or it's requeued."
- "We set a visibility timeout to prevent duplicate processing."
Pub/Sub conversations
- "We publish an event to the topic; subscribers react independently."
- "The email service subscribes to the user-signup topic."
- "We do fan-out via SNS → multiple SQS queues."
- "Kafka consumers track their offset and can replay old events."
- "The publisher is decoupled — it doesn't know who's listening."
Quick decision tree
- One consumer should process each message → Message Queue
- Multiple independent services need the same event → Pub/Sub
- Task distribution among worker pool → Message Queue
- Audit log / event stream → Pub/Sub (Kafka)
- Need replay of past events → Pub/Sub (Kafka)
- Need dead-letter queue and retry → Message Queue (SQS/RabbitMQ)
- Need both broadcast AND isolated processing → SNS + SQS fan-out
Common Mistakes and Trade-offs
A pervasive mistake developers make when initially evaluating message queues versus pub/sub systems is treating them as interchangeable solutions for asynchronous communication. Many teams assume that adding a queue will magically solve all their decoupling problems, overlooking the fundamental differences in delivery guarantees and routing capabilities. This often manifests as using a simple FIFO queue to handle complex event-driven workflows where exactly-once semantics are critical; this can lead to significant data corruption or duplicated processing if not carefully managed with idempotent consumers and robust error handling – something that's far easier to achieve with a system designed for publish/subscribe from the ground up. It's easy to fall into the trap of thinking 'queue = decoupling,' but true decoupling requires thoughtful design regardless of the technology.
At scale, a significant trade-off emerges involving message ordering and eventual consistency that's frequently underestimated with traditional message queues. While guarantees about strict ordering are often advertised, maintaining this across a large cluster – especially when consumers operate independently and potentially concurrently – introduces substantial operational complexity. Pub/Sub systems, particularly those built on distributed log platforms, inherently handle ordering within a consumer's view, but at the cost of eventual consistency for other consumers. This means that even if you meticulously configure your queue for serial delivery, you're still battling against potential out-of-order messages in downstream processing; the operational overhead to manage this is substantially higher than simply accepting eventual consistency and its associated complexity.
The misconception that pub/sub systems are 'always better' for event-driven architectures ignores the critical role of consumer design. A poorly designed consumer, regardless of the underlying technology – whether it's a message queue or pub/sub – can quickly become a bottleneck. If consumers aren't optimized to handle bursts of traffic, or if they lack proper rate limiting and backpressure mechanisms, the entire system can grind to a halt. Furthermore, the flexibility of pub/sub systems – while powerful – demands more rigorous monitoring and tracing; without these, it's extremely difficult to identify performance issues or diagnose failures. The ease of adoption shouldn't overshadow the need for careful architectural planning and robust consumer development.
Migrating from a message queue-centric architecture to a pub/sub model (or vice versa) isn't simply a matter of swapping components; it often necessitates a fundamental shift in thinking about data ownership and responsibility. Consider scenarios where multiple teams rely on the same event stream – with queues, this frequently leads to tightly coupled systems and complex coordination mechanisms. Pub/sub promotes a more decentralized approach, but requires careful governance around schema evolution and versioning. Combining elements of both approaches—using message queues for short-lived, transactional tasks within a pub/sub ecosystem—can be an effective strategy, allowing you to leverage the strengths of each technology while mitigating their respective weaknesses. However, this hybrid model introduces its own complexities regarding routing, transformation, and overall system management.
Frequently asked questions
What is a message queue in plain English?
A message queue is a holding area for messages sent between services, with a point-to-point delivery model: one producer sends a message, and exactly one consumer processes it. Once consumed and acknowledged, the message is gone. Think of a task queue — one worker picks up each job. Examples: RabbitMQ, Amazon SQS.
What is the Pub/Sub pattern?
Publish/Subscribe (Pub/Sub) decouples producers (publishers) from consumers (subscribers) through topics. When a publisher sends a message to a topic, every subscriber to that topic receives a copy. One event, many receivers. Examples: Google Pub/Sub, Amazon SNS, Kafka topics, Redis Pub/Sub.
Can a message queue fan out to multiple consumers?
A single queue is point-to-point. To fan out with a queue-based system (like SQS), you combine it with a Pub/Sub layer — SNS broadcasts to multiple SQS queues, each with its own consumer. RabbitMQ achieves this with exchange types (fanout, topic).
Which preserves message ordering better?
Both can preserve ordering, but with constraints. SQS standard queues do not guarantee ordering; SQS FIFO queues and RabbitMQ single-queue setups do. Kafka preserves ordering within a partition. Pub/Sub systems with multiple subscribers and parallel delivery typically do not guarantee cross-subscriber ordering.
What happens if a consumer is down?
In a message queue, the message waits (up to a configurable retention period) until a consumer comes back up. This is the durability guarantee. In a basic Pub/Sub system (e.g., Redis Pub/Sub), messages sent while a subscriber is offline are lost. Kafka Pub/Sub is durable — subscribers catch up from their last offset.
When would I use both patterns together?
A common architecture: an order-placed event is published to a Pub/Sub topic (SNS), which fans out to multiple SQS queues — one for the email service, one for the fulfilment service, one for analytics. Each downstream service processes at its own pace via its dedicated queue.