Message Queue
Async task queue that decouples producers from consumers and smooths bursts. Each message is processed exactly once (per-message ACK model).
Why it exists
Some work doesn't need to happen in the request/response cycle — sending a welcome email, resizing an image, computing an analytics rollup. If you do it inline, the user waits. If you drop it, you lose reliability. A message queue is the middle path: the producing service enqueues the work and returns immediately; a pool of workers pulls from the queue and processes at their own pace. This decoupling absorbs bursts and lets each side scale independently.
How it works
A queue is FIFO by default (with per-message-ACK semantics — the message stays visible until a worker explicitly acknowledges completion). Producers publish messages to a queue; workers poll the queue, process one message at a time, and ACK on success. On failure or timeout, the message becomes visible again for another worker (visibility timeout). Messages that fail N times land in a dead-letter queue for manual inspection. This differs from Kafka: queues do per-message ACK, not offset-based; consumption removes the message, not just advances a pointer.
Scaling characteristics
A single queue can handle 10K-100K messages/sec depending on message size and durability guarantees. Horizontal scale by adding partitions (like Kafka) or by splitting hot queues into topic hierarchies (per-user queues). Worker pools scale independently: 10 workers vs 1000 depending on the depth of the queue and processing time per message. Queue depth is your primary alarm — a growing queue means consumers are behind.
When to use it
- Background jobs (email, thumbnail generation, PDF rendering)
- Rate-smoothing bursty traffic — request comes in, task is enqueued, worker processes at bounded rate
- Retryable work with delayed retry semantics (backoff + DLQ)
- Fan-out from one producer to one consumer (unlike pub/sub which is 1-to-N)
- Isolating slow-but-non-critical work from the request path
When NOT to use it
- Sub-second delivery latency — queues typically buffer, adding ~50-500ms of latency
- Ordered event streams with replay (use Kafka)
- You need per-message durability guarantees stronger than what your queue offers — Kafka's disk-first design wins here
- Cross-region low-latency — most queue services are single-region or replicate with delay
Failure modes
- Worker crashes mid-message: visibility timeout returns the message to the queue; but if the worker committed a side effect (email sent) before crashing, you get a duplicate. Mitigate with idempotent workers.
- Poison message stalls the queue: one bad message keeps failing and blocks workers. Mitigate with DLQ + retry limit.
- Slow consumers create a backlog: queue depth grows, memory pressure at queue broker. Mitigate with autoscaling consumers + circuit breaker on producer.
- Message loss on broker failure if durability isn't configured — always enable replication + fsync for critical work.
- Fan-out amplification: one message triggers 10 downstream messages, each triggers 10 more — quickly overwhelms the queue. Mitigate with backpressure and per-tier rate limits.
Alternatives
- Kafka — for event streaming with replay + higher throughput; different semantics (offset-based, not per-message ACK)
- Cron + database polling — dead simple for low volume; doesn't scale but zero moving parts
- In-process job queue (Celery, Sidekiq) — piggybacks on Redis; great for smaller scale, harder to isolate failure
- Serverless (AWS SQS + Lambda) — managed queue + managed workers; cost-effective for spiky loads
Interview questions
- Your queue depth is climbing. What are the possible causes and how do you diagnose?
- A message fails 5 times. Where does it go, and what should you do about it?
- How would you guarantee exactly-once processing for a queue where each message triggers a payment?
- You're at 100K messages/sec on a single queue. When do you start sharding?
- How do you handle a message that requires a service that's currently down?
- Design a delayed-execution queue (message becomes visible after 24 hours).
Systems that use this component
See how the real designs on this platform put message-queue to work — concrete usage context per system.
URL Shortener
Async abuse detection (Safe Browsing API) via SQS
Open systemPayment System
Async webhook delivery to merchants (SQS with DLQ)
Open systemPhoto processing queue (resize, thumbnail, ML tagging)
Open systemYouTube
Transcoding queue for uploaded videos
Open systemDropbox
Sync notifications to connected devices
Open system1980s, IBM: banks need a way to talk to each other that survives crashes
Before there was "event-driven microservices", there was a very concrete problem: a bank teller in London needed to talk to a mainframe in Zurich, and neither could crash while the money moved. In 1983, IBM shipped MQ Series — a persistent, transactional message queue that survived power outages and network drops. Every enqueued message was written to disk. Every consumer explicitly acknowledged. If anything failed, replay from the log. For 30 years, this was how enterprises moved data reliably.
The open-source era started when ActiveMQ (2003) and RabbitMQ (2007) brought MQ patterns to everyone with a JVM or an Erlang install. Then in 2011, two things happened simultaneously: AWS SQS made queues serverless — no brokers to run, pay per message — and Apache Kafka reframed the queue as a durable log, not a mailbox. Consumers didn't "take" messages; they read from an offset. The old "delete after consume" assumption was gone.
Today the space is two overlapping models. The message queue model (SQS, RabbitMQ, Google Pub/Sub) offers per-message ACK, visibility timeout, and dead-letter queues — perfect for background jobs and task processing. The log model (Kafka, Pulsar, Redpanda) offers replayable offset-based consumption — perfect for event streams and audit trails. They're not competitors; they solve different problems. Most modern systems use both.
The core lesson: a queue is not a fancy list. It's a contract about failure. "If the worker crashes mid-task, the message will be redelivered." "If the network drops, the message survives." "If the same message arrives twice, my code will do the right thing." Building on top of a queue means writing code that can handle these promises being kept — retries, idempotency, and back-pressure — and knowing why they were made in the first place.
Historical timeline
- 1983IBM MQ Series launchesFirst transactional persistent queue. Powers banking and airline reservations for 4 decades.
- 1993TIBCO RendezvousPublish-subscribe messaging for financial markets. Pattern gets codified.
- 2001JMS specificationJava Message Service standardizes queue APIs across vendors — MQ Series, Sonic, WebSphere, WebLogic.
- 2003Apache ActiveMQFirst widely-used open-source Java queue. JMS-compliant. Powers Camel, Mule, and countless enterprise ESBs.
- 2006AMQP protocolWire protocol standard for messaging emerges — JPMorgan-led. Breaks the vendor lock-in of MQ Series.
- 2007RabbitMQ 1.0Erlang-based AMQP implementation. Flexible routing (topic, direct, fanout, headers). Becomes the default enterprise choice.
- 2011AWS SQS goes GAServerless queue. No brokers to run. Pay per message. Democratizes async processing for every startup.
- 2011Apache Kafka open-sourcedLinkedIn releases Kafka. Redefines the queue as a durable log. Consumers use offsets, not ACKs. Enables event sourcing at scale.
- 2012NATS by Derek CollisonUltra-low-latency messaging in Go. "fire and forget" for high-throughput internal communication. Powers Cloud Foundry.
- 2014Apache PulsarYahoo builds a "messaging + streaming" system. Compute + storage separation. Multi-tenant from day one. Later becomes a Kafka alternative.
- 2015Redis Streams announcedAntirez adds a Kafka-style log to Redis. Powers Sidekiq alternatives, small-scale event streams. Simple and dev-friendly.
- 2017Google Pub/Sub GAServerless publish-subscribe with global scale. Ordering keys added later (2020). Deep BigQuery integration.
- 2019SQS FIFO + exactly-onceAWS SQS adds FIFO queues with deduplication. Message ordering + exactly-once for use cases that need it (with throughput trade-off).
- 2021Redpanda releasesKafka-compatible in C++. No JVM, no ZooKeeper. Lower latency. Focus on modern deployments.
- 2024AI queues emergeLLM providers offer queue-style APIs (OpenAI Batch API). Async LLM inference reshapes what a "job queue" looks like.
Queue vs Pub/Sub: two totally different fan-out models
Everyone says "queue" when they mean one of two things. Getting this right is the difference between "one worker did the job" and "three services all reacted to the event."
Queue (point-to-point)
Producer enqueues → exactly ONE consumer processes → done.
Producer → [ msg1 msg2 msg3 ] → Worker A (msg1)
→ Worker B (msg2)
→ Worker C (msg3)Pub/Sub (topic-based)
Producer publishes to topic → ALL subscribers receive a copy → each independently processes.
Producer → [ topic:order.placed ]
→ Analytics Service (copy)
→ Email Service (copy)
→ Warehouse Service (copy)Three delivery guarantees — pick your poison
Distributed messaging is one of the classic "you can't have all three" corners. The three delivery guarantees each trade something for something:
At-least-once (default in SQS, RabbitMQ, Kafka)
Visibility timeout: how queues handle worker crashes
The magic that makes "at-least-once" delivery real is visibility timeout. When a worker takes a message, it becomes invisible to others. If the worker doesn't ACK within the timeout, the message reappears. Watch it play out:
1. Message enqueued
Producer sends msg-42 to queue. It sits visible, waiting.
Dead Letter Queue (DLQ): how to handle poison messages
Some messages just can't be processed — malformed JSON, missing external dependency, a customer that was deleted while the message sat in the queue. If you retry forever, one bad message blocks the queue. The answer is the DLQ: after N retries, move the message to a separate queue for humans to inspect.
Consumer groups: horizontal scaling made easy
A single queue with N worker processes forms a consumer group. Messages are load-balanced across workers automatically. Add workers → linear throughput scaling. Try it:
Product comparison
| Product | Model | Type | Strength | Weakness |
|---|---|---|---|---|
| AWS SQS Standard | Managed | Queue | Trivial to use. Serverless. Auto-scale. Cheap at scale. | At-least-once (dup). No ordering (Standard). Message size ≤256KB. |
| AWS SQS FIFO | Managed | Queue | Exactly-once + strict ordering per group. Idempotency built-in. | 300 msg/sec limit per API (3000 with batching). More expensive. |
| RabbitMQ | OSS + Managed | Queue/Pub-sub | Flexible routing (topic, direct, fanout, headers). AMQP standard. Mature. | Erlang ops. Cluster complexity. Throughput ceiling ~50K/sec. |
| Apache Kafka | OSS + Managed | Log | Millions of msg/sec. Replay. Multi-consumer. Event sourcing native. | Operational complexity (ZK/KRaft). Overkill for simple tasks. |
| Google Pub/Sub | Managed | Pub-sub | Global scale. Deep BigQuery integration. Ordering keys. | GCP-only. Slower than SQS. Cost ramps. |
| Azure Service Bus | Managed | Queue/Pub-sub | Sessions (message groups). Duplicate detection. Deep .NET integration. | Azure-only. Complex tier model. Message size limits. |
| NATS + JetStream | OSS + Managed | Pub-sub/Log | Sub-ms latency. Simple. Cloud Native (CNCF). Global-scale via clusters. | Younger than Kafka. Persistence in JetStream still evolving. |
| Apache Pulsar | OSS + Managed | Log/Queue hybrid | Multi-tenant. Compute + storage separation. Geo-replication native. | Complex ops (BookKeeper). Smaller community than Kafka. |
| Redpanda | OSS + Enterprise | Log (Kafka-compat) | C++ rewrite. No JVM/ZK. Lower latency. Same client API as Kafka. | Younger. Fewer features than Kafka. Smaller ecosystem. |
| Redis Streams | OSS + Managed | Log | Redis simplicity. Sub-ms latency. Consumer groups + persistence. Cheap. | In-memory limits. Not for petabyte replay. Single-writer per shard. |
| Sidekiq (Redis-backed) | OSS | Queue | Best DX for Ruby. Millions of jobs/day. Simple. | Ruby-only. Redis dependency. Not for exactly-once. |
| Temporal | OSS + Managed | Workflow (queue+state) | Durable execution. Retries + state machine built in. Great for long workflows. | Not a raw queue — different mental model. Ops complexity for self-hosted. |
How to choose: AWS + simple tasks → SQS Standard. Financial ordering → SQS FIFO. Enterprise routing → RabbitMQ. Event streams + replay → Kafka. GCP-native → Pub/Sub. Ultra-low latency internal → NATS. Ruby monolith → Sidekiq. Long workflows → Temporal.
12 real-world message queue deployments
SQS runs trillions of messages/day
SQS is one of AWS's oldest services (2004 internal, 2006 public). Every AWS customer uses it — order processing, workflow steps, retries. Trillions of messages/day. Behind the scenes: massive distributed storage with per-message ACK semantics + visibility timeout as the correctness primitive.
Sidekiq processes billions of Ruby jobs/day
Shopify runs its Ruby background jobs on Sidekiq — Redis-backed queues. Every product update, inventory sync, email send goes through it. During Black Friday: 76M req/min surface, but many more billions of async jobs. Sidekiq Pro used for reliability.
SQS for driver-rider matching
Uber uses SQS at massive scale for dispatch — when a rider requests a ride, the request enters a queue that dispatchers (partitioned by geographic hex) pull from. Absorbs surge pricing storms. Handles retries when GPS drops.
RabbitMQ for booking workflow
Airbnb uses RabbitMQ for the multi-step booking workflow — hold inventory, charge card, notify host, send confirmation. Each step is a queue. Failure at any step triggers rollback via compensating messages. Classic saga pattern.
Kafka + SQS for chat delivery
Twitch uses Kafka for chat message events (need replay + massive fanout to viewers) plus SQS for moderation actions (need ACK + retry semantics). Different tools per shape.
Celery + Redis for async work
Instagram runs Celery (Python task queue) with Redis broker for backend jobs — feed refresh, notification delivery, image processing. Sharded by user ID hash. Now moving parts to Kafka for higher throughput.
SQS + Kafka for message ingest
Discord uses SQS for user-triggered async actions (thumbnail generation, permission updates) and Kafka for message events (fanout to Push + Search + Analytics). Two shapes of "async" problems.
SNS→SQS fanout for events
Slack's standard: publish events to SNS, fan out to SQS queues per consumer. Adding a new feature = subscribe a new SQS to SNS. Zero producer changes. Every backend team owns their own queue.
SQS FIFO for trade execution
Robinhood uses SQS FIFO for trade orders — strict ordering per user, exactly-once semantics. Each user is a message group. Trades within a group execute in submitted order.
Custom queue on top of Workers KV + Durable Objects
Cloudflare's Queues product runs at 300+ edge PoPs using Durable Objects for state. Handles millions of msg/sec at edge. Sub-100ms delivery. Powers customers building on Workers.
Ultra-low-latency internal
Pivotal's Cloud Foundry used NATS for internal control plane messaging — sub-ms delivery between hundreds of internal services. Pattern: NATS core (fire-forget) for speed, JetStream (persistent) for durability.
The AI-era job queue
OpenAI's Batch API is a modern queue: submit prompts async, get results within 24h, 50% cheaper than sync. Under the hood: a job queue with prioritization, retries, and result storage. Powers embedding pipelines, dataset labeling, RAG indexing.
Key takeaways
- 1A message queue is a contract about failure — retries, redelivery, at-least-once, dead-letter queue. Build with that contract in mind.
- 2Distinguish queue (point-to-point) from pub/sub (topic fan-out). Most modern stacks use SNS→SQS to combine both.
- 3At-least-once + idempotent workers is the correct default. Exactly-once is real but limited in scope and adds complexity.
- 4Visibility timeout is the mechanism that turns crash-recovery into a solved problem. Set it > expected task time. Consumers must be idempotent.
- 5DLQ + exponential backoff handles poison messages without blocking the queue. Alert on DLQ growth; drain periodically.
- 6Consumer groups = horizontal scale for free. Add workers to increase throughput. Kafka partitions cap that scale; plan them.
- 7Use a queue for tasks, a log for events. Same "messaging" category, different problem shapes.
- 8For most workloads, managed queues (SQS, Google Pub/Sub, Azure Service Bus) remove 80% of operational pain and cost less than self-hosted RabbitMQ/Kafka.
References & further reading
- • Hohpe, G. & Woolf, B. (2003). Enterprise Integration Patterns. Addison-Wesley. Foundational catalog of messaging patterns.
- • Kreps, J. (2014). "I Heart Logs." O'Reilly. Why the log is the primitive of distributed data.
- • AWS Docs: "How the Amazon SQS Message Lifecycle Works." The definitive reference on visibility timeout + at-least-once.
- • RabbitMQ Docs: "Reliability Guide." Best writeup on acks, confirms, and idempotency.
- • Kafka Improvement Proposals: KIP-98 (exactly-once semantics). How Kafka transactions actually work.
- • Ellis, J. (2016). "Exactly-once Support in Apache Kafka." Confluent blog. Excellent deep-dive.
- • Sean T. Allen (2018). "Message Broker Comparison Guide." Detailed head-to-head of RabbitMQ, Kafka, NATS, Pulsar.
- • Aphyr (Kyle Kingsbury) Jepsen posts: Rigorous consistency tests of every major broker. Read before trusting exactly-once claims.
- • Google Pub/Sub docs: "Message delivery semantics." Best modern managed-pubsub explanation.
- • Kleppmann, M. (2017). DDIA, Chapter 11 (Stream Processing) covers queues + logs together.