Message Queues
Updated June 6, 2026Picture a busy restaurant kitchen. Orders come in from the dining room constantly, sometimes in bursts when a large group sits down. The kitchen doesn't grind to a halt waiting for each order to be perfectly timed with a table's arrival. Instead, orders go on a ticket rail, and the cooks work through them at the pace the kitchen can sustain.
That ticket rail is a message queue.
What Message Queues Do
A message queue is a buffer between a producer and a consumer. The producer puts messages on the queue. The consumer takes them off and processes them independently, at its own pace.
This decoupling is powerful:
- The producer doesn't need to know who will process the message, or when
- The consumer doesn't need to be running when the message is sent
- If the consumer is busy or slow, messages wait in the queue rather than being lost or causing the producer to block
- If traffic spikes, the queue absorbs the burst — producers keep writing, consumers process at a sustainable rate
Message queue — producer publishes, queue buffers, consumer processes with DLQ for failures
Without a queue, when your checkout service needs to send a confirmation email, it has to call the email service directly. If the email service is slow, checkout is slow. If the email service crashes, checkout crashes. With a queue, checkout drops a message and moves on — the email service processes it whenever it's ready.
Queue Semantics You Should Know
FIFO (First-In, First-Out)
The default and most intuitive: messages are processed in the order they were received. Most queues give you FIFO within a queue or partition. Amazon SQS has an explicit "FIFO queue" type that provides strict ordering guarantees.
Strict FIFO at scale is expensive. It requires coordination to ensure ordering, which limits throughput. Many systems only need best-effort ordering, not strict FIFO.
Priority Queues
Some work matters more than others. A priority queue lets you attach a priority to each message — a "high priority" message jumps ahead of "low priority" ones regardless of arrival order.
RabbitMQ supports priority queues natively. Useful for scenarios like: process premium users' video transcodes before free-tier ones, or handle urgent fraud alerts before routine batch jobs.
Dead Letter Queues (DLQ)
This is one of the most important concepts in production queue systems. When a message can't be processed after N retries — maybe the payload is malformed, maybe it triggers a bug — you don't want it looping forever and blocking other messages.
A dead letter queue is where failed messages go. Once a message hits the DLQ, it's out of the main processing flow but preserved for inspection, debugging, and manual reprocessing.
Every production queue setup should have a DLQ with alerting on it. If messages start piling up in your DLQ, something is wrong.
At-Least-Once vs Exactly-Once Delivery
Most message queues guarantee at-least-once delivery: a message will be delivered to a consumer, but might be delivered more than once if the consumer crashes after processing but before acknowledging.
Exactly-once delivery is much harder to guarantee and comes with significant overhead. Most systems don't bother — instead, they design consumers to be idempotent (processing the same message twice has the same effect as processing it once).
What is the purpose of a Dead Letter Queue (DLQ)?
Real Systems
RabbitMQ
The classic open-source message broker. Built on AMQP (Advanced Message Queuing Protocol). Supports complex routing via exchanges and bindings, priority queues, dead letter queues, and message TTLs. Good for traditional work queues and point-to-point messaging. Less suited for very high throughput or event streaming use cases (that's Kafka's territory).
Use it for: task queues, job distribution, microservice communication, anything needing flexible routing rules.
Amazon SQS
AWS's fully managed message queue. You don't manage any infrastructure — just send and receive messages via the API. Two flavors: Standard (higher throughput, best-effort ordering) and FIFO (strict ordering, lower throughput). SQS integrates natively with Lambda, EC2, and other AWS services.
Use it for: AWS-native architectures, serverless workflows, simple reliable work queues without operational overhead.
ActiveMQ
The older, enterprise-grade message broker. Supports JMS (Java Message Service), multiple protocols (AMQP, STOMP, MQTT), and complex configurations. Common in large enterprise Java shops.
Use it for: legacy enterprise integrations, JMS compatibility requirements, complex routing in enterprise environments.
Redis (as a queue)
Redis Lists and Streams can be used as lightweight queues. RPUSH/BLPOP gives you a basic FIFO queue. Redis Streams give you something closer to a proper message broker with consumer groups and ACKing. It's simple and fast, but Redis isn't purpose-built for queueing — you lose messages if Redis goes down without persistence configured, and there's no DLQ concept out of the box.
Use it for: lightweight job queues in small systems, delayed jobs (with Sidekiq, Bull, etc.).
What is the competing consumers pattern, and why does it simplify horizontal scaling?
Use Cases
Order Processing
A customer places an order → order service writes an "order.placed" message to the queue → multiple workers consume it:
- Inventory service decrements stock
- Notification service sends confirmation email
- Fraud detection service runs risk analysis
- Analytics service records the sale
All independently, all at their own pace.
Email and Notification Sending
Email sending is inherently async — there's no reason to block an API response waiting for an SMTP server. Put the "send this email" task on a queue, let an email worker handle it, retry on failure.
Video Transcoding
When a user uploads a video, transcoding into multiple formats is expensive and takes minutes. The upload endpoint puts a transcoding job on a queue. A pool of transcoding workers pick up jobs and process them. The user gets a "Processing..." message and comes back later.
Background Jobs in Web Apps
Database cleanup, generating reports, sending weekly digests, recomputing recommendations — any work that doesn't need to happen in the request/response cycle should be a background job, and background jobs need a queue.
Scaling with Queues
One of the best things about queues is how naturally they enable horizontal scaling.
Have more messages than one worker can handle? Add more workers. They all consume from the same queue. Each message goes to exactly one worker (competing consumers pattern). No coordination needed — the queue handles it.
Competing consumers — add workers to scale, queue distributes work automatically
Want to slow down processing to avoid overwhelming a downstream database? Limit your workers. The queue acts as a natural throttle.
This is fundamentally different from direct service-to-service calls, where scaling means scaling both the caller and the callee in lockstep.
Why does at-least-once delivery require consumers to be idempotent?
Summary
Message queues are the backbone of resilient, scalable distributed systems. By buffering messages between producers and consumers, they absorb traffic spikes, isolate failures, and enable independent scaling. Key concepts to know: FIFO ordering, priority queues for mixed-priority workloads, and dead letter queues for handling messages that can't be processed. The major systems — RabbitMQ (flexible routing), Amazon SQS (managed, AWS-native), and ActiveMQ (enterprise Java) — each have different strengths. In practice, queues show up everywhere: order processing, email sending, video transcoding, background jobs. If a task doesn't need to happen synchronously in a request's path, it should probably be on a queue.
Saved on this device only
Sign in to sync progress across devices