RabbitMQ classic, quorum or stream: which queue type to choose? Intermediate
Estimated reading time: 6 min read
The problem
Section titled “The problem”A new project has three flows: order confirmation emails (one worker per job, must survive losing a node), a temporary reply queue for an internal request, and an audit feed that several services need to reread. Using the same queue type for all three gets at least one requirement wrong.
RabbitMQ 4.x has classic, quorum and stream. They differ in storage mechanism, replication and how they are read, not three levels of “fast, medium, slow”.
Quick choice
Section titled “Quick choice”Default: quorum queues for every durable queue holding jobs. The RabbitMQ docs describe quorum as the option when you need a replicated, highly available queue. Classic only for temporary queues or when you accept the risk of losing a node. Stream when you need to reread.
Loading diagram…
Feature matrix
Section titled “Feature matrix”| Feature | Classic | Quorum | Stream |
|---|---|---|---|
| Read model | Message deleted after ack | Message deleted after ack | Append-only log, read by offset, not deleted on read |
| Replication (4.x) | No | Yes (Raft, 3 replicas by default) | Yes |
| Confirm sent when | Message written to disk (persistent, durable queue) | Replicated to a majority | Reached a majority of replicas |
| Replay | No | No | Yes, within the retention window |
| Exclusive / transient | Yes | No (always durable, not exclusive) | No |
| Priority | Yes | Yes since 4.3 (0 to 31); x-max-priority is ignored |
No |
| Message TTL | Yes | Yes | No (use retention) |
| Dead-letter | Yes | Yes, including at-least-once | No |
Poison message (x-delivery-limit) |
No | Yes, default 20 | No |
| Global QoS | Yes | No | No |
| Keeps messages in RAM | Partly (CQv2 loads from disk when needed) | Never | Never |
Three points checked against the docs: classic is non-replicated since 4.0; quorum and stream both raise a channel error with global QoS; stream retention is evaluated per segment, and with no age or size set the log grows until the disk is full.
Verified configuration and arguments
Section titled “Verified configuration and arguments”Quorum queue x-queue-type = quorum x-quorum-initial-group-size = 3 # default 3, should be odd x-delivery-limit = 20 # default 20 since 4.0, -1 disables (not recommended) policy dead-letter-strategy = at-least-once # with overflow=reject-publish
Stream x-queue-type = stream x-max-age = 7D # default: not set x-max-length-bytes = ... # default: not set x-stream-max-segment-size-bytes = 500000000 # default 500 MBAMQP 0-9-1 consumers of a stream must use manual ack, set a prefetch, and pick a starting point with the x-stream-offset argument (if unset, reading starts from offsets written after attaching). The docs recommend a stream protocol client for the full feature set and the best throughput.
Three situations, three choices
Section titled “Three situations, three choices”If the problem really is a partitioned log, very high throughput and a stream processing ecosystem, consider Kafka: see Kafka or RabbitMQ and Kafka distributed log.
Running the simulated selection table
Section titled “Running the simulated selection table”npx --prefix examples ts-node examples/src/rabbitmq-queue-types/selection-model.tsThe script prints three suggestions: email job → quorum, audit feed → stream, temporary reply → classic. It is only a decision aid; it does not connect to RabbitMQ or verify replication.
Real verification needs a multi-node cluster. Quorum: publish persistent messages with confirms, stop one node, watch publish/consume; then lose a majority of nodes to see availability drop.
Stream: two consumers with separate offsets reread the same record, then watch retention delete old data. A single local broker does not prove failover.
Migrating from 3.x to 4.x
Section titled “Migrating from 3.x to 4.x”Classic mirrored queues were removed in 4.0. Declaring a queue with the same name and a different x-queue-type is rejected as a property mismatch, so the type cannot be changed in place.
- List queues using a mirroring policy (
ha-mode) and treat them as unreplicated after 4.0. - Create a new quorum queue (new name) with the same bindings.
- Move producers to the new exchange/binding or dual publish; drain the old queue with the existing consumers.
- Remove dependencies on features quorum lacks: exclusive, transient, global QoS, server-named queues.
- Set
x-delivery-limit, a DLX and at-least-once dead-lettering on the new queue. - Idempotent consumers, because during cutover the same message may appear in both queues.
This list is a general procedure inferred from the verified behavior of queue declaration and quorum features, not checked against the official migration page; read the migration guide for the exact version you use.
When to use / not use
Section titled “When to use / not use”| Type | Use when | Do not use when |
|---|---|---|
| Classic | Temporary queues, replies, need exclusive or legacy priority | Important jobs that must survive losing a node |
| Quorum | Durable work queues needing HA and poison handling | Temporary queues, high churn, need exclusive |
| Stream | Fan-out, audit, replay within retention | Competing-consumer work queues, need DLX or per-message TTL |
Production checklist
Section titled “Production checklist”- Every durable work queue: quorum, odd number of nodes (at least 3).
- Publisher confirms and manual ack; see the three acknowledgment boundaries.
- Set
x-delivery-limitand a DLX; do not let poison messages cycle. - Streams: set
x-max-ageorx-max-length-bytesfrom day one, and monitor disk. - Temporary queues:
exclusive, not quorum. - Declare topology with versioned code; changing the type means creating a new queue.
- Prepare backups and a runbook: replication does not protect against accidental deletion.
Common mistakes
Section titled “Common mistakes”Summary
Section titled “Summary”Self-check: (1) Can a durable classic queue survive permanently losing the node that stores it? (2) Why does an audit feed fit a stream better than quorum? (3) Why can you not change the x-queue-type of a running queue?