Quorum Queues
Overview
The RabbitMQ quorum queue is a modern queue type, which implements a durable, replicated FIFO queue based on the Raft consensus algorithm.
Quorum queues are designed to be safer and provide simpler, well defined failure handling semantics that users should find easier to reason about when designing and operating their systems.
Quorum queues and streams now replace the original, replicated mirrored classic queue. Mirrored classic queues are have long been deprecated and were removed from RabbitMQ 4.x. Use the Migrate your RabbitMQ Mirrored Classic Queues to Quorum Queues guide for migrating RabbitMQ installations that currently use classic mirrored queues.
Quorum queues are optimized for set of use cases where data safety is a top priority. This is covered in Motivation. Quorum queues should be considered the default option for a replicated queue type.
Quorum queues also have important differences in behaviour and some limitations compared to classic mirrored queues, including workload-specific ones, e.g. when consumers repeatedly requeue the same message.
Some features, such as poison message handling, are specific to quorum queues.
For cases that would benefit from replication and repeatable reads, streams may be a better option than quorum queues.
Topics Covered
Topics covered in this information include:
- What are quorum queues and why they were introduced
- How are they different from classic queues
- Primary use cases of quorum queues and when not to use them
- How to declare a quorum queue
- Replication-related topics: replica management, replica leader rebalancing, optimal number of replicas, etc
- What guarantees quorum queues offer in terms of leader failure handling, data safety and availability
- Continuous Membership Reconciliation
- Performance characteristics of quorum queues and performance tuning relevant to them
- Poison message handling (failure-redelivery loop protection)
- Configurable settings of quorum queues
- Resource use of quorum queues, most importantly their memory footprint
and more.
General familiarity with RabbitMQ clustering would be helpful here when learning more about quorum queues.
Motivation
Quorum queues adopt a different replication and consensus protocol and give up support for certain "transient" in nature features, which results in some limitations. These limitations are covered later in this information.
Quorum queues pass a refactored and more demanding version of the original Jepsen test. This ensures they behave as expected under network partitions and failure scenarios. The new test runs continuously to spot possible regressions and is enhanced regularly to test new features (e.g. dead lettering).
What is a Quorum?
If intentionally simplified, quorum in a distributed system can
be defined as an agreement between the majority of nodes ((N/2)+1
where N
is the total number of
system participants).
When applied to queue mirroring in RabbitMQ clusters this means that the majority of replicas (including the currently elected queue leader) agree on the state of the queue and its contents.
Differences between Quorum Queues and Classic Mirrored Queues
Quorum queues share many of the fundamentals with queues of other types in RabbitMQ. However, they are more purpose-built, focus on data safety and predictable recovery, and do not support certain features.
The differences are covered in this guide.
Classic mirrored queues in RabbitMQ have technical limitations that makes it difficult to provide comprehensible guarantees and clear failure handling semantics.
Certain failure scenarios can result in mirrored queues confirming messages too early, potentially resulting in a data loss.
Feature Comparison with Regular Queues
Quorum queues share most of the fundamentals with other queue types. A client library that can use regular mirrored queues will be able to use quorum queues.
The following operations work the same way for quorum queues as they do for regular queues:
- Consumption (subscription)
- Consumer acknowledgements (except for global QoS and prefetch)
- Cancelling consumers
- Purging
- Deletion
With some queue operations there are minor differences:
- Declaration
- Setting prefetch for consumers
Some features are not currently supported by quorum queues.
Feature Matrix
Feature | Classic queues | Quorum queues |
---|---|---|
Non-durable queues | yes | no |
Exclusivity | yes | no |
Per message persistence | per message | always |
Membership changes | automatic | manual |
Message TTL (Time-To-Live) | yes | yes (since 3.10) |
Queue TTL | yes | partially (lease is not renewed on queue re-declaration) |
Queue length limits | yes | yes (except x-overflow : reject-publish-dlx ) |
Lazy behaviour | yes | always (since 3.10) |
Message priority | yes | no |
Single Active Consumer | yes | yes |
Consumer exclusivity | yes | no (use Single Active Consumer) |
Consumer priority | yes | yes |
Dead letter exchanges | yes | yes |
Adheres to policies | yes | yes (see Policy support) |
Poison message handling | no | yes |
Global QoS Prefetch | yes | no |
Server-named queues | yes | no |
Modern quorum queues also offer higher throughput and less latency variability for many workloads.
Non-durable Queues
Classic queues can be non-durable. Quorum queues are always durable per their assumed use cases.
Exclusivity
Exclusive queues are tied to the lifecycle of their declaring connection. Quorum queues by design are replicated and durable, therefore the exclusive property makes no sense in their context. Therefore quorum queues cannot be exclusive.
Quorum queues are not meant to be used as temporary queues.
Queue and Per-Message TTL (since RabbitMQ 3.10)
Quorum queues support both Queue TTL and message TTL (including Per-Queue Message TTL in Queues and Per-Message TTL in Publishers). When using any form of message TTL, the memory overhead increases by 2 bytes per message.
Length Limit
Quorum queues has support for queue length limits.
The drop-head
and reject-publish
overflow behaviours are supported but they
do not support reject-publish-dlx
configurations as Quorum queues take a different
implementation approach than classic queues.
The current implementation of reject-publish
overflow behaviour does not strictly
enforce the limit and allows a quorum queue to overshoot its limit by at least
one message, therefore it should be taken with care in scenarios where a precise
limit is required.
When a quorum queue reaches the max-length limit and reject-publish
is configured
it notifies each publishing channel who from thereon will reject all messages back to
the client. This means that quorum queues may overshoot their limit by some small number
of messages as there may be messages in flight whilst the channels are notified.
The number of additional messages that are accepted by the queue will vary depending
on how many messages are in flight at the time.
Dead Lettering
Quorum queues support dead letter exchanges (DLXs).
Traditionally, using DLXs in a clustered environment has not been safe.
Since RabbitMQ 3.10 quorum queues support a safer form of dead-lettering that uses
at-least-once
guarantees for the message transfer between queues
(with the limitations and caveats outlined below).
This is done by implementing a special, internal dead-letter consumer process that works similarly to a normal queue consumer with manual acknowledgements apart from it only consumes messages that have been dead-lettered.
This means that the source quorum queue will retain the
dead-lettered messages until they have been acknowledged. The internal consumer
will consume dead-lettered messages and publish them to the target queue(s) using
publisher confirms. It will only acknowledge once publisher confirms have been
received, hence providing at-least-once
guarantees.
at-most-once
remains the default dead-letter-strategy for quorum queues and is useful for scenarios
where the dead lettered messages are more of an informational nature and where it does not matter so much
if they are lost in transit between queues or when the overflow
configuration restriction outlined below is not suitable.
Activating at-least-once dead-lettering
To activate or turn on at-least-once
dead-lettering for a source quorum queue, apply all of the following policies
(or the equivalent queue arguments starting with x-
):
- Set
dead-letter-strategy
toat-least-once
(default isat-most-once
). - Set
overflow
toreject-publish
(default isdrop-head
). - Configure a
dead-letter-exchange
. - Turn on feature flag
stream_queue
(turned on by default for RabbitMQ clusters created in 3.9 or later).
It is recommended to additionally configure max-length
or max-length-bytes
to prevent excessive message buildup in the source quorum queue (see caveats below).
Optionally, configure a dead-letter-routing-key
.
Limitations
at-least-once
dead lettering does not work with the default drop-head
overflow
strategy even if a queue length limit is not set.
Hence if drop-head
is configured the dead-lettering will fall back
to at-most-once
. Use the overflow strategy reject-publish
instead.
Caveats
at-least-once
dead-lettering will require more system resources such as memory and CPU.
Therefore, turn on at-least-once
only if dead lettered messages should not be lost.
at-least-once
guarantees opens up some specific failure cases that needs handling.
As dead-lettered messages are now retained by the source quorum queue until they have been
safely accepted by the dead-letter target queue(s) this means they have to contribute to the
queue resource limits, such as max length limits so that the queue can refuse to accept
more messages until some have been removed. Theoretically it is then possible for a queue
to only contain dead-lettered messages, in the case where, say a target dead-letter
queue isn't available to accept messages for a long time and normal queue consumers
consume most of the messages.
Dead-lettered messages are considered "live" until they have been confirmed by the dead-letter target queue(s).
There are few cases for which dead lettered messages will not be removed from the source queue in a timely manner:
- The configured dead-letter exchange does not exist.
- The messages cannot be routed to any queue (equivalent to the
mandatory
message property). - One (of possibly many) routed target queues does not confirm receipt of the message. This can happen when a target queue is not available or when a target queue rejects a message (e.g. due to exceeded queue length limit).
The dead-letter consumer process will retry periodically if either of the scenarios above occur which means there is a possibility of duplicates appearing at the DLX target queue(s).
For each quorum queue with at-least-once
dead-lettering turned on, there will be one internal dead-letter
consumer process. The internal dead-letter consumer process is co-located on the quorum queue leader node.
It keeps all dead-lettered message bodies in memory.
It uses a prefetch size of 32 messages to limit the amount of message bodies kept in memory if no confirms
are received from the target queues.
That prefetch size can be increased by the dead_letter_worker_consumer_prefetch
setting in the rabbit
app section of the
advanced config file if high dead-lettering throughput
(thousands of messages per second) is required.
For a source quorum queue, it is possible to switch dead-letter strategy dynamically from at-most-once
to at-least-once
and vice versa. If the dead-letter strategy is changed either directly
from at-least-once
to at-most-once
or indirectly, for example by changing overflow from reject-publish
to drop-head
, any dead-lettered messages that have not yet been confirmed by all target queues will be deleted.
Messages published to the source quorum queue are persisted on disk regardless of the message delivery mode (transient or persistent). However, messages that are dead lettered by the source quorum queue will keep the original message delivery mode. This means if dead lettered messages in the target queue should survive a broker restart, the target queue must be durable and the message delivery mode must be set to persistent when publishing messages to the source quorum queue.
Lazy Mode
Quorum queues store their message content on disk (per Raft requirements) and only keep a small metadata record of each message in memory. This is a change from prior versions of quorum queues where there was an option to keep the message bodies in memory as well. This never proved to be beneficial especially when the queue length was large.
The memory limit configuration is still permitted but has no
effect. The only option now is effectively the same as configuring: x-max-in-memory-length=0
The lazy
mode configuration does not apply to quorum queues.
Global QoS
Quorum queues do not support global QoS prefetch where a channel sets a single prefetch limit for all consumers using that channel. If an attempt is made to consume from a quorum queue from a channel with global QoS activated a channel error will be returned.
Use per-consumer QoS prefetch, which is the default in several popular clients.
Priorities
Quorum queues support consumer priorities, but not message priorities.
To prioritize messages with Quorum Queues, use multiple queues; one for each priority.
Poison Message Handling (Handling of Repeated Redeliveries)
Unlike classic queues, quorum queues support poison message handling.
Policy Support
Quorum queues can be configured via RabbitMQ policies. The below table summarises the policy keys they adhere to.
Definition Key | Type |
---|---|
max-length | Number |
max-length-bytes | Number |
overflow | "drop-head" or "reject-publish" |
expires | Number (milliseconds) |
dead-letter-exchange | String |
dead-letter-routing-key | String |
max-in-memory-length | Number |
max-in-memory-bytes | Number |
delivery-limit | Number |
Use Cases
Quorum queues are purpose built by design. They are not designed to be used for every problem. Their intended use is for topologies where queues exist for a long time and are critical to certain aspects of system operation, therefore fault tolerance and data safety is more important than, say, lowest possible latency and advanced queue features.
Examples would be incoming orders in a sales system or votes cast in an election system where potentially losing messages would have a significant impact on system correctness and function.
Stock tickers and instant messaging systems benefit less or not at all from quorum queues.
Publishers should use publisher confirms as this is how clients can interact with the quorum queue consensus system. Publisher confirms will only be issued once a published message has been successfully replicated to a quorum of nodes and is considered "safe" within the context of the system.
Consumers should use manual acknowledgements to ensure messages that aren't successfully processed are returned to the queue so that another consumer can re-attempt processing.
When Not to Use Quorum Queues
In some cases quorum queues should not be used. They typically involve:
- Temporary nature of queues: transient or exclusive queues, high queue churn (declaration and deletion rates)
- Lowest possible latency: the underlying consensus algorithm has an inherently higher latency due to its data safety features
- When data safety is not a priority (e.g. applications do not use manual acknowledgements and publisher confirms are not used)
- Very long queue backlogs (streams are likely to be a better fit)
Usage
As stated earlier, quorum queues share most of the fundamentals with other queue types. A client library that can specify optional queue arguments will be able to use quorum queues.
First we will cover how to declare a quorum queue.
Declaring
To declare a quorum queue set the x-queue-type
queue argument to quorum
(the default is classic
). This argument must be provided by a client
at queue declaration time; it cannot be set or changed using a policy.
This is because policy definition or applicable policy can be changed dynamically but
queue type cannot. It must be specified at the time of declaration.
Declaring a queue with an x-queue-type
argument set to quorum
will declare a quorum queue with
up to five replicas (default replication factor), one per each cluster node.
For example, a cluster of three nodes will have three replicas, one on each node. In a cluster of seven nodes, five nodes will have one replica each but two nodes won't host any replicas.
After declaration a quorum queue can be bound to any exchange just as any other RabbitMQ queue.
If declaring using management UI, queue type must be specified using the queue type drop down menu.
Client Operations for Quorum Queues
The following operations work the same way for quorum queues as they do for classic queues:
- Consumption (subscription)
- Consumer acknowledgements (keep QoS Prefetch Limitations in mind)
- Cancellation of consumers
- Purging of queue messages
- Queue deletion
With some queue operations there are minor differences:
- Declaration (covered above)
- Setting QoS prefetch for consumers
Quorum Queue Replication and Data Locality
When a quorum queue is declared, an initial number of replicas for it must be started in the cluster. By default the number of replicas to be started is up to three, one per RabbitMQ node in the cluster.
Three nodes is the practical minimum of replicas for a quorum queue. In RabbitMQ clusters with a larger number of nodes, adding more replicas than a quorum (majority) will not provide any improvements in terms of quorum queue availability but it will consume more cluster resources.
Therefore the recommended number of replicas for a quorum queue is the quorum of cluster nodes (but no fewer than three). This assumes a fully formed cluster of at least three nodes.
Controlling the Initial Replication Factor
For example, a cluster of three nodes will have three replicas, one on each node. In a cluster of seven nodes, three nodes will have one replica each but four more nodes won't host any replicas of the newly declared queue.
Like with classic mirrored queues, the replication factor (number of replicas a queue has) can be configured for quorum queues.
The minimum factor value that makes practical sense is three. It is highly recommended for the factor to be an odd number. This way a clear quorum (majority) of nodes can be computed. For example, there is no "majority" of nodes in a two node cluster. This is covered with more examples below in the Fault Tolerance and Minimum Number of Replicas Online section.
This may not be desirable for larger clusters or for cluster with an even number of
nodes. To control the number of quorum queue members set the
x-quorum-initial-group-size
queue argument when declaring the queue. The
group size argument provided should be an integer that is greater than zero and smaller or
equal to the current RabbitMQ cluster size. The quorum queue will be
launched to run on a random subset of RabbitMQ nodes present in the cluster at declaration time.
In case a quorum queue is declared before all cluster nodes have joined the cluster, and the initial replica count is greater than the total number of cluster members, the effective value used will be equal to the total number of cluster nodes. When more nodes join the cluster, the replica count will not be automatically increased but it can be increased by the operator.
Queue Leader Location
Every quorum queue has a primary replica. That replica is called queue leader. All queue operations go through the leader first and then are replicated to followers (mirrors). This is necessary to guarantee FIFO ordering of messages.
To avoid some nodes in a cluster hosting the majority of queue leader replicas and thus handling most of the load, queue leaders should be reasonably evenly distributed across cluster nodes.
When a new quorum queue is declared, the set of nodes that will host its replicas is randomly picked, but will always include the node the client that declares the queue is connected to.
Which replica becomes the initial leader can controlled using three options:
- Setting the
queue-leader-locator
policy key (recommended) - By defining the
queue_leader_locator
key in the configuration file (recommended) - Using the
x-queue-leader-locator
optional queue argument
Supported queue leader locator values are
client-local
: Pick the node the client that declares the queue is connected to. This is the default value.balanced
: If there are overall less than 1000 queues (classic queues, quorum queues, and streams), pick the node hosting the minimum number of quorum queue leaders. If there are overall more than 1000 queues, pick a random node.
Managing Replicas
Replicas of a quorum queue are explicitly managed by the operator. When a new node is added to the cluster, it will host no quorum queue replicas unless the operator explicitly adds it to a member (replica) list of a quorum queue or a set of quorum queues.
When a node has to be decommissioned (permanently removed from the cluster), it must be explicitly removed from the member list of all quorum queues it currently hosts replicas for.
Several CLI commands are provided to perform the above operations:
rabbitmq-queues add_member [-p <vhost>] <queue-name> <node>
rabbitmq-queues delete_member [-p <vhost>] <queue-name> <node>
rabbitmq-queues grow <node> <all | even> [--vhost-pattern <pattern>] [--queue-pattern <pattern>]
rabbitmq-queues shrink <node> [--errors-only]
To successfully add and remove members a quorum of replicas in the cluster must be available because cluster membership changes are treated as queue state changes.
Care needs to be taken not to accidentally make a queue unavailable by losing the quorum whilst performing maintenance operations that involve membership changes.
When replacing a cluster node, it is safer to first add a new node and then decomission the node it replaces.
Rebalancing Replicas for Quorum Queues
Once declared, the RabbitMQ quorum queue leaders may be unevenly distributed across the RabbitMQ cluster.
To re-balance use the rabbitmq-queues rebalance
command. It is important to know that this does not change the nodes which the quorum queues span. To modify the membership instead see managing replicas.
# rebalances all quorum queues
rabbitmq-queues rebalance quorum
it is possible to rebalance a subset of queues selected by name:
# rebalances a subset of quorum queues
rabbitmq-queues rebalance quorum --queue-pattern "orders.*"
or quorum queues in a particular set of virtual hosts:
# rebalances a subset of quorum queues
rabbitmq-queues rebalance quorum --vhost-pattern "production.*"
Continuous Membership Reconciliation (CMR)
The continuous membership reconciliation (CMR) feature exists in addition to, and not as a replacement for, explicit replica management. In certain cases where nodes are permanently removed from the cluster, explicitly removing quorum queue replicas may still be necessary.
In addition to controlling quorum queue replica membership by using the initial target size and explicit replica management, nodes can be configured to automatically try to grow the quorum queue replica membership to a configured target replica number (group size) by enabling the continuous membership reconciliation feature.
When activated, every quorum queue leader replica will periodically check its current membership group size (the number of replicas online), and compare it with the target value.
If a queue is below the target value, RabbitMQ will attempt to grow the queue onto the availible nodes that do not currently host replicas of said queue, if any, up to the target value.