CMD Guide
HomeSystem DesignDistributed File System

Message Queues vs Service Bus

The real fork: point-to-point vs publish/subscribe

"Message queue vs service bus" sounds like a choice between two products, but underneath it is a choice between two delivery semantics. A point-to-point queue hands each message to exactly one consumer out of a pool of competing workers — a single ticket that one cashier will serve. A service bus (or any publish/subscribe broker: Azure Service Bus topics, RabbitMQ topic exchanges, SNS fanning out to per-subscriber SQS queues) broadcasts each message to every interested subscriber independently — a company-wide announcement that every department acts on in its own way. Routing rules, transformation, orchestration, and operational cost all follow from that one structural difference.

Message queues: one message, one worker

Definition

A message queue is a buffer between a producer and a pool of workers. The producer enqueues a unit of work; any one available worker dequeues it, processes it, and acknowledges it. Once acknowledged, the message is gone — no other worker will ever see it.

How delivery actually works

Where it fits

Use a queue when you have one logical job that needs to happen exactly once, spread across a pool of interchangeable workers: resizing an uploaded image, sending a single email, charging a card. The queue's only job is to make sure the work happens once, survives a crash, and doesn't overload downstream systems.

diagram
diagram

Service bus / publish-subscribe: one message, every interested subscriber

Definition

A service bus (classically an Enterprise Service Bus, or more commonly today a topic-based broker) delivers each published message to every subscription registered on that topic, independently of the others. Azure Service Bus topics, RabbitMQ topic exchanges, and SNS fanning out to multiple SQS queues are the same pattern under different names: one publish, many independent deliveries.

Characteristics

Where it fits

Use a service bus / pub-sub topic when one event needs to trigger multiple, independent reactions that shouldn't know about each other: "order placed" needs to update inventory, charge the card, send a confirmation, and update analytics — four different services with four different SLAs, none of which should block or even know about the others.

diagram
diagram

Worked example: message #8842, from enqueue to redelivery

Scenario 1 — normal path (order-confirmation email)

An order service places message #8842 ("send confirmation email for order 91204") onto a queue at t=0s.

This is why consumers must be idempotent (e.g. de-duplicate on order ID before sending) and why the visibility timeout should be sized above your p99 processing time, not your average.

Ack-race semantics by broker

Who "wins" the t=40s/t=45s ack race is a per-broker contract, not a law of queues:

BrokerExpired/first worker's ack (Worker A)Second worker's ack (Worker B)What the consumer must therefore do
SQS (standard)DeleteMessage with the stale receipt handle returns 200 OK, but AWS documents the message "might not be deleted" — and on standard queues a message can occasionally be redelivered even after a successful deleteDelete with the newest receipt handle removes the message (a visibility-timeout lock held by another consumer does not block a delete)Treat delete as best-effort bookkeeping; de-duplicate on a business key (order ID)
Azure Service Bus (peek-lock)Complete fails with MessageLockLostException — the lock token expired when the window lapsed (delivery count is not incremented on lock loss)Succeeds — B owns the current lock tokenCatch lock-lost as "someone else owns this delivery now"; dedup on message-id
RabbitMQ (manual ack)Cannot ack across channels: delivery tags are channel-scoped, so if A's channel closed, its unacked delivery was already requeued with redelivered=true; acking a stale/unknown tag on a live channel raises PRECONDITION_FAILED and closes the channelSucceeds — B's delivery carries its own tag on B's own channelTreat redelivered=true as a duplicate-risk hint; dedup on a business key

The invariant across all three: the ack protocol protects the queue's bookkeeping, never your side effects — only idempotency protects those.

Scenario 2 — a separate, hypothetical replay: what if the email API stays down for good?

This is a fresh, independent branch, not a continuation of Scenario 1 above. Rewind the clock back to t=0s and replay the exact same message #8842 from scratch, this time assuming the email API is permanently unreachable rather than merely slow.

An on-call engineer inspects the DLQ, sees the email API has been down for 20 minutes, fixes the underlying outage, and replays the DLQ contents back onto the main queue. Without a DLQ, this failure mode would consume worker capacity indefinitely and could starve unrelated messages behind it.

When to use which — and what it costs you

Use a plain message queue when

Do not use a plain queue when

Benefits you get from a plain queue: minimal moving parts (one queue, one DLQ, one retry policy to reason about), horizontal scaling by adding workers with no coordination between them, and low latency because there's no fan-out or filter evaluation on the hot path. What it costs you: no visibility for other teams into what's flowing through the queue, and if a second consumer later needs the same events, you're re-architecting rather than just adding a subscription.

Use a service bus / pub-sub topic when

Do not use a service bus when

Benefits you get from a service bus: new consumers are additive (no producer change), each subscriber fails and retries independently so a broken email service can't stall billing, and routing rules push filtering logic into infrastructure instead of scattering "if type == X" checks across every consumer. It costs you: operational weight (a broker cluster, per-subscription DLQs, and routing-rule configuration to provision and monitor instead of a single queue), higher end-to-end latency on the hot path because every publish now pays for topic matching and per-subscription fan-out rather than a single direct handoff, harder debugging because a single logical event now has N independent delivery timelines to trace instead of one, and a bigger blast radius for misconfiguration — a bad filter rule or a forgotten subscription silently drops an entire class of downstream reactions instead of failing loudly.

A third option: log-based streaming (Kafka)

Neither a queue nor a service bus is the only alternative. A log-based platform like Kafka keeps every message on a durable, ordered, replayable log per partition and lets each consumer group track its own read offset — closer to "subscribers can rewind and replay history" than either a queue (message is gone once acked) or a classic pub/sub bus (delivered live, not typically replayed). Reach for Kafka instead of a queue or a service bus when consumers need to replay historical events (rebuilding a cache, backfilling a new service), when message volume is high enough that per-message broker bookkeeping (leases, acks, DLQs) becomes the bottleneck, or when you want strict ordering within a partition preserved across multiple independent consumer groups. The cost: you take on partition and offset management, and "delivery" becomes "read position" — a different mental model your team has to learn, not a drop-in swap for either a queue or a bus. The reverse crossover is worth naming too: a classic service bus (Azure Service Bus) beats Kafka when you need features Kafka does not ship — message sessions (ordered, stateful sub-streams keyed by a session ID), broker-side transactions across multiple sends/receives, per-message scheduled delivery and dead-lettering, and managed enterprise protocol connectors/mediation. Kafka wins on retention, replay, and raw partitioned throughput; the bus wins on transactional, session-ordered, protocol-rich enterprise integration.

Variable-duration jobs: renew the lease, do not set a one-hour timeout

A static visibility timeout works when processing time is tightly distributed. It breaks for variable tasks: if file jobs take anywhere from 10 seconds to 1 hour, setting the timeout to 1 hour delays retry for a worker that crashes at second 11; setting it to 30 seconds causes duplicate work for every legitimate long job. That is the visibility-timeout dilemma.

The production mechanism is active lease renewal, also called heartbeating. Start with a short timeout, such as 60 seconds. While the worker is healthy and still making progress, it periodically extends the message lease before it expires. In AWS SQS this is ChangeMessageVisibility; other brokers expose the same concept as renew-lock, extend-lease, or touch. If the worker crashes, heartbeats stop and the message becomes visible after only the short timeout. If the worker is alive for an hour, it keeps extending the lease and avoids duplicate delivery.

Make renewal progress-aware: renew only after durable progress, cap the maximum total lease time, and send the message to a DLQ after repeated failures. Otherwise a wedged worker that keeps heartbeating can hide a poison message forever.

The operability tell for both patterns is the same: alert on DLQ depth and message age, not only on queue lag. A queue can show near-zero lag while every message is quietly failing into the DLQ, and a session/affinity-based subscription can wedge when its sticky consumer host dies — both are invisible to a lag-only dashboard. Watch DLQ arrival rate, oldest-message age, and (for pub/sub) per-subscription backlog so a single broken subscriber surfaces loudly instead of silently dropping a whole class of downstream reactions.

Sources

Concepts and terminology cross-checked against: the AWS SQS developer guide (visibility timeout, at-least-once delivery, dead-letter queues), the Azure Service Bus documentation (topics, subscriptions, filter rules), the RabbitMQ documentation (topic exchanges, competing consumers), and the Apache Kafka documentation (log-based retention, consumer groups, offsets).

🤖 Don't fully get this? Learn it with Claude

Stuck on Message Queues vs Service Bus? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Message Queues vs Service Bus** (System Design) and want to truly understand it. Explain Message Queues vs Service Bus from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Message Queues vs Service Bus** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Message Queues vs Service Bus** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Message Queues vs Service Bus** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes