RabbitMQ classic mirrored queues removed: migrating to quorum queues
If you upgraded RabbitMQ and your ha-mode policies silently stopped doing anything, that is not a bug. Classic queue mirroring was deprecated in 3.9 and removed in 4.0. On 4.x the mirroring-related keys in your policies (ha-mode, ha-params, ha-sync-mode, ha-promote-on-shutdown, ha-promote-on-failure, ha-sync-batch-size) have no effect. The classic queues themselves keep working, but each one now runs as a single, non-replicated queue on one node.
That is the dangerous part: nothing crashes, nothing logs an obvious error, and producers and consumers keep flowing. You simply lost replication. The first time you notice is often the first node failure after the upgrade, when queues you believed were highly available go down with their host node.
This article covers getting from that state to quorum queues: how to find what is affected, which features do not carry over, how to run the migration, and which new failure modes to monitor afterward.
Why ha-mode policies silently stop meaning anything
Policies are still supported in RabbitMQ 4.x. What changed is the set of keys the server acts on. A policy matching classic queues that only contains ha-mode and friends still matches queues but changes nothing about them. The result:
- Queue state: classic queues show
running, keep serving traffic, and report normally in the Management API. There is no “mirroring broken” state because mirroring no longer exists as a concept. - Node failure behavior: a classic queue mastered on a failed node shows
downuntil the node returns. Before the upgrade, a mirror on a surviving node would have been promoted. Now there is nothing to promote. - Upgrade behavior on 4.0: classic queues still on the v1 storage implementation are converted to v2 during startup, which can take a long time on nodes holding many messages. If you still have
classic_queue.default_version = 1inrabbitmq.conf, the node refuses to start. Pre-convert deliberately on 3.13 before upgrading.
Two more defaults changed in 4.0 that interact with this migration: quorum queues gained a default redelivery limit of 20 (messages redelivered more than that are dead-lettered or dropped), and the default maximum message size dropped to 16 MiB.
Quorum queues vs classic mirrored queues
| Aspect | Classic mirrored queue | Quorum queue |
|---|---|---|
| Replication | Bespoke master/mirror protocol | Raft consensus, leader plus followers |
| Declaration | Any classic queue + ha-mode policy | x-queue-type=quorum at declaration (or via default_queue_type) |
| Durability | Optional (transient queues allowed) | Always durable, per-message |
| Poison message handling | Requeue loop by default | x-delivery-limit, default 20 in 4.x, then dead-letter or drop |
| Memory per message | Lower | Higher (Raft metadata and WAL overhead) |
| Disk footprint | Message store + index | Per-queue Raft WAL segments, compacted via snapshots |
| Node failure | Mirror promotion, known consistency gaps across partitions | Leader election; minority side becomes read-only or unavailable |
| Minimum sensible cluster | 2 nodes | 3 nodes (no HA benefit below 3 members) |
The failure-mode shift is what operators underestimate. Mirrored queues failed toward availability with possible divergence. Quorum queues fail toward consistency: a queue that loses a majority of its members shows state minority and stops accepting writes while still serving already-committed messages. Your alerting needs to know the difference.
Inventory: find what is affected
Before touching anything, build the list of queues and policies that need attention. These are safe, read-only checks.
# List all policies and their definitions
rabbitmqctl list_policies
# List every queue with its type, leader node, and member nodes
rabbitmqctl list_queues name durable type leader online members
# Count leaders per node (leader imbalance check, useful before and after migration)
rabbitmqctl list_queues name type leader | awk '{print $3}' | sort | uniq -c
What you are looking for:
- Policies with ha-mode keys: every policy whose definition contains
ha-modeis a migration candidate. On 3.10.13/3.11.5 and later you can inspecteffective_policy_definitionper queue via the Management API to see exactly which policy applies to which queue. - Classic queues that should be replicated: any durable classic queue that carried an ha-mode policy must become a quorum queue.
- Queues that must stay classic: transient (non-durable) queues cannot be quorum queues. They remain classic, non-mirrored. Accept the durability semantics or redesign them as durable.
- Exclusive queues: cannot be declared as quorum. Attempting it returns an error. They stay classic by design.
flowchart TD
A[Queue under ha-mode policy] --> B{Durable?}
B -->|No| C[Stays classic, non-mirrored]
B -->|Yes| D{Exclusive?}
D -->|Yes| C
D -->|No| E{Uses incompatible features?}
E -->|reject-publish-dlx, global QoS, priorities pre-4.0| F[Remediate app or policy first]
E -->|None| G[Migrate to quorum queue]
F --> GFeature compatibility checklist
Work through this table per queue before migrating. These are the incompatibilities that break applications if you migrate blind.
| Feature | Classic mirrored | Quorum queue | Required action |
|---|---|---|---|
reject-publish-dlx overflow | Supported | Not supported | Change policy to reject-publish, handle dead-lettering in the application |
| Global QoS prefetch | Supported | Not supported | Move to per-consumer QoS |
| Message priorities | Supported | Not supported before 4.0; 4.0 adds priorities with different semantics | Split into multiple queues or verify 4.x behavior against your workload |
x-delivery-limit | Not applicable | Supported; default 20 in 4.x | Ensure a dead-letter exchange is configured, or raise the limit via policy for legitimate high-redelivery workloads |
queue_master_locator | Supported | Deprecated, replaced by queue_leader_locator | Update policies to the new key |
| Transient queues | Supported | Not supported (quorum queues are always durable) | Keep as classic non-mirrored |
| Cluster size | 2 nodes usable | No benefit below 3 members | Add a third node before migrating |
One more operational trap: if you declare many quorum queues over a single connection, the node hosting that connection tends to become a member of every queue. Spread declarations across connections, or rebalance afterward.
Migration procedure
The safe pattern for queues holding in-flight messages is blue-green: stand up the new quorum queue, move producers, drain the old queue, delete it. In-place delete-and-recreate is only acceptable for queues that are empty or whose messages are disposable.
- Remediate incompatibilities. Fix every item flagged in the checklist above: overflow mode, global QoS, priorities, delivery limits. Do this on the classic queues first where possible, so the application change is decoupled from the queue-type change.
- Declare the new quorum queue. Same exchange bindings,
x-queue-type=quorum, durable. Use a temporary name if you plan to keep the original name (blue-green), or declare after deleting the old queue (in-place). - Move producers. Repoint publishers at the new queue (via binding or name change). Verify publish rate on the new queue and zero publish rate on the old one.
- Drain the old queue. Let consumers finish the backlog. For messages that cannot be replayed, use shovel or federation to move them to the new queue. Watch the old queue’s
messages_readyreach zero. - Delete the old queue. This is destructive: confirm
messages_readyandmessages_unacknowledgedare both zero first. In blue-green with a renamed queue, rebind the original name now. - Drop dead policies. Remove or rewrite ha-mode policies so they no longer pretend to do something. On 4.x, attempting to create a policy with
ha-modekeys is refused; on 3.13 it still applies and masks queues you missed. - Rebalance leadership. After migrating many queues, check leader distribution and run
rabbitmq-queues rebalance allduring low traffic. Leader transfer causes brief per-queue unavailability.
RabbitMQ’s rabbitmqadmin v2 CLI supports automated migration via the prepare_for_quorum_queue_migration and drop_empty_policies transformations, which generate much of this for you.
If you are still on 3.13 and planning the 4.0 upgrade: set default_queue_type (available since 3.13.3) so newly declared queues default to quorum, and convert any remaining CQv1 classic queues to v2 deliberately before the upgrade rather than during it.
After the migration: the failure modes change
Your runbooks and alerts written for mirrored queues need updating:
- Leader elections are normal. Rolling restarts now trigger Raft elections. Expect them; alert only on flapping.
minorityis a real state. A quorum queue with insufficient members cannot accept writes. Monitor queuestateand treat sustainedminorityon an active queue as urgent.- Redundancy math changed. A 3-member quorum queue with one member down still commits (2 of 3) but has zero remaining fault tolerance. It looks healthy and is not. Watch the
onlinemember list per queue. - Disk profile changed. Quorum queues write per-queue WAL segments. Under sustained throughput, WAL growth can outpace snapshot compaction, especially if a follower is lagging. Watch disk free and the Raft WAL directory, not just the message store.
- Memory profile changed. Quorum queues carry higher per-message memory overhead than classic queues. Re-baseline your
mem_used / mem_limitexpectations after migrating large queues. - Before any shutdown, run
rabbitmq-diagnostics check_if_node_is_quorum_critical. If the node is quorum-critical, stopping it takes queues intominority.
Signals to monitor
| Signal | Why it matters | Warning sign |
|---|---|---|
Queue state per queue | Catches minority, down, crashed after migration and during restarts | Any active quorum queue in minority |
leader and online members per queue | Reveals redundancy loss that looks healthy | Fewer online members than declared, or leaders concentrated on one node |
| Leader distribution across nodes | The leader does all publish and dispatch work | One node holding >60% of quorum queue leaders |
Disk free vs disk_free_limit | WAL growth is faster and spikier than classic store growth | Sustained downward trend; disk_free < 3x limit |
mem_used / mem_limit | Higher per-message overhead after migration | Ratio trending above your pre-migration baseline |
| Redeliver rate per queue | With the default delivery-limit of 20, poison loops now end in dead-lettering or drops | Elevated redeliver plus a growing DLX queue |
| Dead-letter queue depth | Where redelivery-limited messages land | Growing DLX queue nobody consumes from |
How Netdata helps
- Per-queue state and depth over time: Netdata’s RabbitMQ collector tracks queue state,
messages_ready, andmessages_unacknowledgedper queue, so you can watch the old classic queue drain to zero while the new quorum queue takes over during a blue-green migration. - Leader and member visibility: queue type and node attribution in per-queue metrics let you spot a quorum queue that lost members while still serving traffic, the silent redundancy-loss case.
- Disk and memory correlation: correlating disk free, memory ratio, and per-queue depth on the same dashboard separates classic store growth from post-migration WAL growth.
- Redeliver and DLX signals: per-queue redeliver rates surfaced against queue depth make the new delivery-limit behavior visible before messages start landing in the dead-letter queue unnoticed.
- Node-level alarms: memory and disk alarm states per node, correlated with publish rates, catch the cluster-wide publishing halt a WAL-driven disk alarm would cause.
Related guides
- RabbitMQ channel leak and churn: the channel-per-message anti-pattern
- RabbitMQ connection blocked / blocking: publishers frozen by a resource alarm
- RabbitMQ connection in flow state: credit-based backpressure explained
- RabbitMQ connection storm: reconnect loops, FD pressure, and CPU spent on handshakes
- RabbitMQ consumer timeout: delivery acknowledgement timed out and the channel is closed
- RabbitMQ consumer_utilisation low: consumers attached but not keeping up
- RabbitMQ consumers connected but not acknowledging: the consumer black hole
- RabbitMQ disk free limit alarm: free disk space insufficient and publishing halted
- RabbitMQ disk_free_limit at the default 50MB: the production footgun
- RabbitMQ Erlang distribution port saturation: the single link that carries all cluster traffic
- RabbitMQ file descriptor exhaustion: fd_used near fd_total and refused connections
- RabbitMQ flow control vs resource alarms: the two throttling mechanisms operators confuse






