Back to blog

// OSSeva Blog

Migration

Migrating RabbitMQ Classic Mirrored Queues to Quorum Queues: The 3.13 to 4.x Guide

Matt Reynolds13 min read

The short answer

RabbitMQ 4.0 removed classic queue mirroring. A classic queue on 4.x is a single-node queue: the ha-mode policies that used to replicate it are ignored. Starting with RabbitMQ 4.0, the replacement for classic mirrored queues is the quorum queue, the replicated queue type built on the Raft consensus algorithm. If your RabbitMQ cluster depends on mirroring for availability, you have to migrate those classic mirrored queues to quorum queues before you move to 4.x.

There is no switch that converts a queue in place. The queue type is fixed when a queue is declared, so every migration comes down to declaring a new quorum queue and moving the traffic onto it. RabbitMQ documents two ways to do that: move one virtual host at a time to a new vhost whose default queue type is quorum, or drain and redeclare the queues in the same vhost. Which one fits depends on how your applications declare queues, and that is the thing to find out first.

Why mirrored classic queues had to go

Classic queue mirroring was RabbitMQ's original high-availability design. A leader replica accepted writes and pushed them to mirrors on other nodes, and a mirror was promoted when the leader failed. It worked, but it had well-known failure modes: unsynchronised mirrors that silently lost messages on promotion, synchronisation that blocked the queue while it ran, and behaviour under network partitions that was hard to reason about.

Quorum queues replace that with a Raft log. A write is confirmed only after a majority of replicas have it, leader election is deterministic, and a replica that falls behind catches up from the log without blocking the queue. Classic mirrored queues were deprecated in 3.9, operators were told to use quorum queues for several releases, and mirroring was removed in 4.0. Streams are the other replicated type, for replay-style consumption. For work queues that used to be mirrored, the quorum queue is the replacement.

Classic vs quorum queues: what actually changes

Mirrored classic queue (3.x)Quorum queue
ReplicationLeader plus mirrors, set by an ha-mode policyRaft log across a fixed replica group, set at declaration
DurabilityDurable or transientDurable only
Exclusive queuesAllowed, never mirroredNot supported
Poison messagesRedelivered forever unless the app intervenesDelivery limit, then dead-lettered or dropped
Global QoS (basic.qos with global)SupportedNot supported; use per-consumer prefetch
Prioritiesx-max-priority with many levelsTwo levels on 4.x (normal and high); none before 4.0
Memory profileMessages in memory unless lazyLog on disk, small in-memory working set

The rows that cause migration work are the second, third and fifth, because quorum queues do not support transient queues, an exclusive queue, or global QoS. A queue that is transient or exclusive by design cannot become a quorum queue at all. It should become a non-mirrored queue: a plain classic queue, which is still fully supported on 4.x and is the correct type for temporary and reply queues.

Step 1: find every mirrored queue and every policy that mirrors

Mirroring is applied by policy, not by the application, so start with the policies. On each vhost, list them and look for any with an ha-mode key:

rabbitmqctl list_policies -p my-vhost --formatter=pretty_table
# any policy whose definition contains ha-mode, ha-params or ha-sync-mode mirrors queues

Then list the queues those policies actually match, with their type and flags:

rabbitmqctl list_queues -p my-vhost name type durable exclusive auto_delete policy arguments

Keep this output. It is your migration inventory, and three columns in it decide the route: durable, exclusive and arguments. A queue whose arguments include x-max-priority, or whose consumers set global QoS, needs application changes whichever route you choose.

On 3.13 you can rehearse the removal before you upgrade. RabbitMQ 3.13 tracks mirroring as a deprecated feature, and the broker can be configured to refuse it, so a staging cluster tells you which applications still declare or depend on mirrored queues before production does.

Step 2: find out how your applications declare queues

This is the question that decides everything else, and it is not a broker question. Three patterns exist, and most estates have all three:

  • The application declares the queue with no type. It inherits the vhost's default queue type. These queues migrate with no code change if the new vhost defaults to quorum.
  • The application declares x-queue-type: classic explicitly. A redeclaration against an existing quorum queue with a different type fails with a PRECONDITION_FAILED channel error. These need a code or configuration change.
  • The queue is created by operations, through definitions files or Terraform. Change the definition, not the application.

Client libraries and frameworks matter here. Spring AMQP, MassTransit and NServiceBus each have their own conventions for declaring queues and their own settings for the queue type, and some declare topology on every start. Find where each service's topology comes from before you choose a route.

Step 3: choose a migration route

Route A: migrate one virtual host at a time

This is the cleanest migration to quorum queues for most estates. Create a new vhost with the default queue type set to quorum, recreate the exchanges, bindings and policies there without the ha-mode keys, and move producers and consumers across. Messages still sitting in the old vhost are moved with a Shovel or drained by consumers that read from the old vhost until it is empty.

This is the lower-risk route and the one RabbitMQ recommends when it is possible. The old vhost stays intact until you delete it, so rolling back means pointing clients at the old vhost again. The cost is that every client's connection settings change, which is easy for a handful of services and a project in its own right for a hundred.

Route B: migrate in place

Keep the vhost and, queue by queue, stop publishing, let consumers drain the queue, delete it, and redeclare it as a quorum queue with the same name. Update the policies to drop the mirroring keys first, so the new queue is not matched by an obsolete policy definition.

In-place migration avoids the client reconfiguration but requires a publishing pause per queue, and it has no rollback beyond redeclaring the classic queue. It suits queues that can be emptied in a maintenance window, and it is the only practical route when client connection strings are baked into images you cannot rebuild quickly.

On Amazon MQ for RabbitMQ, AWS provides a queue migration tool and documents both routes for brokers moving to 4.x. The in-place upgrade to 4.x requires that no classic mirrored queues remain, so the migration is a precondition of the upgrade rather than a follow-up to it.

Step 4: fix the features quorum queues handle differently

  • Delivery limit. RabbitMQ 4.0 sets a default delivery limit of 20 on quorum queues. A message that is rejected or requeued past the limit is dead-lettered if a dead-letter exchange is configured and dropped if not. Applications that relied on endless redelivery to retry will start losing messages. Configure a dead-letter exchange and a sensible limit per queue before cutover.
  • Global QoS. Consumers that set a global prefetch on the channel get an error against a quorum queue. Move to per-consumer prefetch.
  • Priorities. A classic queue with x-max-priority of ten becomes a quorum queue with two priority levels. If the application genuinely needs more, the usual pattern is a separate queue per priority band.
  • Exclusive, auto-delete and transient queues. Leave them as classic queues. They were never safely mirrored anyway, and they are the right type for RPC reply queues and other temporary queues.
  • Message size. RabbitMQ 4.0 also lowers the default maximum message size to 16 MiB, down from 128 MiB. Producers that send large payloads fail at the broker after the move to 4.x unless the limit is raised deliberately.
  • Memory and disk. Quorum queues write every message to a replicated log. Size disks and watch the Raft segment files, especially for queues that hold large backlogs for long periods.

Step 5: test the failure modes, not just the happy path

A migrated queue that passes a smoke test has shown very little. Before you call a vhost done, test what happens when a queue leader fails: stop the node hosting a quorum queue leader and confirm consumers reconnect and continue; publish with confirms and check none are lost across the failover; reject a message repeatedly and confirm it lands in the dead-letter queue at the limit. These three tests catch the application assumptions that the broker change exposes.

Then move the cluster from 3.13 to 4.x

Once no mirrored queues remain and all stable feature flags are enabled on the latest 3.13 patch you can run, the rolling move to 4.x is routine. The exception is a cluster that enabled the experimental Khepri metadata store on 3.13: RabbitMQ's 4.0 release notes give it no in-place upgrade path, so it moves by blue-green deployment to a new cluster. RabbitMQ requires the path to go through 3.13, so clusters on 3.12 or earlier go to 3.13 first. Moving to 4.x also brings a supported Erlang runtime, which is the security reason to do all of this: public RabbitMQ 3.13 builds run on Erlang/OTP 26, and OTP 26 no longer receives patches. See RabbitMQ and Erlang version compatibility for the matrix.

When the migration will take longer than the support window

For a small estate this is a few weeks of careful work. For an estate with an ISV product that embeds RabbitMQ and has not certified 4.x, or with hundreds of services declaring their own topology, it is quarters. That gap is where extended support fits: patched RabbitMQ 3.13 on a patched Erlang runtime, so the migration runs on its own timeline rather than the CVE calendar's.

OSSeva provides exactly that, and runs quorum queue migrations: RabbitMQ 3.x extended support covers the broker and the Erlang layer while the queues move, and the RabbitMQ 3.13 end-of-life page sets out the dates.

Frequently asked questions

Can I convert an existing classic queue to a quorum queue?

No. The queue type is set at declaration and cannot be changed. You declare a new quorum queue, either in a new vhost or after deleting the classic queue, and move traffic to it.

Do quorum queues support message TTL and queue length limits?

Yes. Message TTL, queue TTL and length limits with the drop-head and reject-publish overflow behaviours are supported. Check the RabbitMQ documentation for your exact version, because support was added across several 3.x releases.

Should I use quorum queues for everything?

No. Use quorum queues for durable work queues where losing messages matters. Keep classic queues for temporary, exclusive and reply queues, and consider streams where consumers need to replay history. RabbitMQ quorum queues trade some latency for safety, and that trade is wrong for short-lived traffic.

Are classic queues deprecated in RabbitMQ 4.0?

No. Only classic queue mirroring was removed. Non-replicated classic queues remain supported and are the right choice for temporary, exclusive and reply queues.

How many replicas should a quorum queue have?

An odd number, usually three or five. Three tolerates the loss of one node; five tolerates two. More replicas add write latency without adding much safety.

Tags

RabbitMQQuorum QueuesMigrationRabbitMQ 4.0Classic Mirrored Queues

Ready to get your open source under control?

Talk to an OSSeva engineer about CVE coverage, compliance, and migration support for your stack.