Skip to content

RabbitMQ Streams

The previous lesson framed RabbitMQ and Kafka as opposites — a delete-on-ack queue versus a retained log. RabbitMQ Streams blur that line: they bring a Kafka-like append-only log into RabbitMQ, so you can get replay and high fan-out without adding a second system.

A stream is a queue type (declared with x-queue-type: stream), but it behaves very differently from a classic queue:

  • Messages are appended to a log and retained, not deleted when read.
  • Reads are non-destructive — consuming does not remove anything.
  • Each consumer reads from an offset it controls, and can start from the beginning, the end, a timestamp, or a saved position.
  • Many consumers can read the same stream independently and at different speeds.
flowchart LR
  prod["Producer"] -->|append| log["Stream log
[0][1][2][3][4][5]"]
  log --> c1["Consumer A
offset 1 (replaying)"]
  log --> c2["Consumer B
offset 5 (live)"]
  log --> c3["Consumer C
from timestamp"]
A stream: one retained log, many consumers at their own offset
Classic queueStream
On readRemoved after ackRetained (non-destructive)
ReplayNoYes — reset the offset
Multiple readers of same dataCompete for messagesEach reads the whole log independently
RetentionUntil consumedTime or size based (e.g. keep 7 days)
Best atTask distribution, per-message workEvent fan-out, replay, large-scale reads

Because a stream keeps data, you declare retention — by time or total size — rather than relying on consumption to clear it. Old segments are truncated once they age out.

Choose a stream when you want a Kafka-like log but would rather not run Kafka: you need replay, you have many consumers reading the same events, or you’re ingesting a high-throughput firehose and want time-based retention. There’s also a super stream — a partitioned stream — for scaling a single logical stream across nodes, much like Kafka partitions.

Stay with a classic (or quorum) queue when the message is a task done once and discarded, when you need per-message routing and retry, or when you don’t need history. Most task-distribution and RPC work still wants a queue, not a stream.

What happens to a message in a RabbitMQ stream when a consumer reads it?
How does a consumer choose where to start reading a stream?
How is data cleared from a stream?
Which workload still belongs on a classic/quorum queue rather than a stream?