Queues vs Logs: The Architecture Decision That Changes Everything
A message queue and a commit log are architecturally opposite primitives. Queues delete messages on consumption — they are for task dispatch. Logs retain events indefinitely — they are for event streaming and independent consumer replay. Treating them as interchangeable is an architectural mistake that forces a full rewrite at scale.
Distributed Messaging Systems
Queues vs Logs: The Architecture Decision That Changes Everything
The engineering team that builds the notification system using RabbitMQ ships quickly, the product works, and eighteen months later they need to add a real-time analytics pipeline that replays the last 90 days of events to train a recommendation model. They discover that RabbitMQ deleted every message the moment the original consumer acknowledged it. The events are gone. The only record of what happened is the application database, which was not designed to be an event log. The rewrite takes six weeks. This is not a RabbitMQ failure — it is a model mismatch. The team needed a log. They built with a queue.
A message is a fact about the world — and the single most consequential decision in any messaging architecture is what happens to that fact after the first consumer reads it.
Series positioning: This is Part 2 of Distributed Messaging Systems. Part 1 established why synchronous coupling fails and what messaging enables. This article establishes the architectural divide between queues and logs — a decision that governs which broker you choose and cannot be reversed without a full rewrite. Part 3 dives into Kafka internals; Part 4 into RabbitMQ and AMQP.
1. Two Primitives, Two Mental Models
1.1 The Queue: Task Dispatch
A message queue operates on a simple contract: a message exists until exactly one consumer acknowledges it, then it is deleted. The queue is a work distributor — tasks flow in, workers compete to claim them, and the queue tracks only which tasks are outstanding.
Key properties of the queue model:
| Property | Behaviour |
|---|---|
| Delivery | Each message delivered to exactly one consumer (competing consumers) |
| After ack | Message permanently deleted |
| Replay | Not possible — deleted messages cannot be re-consumed |
| Consumer independence | Consumers share a single queue position — they compete |
| Backpressure | Queue depth grows; consumers slow down the drain |
1.2 The Log: Event Streaming
A commit log operates on the opposite contract: messages are appended to a sequential, durable log and never deleted on consumption. Each consumer group maintains its own offset cursor — a pointer to the position it has read up to. Two consumer groups reading the same log are fully independent.
Key properties of the log model:
| Property | Behaviour |
|---|---|
| Delivery | Each consumer group gets every message |
| After read | Message retained — log is append-only |
| Replay | Possible — reset offset to re-consume from any point |
| Consumer independence | Each group has its own cursor; groups do not interfere |
| Backpressure | Consumer lag measured in offset distance; broker unaffected |
2. The Offset Model: Why Consumer Independence Matters
2.1 What an Offset Is
An offset is a 64-bit integer — the position of a message within a partition. The log is immutable; offsets are monotonically increasing. A consumer group's progress is entirely represented by a single number per partition: the committed offset, which is the next message it will read.
Commit the offset after processing succeeds, not before. Committing before guarantees at-most-once delivery — if the process crashes after committing but before writing to the database, the event is lost permanently. The Kafka offset model makes at-least-once the default; idempotent processing makes it effectively-once.
2.2 Independent Consumer Groups
The same log, consumed twice, independently:
This is impossible with a queue. Once Worker 1 acknowledges a message, Worker 2 cannot read it. Consumer group independence is a log property.
Think of a log as a DVD and a queue as a cinema ticket. A DVD can be watched by any number of viewers, each starting and stopping independently, rewinding at will. A cinema ticket grants exactly one seat for one showing — once used, the seat is gone.
3. The Decision Matrix
Choosing the wrong primitive is not a performance problem you can tune away. It is an architectural mismatch that forces a rewrite. Use this matrix to make the decision before you write any code:
| Criterion | Use a Queue (RabbitMQ / SQS / Azure Service Bus) | Use a Log (Kafka / Kinesis / AWS MSK) |
|---|---|---|
| How many consumers need the event? | Exactly one (task dispatch) | Multiple independent consumers |
| Do you need replay? | No — task either runs or is retried | Yes — reprocess, backfill, add new consumers later |
| Event retention | Delete after ack | Hours to years (configurable) |
| Throughput requirements | Moderate (< 100K msg/s typical) | Very high (millions msg/s per partition) |
| Consumer routing complexity | Complex (topic exchanges, header routing, fan-out via bindings) | Simple (partition key determines assignment) |
| Latency sensitivity | Lower end-to-end latency (no replication wait) | Higher latency at very low throughput (batch accumulation) |
| Operational overhead | Lower — managed services widely available | Higher — partition planning, replication factor, consumer lag monitoring |
3.1 The Replay Use Cases That Force Logs
These requirements all mandate a log. If you discover any of them after building on a queue, you face a rewrite:
The most common architectural mistake is choosing a queue for simplicity and then adding fan-out by publishing the same message to multiple queues from the producer. This re-creates the tight coupling messaging was supposed to eliminate — now the producer must know every consumer and explicitly route to each one. A log decouples this entirely: producers are unaware of consumers.
4. Hybrid Architectures: When to Use Both
Real systems use both primitives — each for the role it is designed for:
- Kafka (log) for the primary event stream — multiple consumers, replay, audit, analytics
- RabbitMQ (queue) for task dispatch within a single bounded context — fraud alert workers compete to process each alert exactly once
A useful heuristic: if you're thinking "every consumer needs every event," reach for a log. If you're thinking "I need exactly one worker to process each task," reach for a queue. Most production systems need both.
5. Managed Primitives: Cloud Equivalents
Understanding the abstract model lets you map it to managed cloud services:
| Model | Self-Hosted | AWS | Google Cloud | Azure |
|---|---|---|---|---|
| Queue | RabbitMQ | SQS | Cloud Tasks | Service Bus Queues |
| Log | Kafka | Kinesis / MSK | Pub/Sub | Event Hubs |
| Hybrid Pub/Sub | — | SNS → SQS | Pub/Sub | Service Bus Topics |
AWS SNS + SQS fan-out is the managed equivalent of a RabbitMQ topic exchange: SNS broadcasts to multiple SQS queues, each with their own consumers. It looks like a log (fan-out to multiple subscribers) but each SQS queue is still a destructive queue — messages are deleted on ack. This means individual subscribers can replay within their own queue's visibility window (default 30 seconds), but you cannot add a new subscriber and replay historical events. It is fan-out without replayability.
6. The Retention Model: How Long Does the Log Remember?
Kafka and Kinesis retain messages by time or size, not by consumption:
This has a critical implication: a consumer that falls behind and does not catch up within the retention window permanently loses access to the unread messages. The log is not unlimited storage — it is a sliding window.
Consumer lag is not free on a log. A consumer group that is thousands of hours behind is consuming disk on every broker in the replication factor. Monitor consumer lag (kafka-consumer-groups.sh --describe) actively. A consumer that is offline for longer than the retention period will fail on restart with OffsetOutOfRangeError — its committed offset no longer exists in the log.
Summary
| Concept | Rule |
|---|---|
| Queue vs Log | Queues (AMQP) delete messages on ack — they are for task dispatch; logs (Kafka) retain forever — they are for event streaming. |
| Consumer independence | Consumer independence is a log property, not a queue property: separate consumer groups each maintain their own offset cursor. |
| Decision is irreversible | Choosing the wrong primitive forces an architectural rewrite later; the decision is irreversible at scale. |
What's Next
Part 3: Kafka Internals — Partitions, Leaders, and the Commit Log dives below the offset model into how Kafka actually stores and replicates data: the write path from
producer.send()through leader election, ISR replication, andackssemantics. Part 4 covers the RabbitMQ and AMQP model in equivalent depth — both are independent reads grounded in the queue vs log distinction established here.
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.