Skip to content

← ~/devcoffee

13 min read

RabbitMQ Classic Queues, Quorum Queues and Reliability

Cover Image for RabbitMQ Classic Queues, Quorum Queues and Reliability

RabbitMQ Classic Queues, Quorum Queues and Reliability

Recently I was trying to learn RabbitMQ properly.

Not just the "producer sends message and consumer reads message" part, because that is the easy part. I wanted to understand what actually happens when we say a queue is durable, what is a quorum queue, why people keep saying publisher confirms and consumer acknowledgements, and what the hell is Raft doing inside a message broker.

And as usual, once you go one layer deeper, everything becomes a rabbit hole.

RabbitMQ looks simple from outside:

producer -> rabbitmq -> consumer

But internally it is more like:

producer -> exchange -> binding -> queue -> consumer

and each part has its own job.

Let's knot down what I learnt.

First, what is a queue?

A queue is just a place where messages wait.

Producer puts messages into RabbitMQ. Consumer takes messages from RabbitMQ.

But the producer usually does not publish directly to a queue. It publishes to an exchange. The exchange looks at the routing key and bindings and decides which queue should get the message.

Producer publishes to an exchange, and RabbitMQ routes the message through a binding into a queue

So when someone says "message went to RabbitMQ", ask:

  • Which exchange?
  • Which routing key?
  • Which queue?
  • Which consumer?

Because if a message disappears, most of the time the issue is not magic, it is routing.

Classic queue

Classic queue is the normal queue type in RabbitMQ.

It is simple, old, and useful for a lot of cases. If you just need a queue where messages sit and consumers read from it, classic queue is usually fine.

Example:

rabbitmqadmin declare queue --name classic.demo --type classic --durable true

The important thing is this: a classic queue is usually owned by one node.

If I have a 3 node cluster:

rabbit1
rabbit2
rabbit3

and I create a classic queue, it might live on rabbit1.

A classic RabbitMQ queue living on one node inside a three node cluster

So if your classic queue is on rabbit1, then rabbit1 is important for that queue.

This was the first thing that clicked for me:

Cluster has 3 nodes does not mean every queue has 3 copies.

The cluster can have 3 nodes, but a classic queue can still have only one queue member.

Durable vs transient queue

This confused me in the beginning.

Durable queue means the queue definition survives broker restart.

durable queue = queue exists after restart
transient queue = queue goes away after restart

But durable queue does not automatically mean every message survives restart.

For that you need two things:

durable queue + persistent message

Simple table:

Queue Message After restart
transient transient queue gone, message gone
transient persistent queue gone, message gone
durable transient queue exists, message may be gone
durable persistent queue exists, message can survive

This is one of those tiny details that can absolutely ruin your day.

You thought "I made the queue durable bro", but your publisher was sending transient messages. Nice. Data gone.

Quorum queue

Quorum queue is RabbitMQ's replicated queue type.

It is meant for queues where losing messages is not okay.

Example:

rabbitmqadmin declare queue --name orders.queue --type quorum --durable true

A quorum queue uses Raft internally.

Raft is a consensus algorithm. Big fancy distributed systems word, but the idea is simple:

Multiple nodes need to agree on the same truth.

If you want to properly understand Raft, these two are worth opening:

In a 3 node RabbitMQ cluster, a quorum queue usually has 3 members:

orders.queue members:
- rabbit1
- rabbit2
- rabbit3

One node is the leader. The others are followers.

A quorum queue with one leader node and two follower nodes replicating messages

When a producer publishes a message:

  1. Message reaches the quorum queue leader.
  2. Leader writes it.
  3. Leader replicates it to followers.
  4. Majority agrees.
  5. Message is considered committed.
  6. Consumer can read it.

In 3 nodes, majority is 2.

3 members -> majority 2 -> can survive 1 member failure
5 members -> majority 3 -> can survive 2 member failures

This is why quorum queues are safer, but not free.

Every message has extra work:

  • write locally
  • replicate
  • wait for majority
  • track log position
  • handle leader election if leader dies

So yes, quorum queue will generally have more latency than a simple classic queue. That is the cost of not losing important messages.

Is the message copied to all nodes?

In a 3 member quorum queue, yes the queue has members on all 3 nodes.

So if you publish one message to a quorum queue, that message becomes part of the replicated queue log across those members.

But RabbitMQ does not need to wait for all 3 nodes before saying the message is safe. It needs a majority.

For 3:

leader + 1 follower = majority

So if one follower is slow, the queue can still continue.

A producer publishing to a quorum queue leader, with majority replication before publisher confirm

This is the balance:

  • classic queue: less coordination, less safety
  • quorum queue: more coordination, more safety

What happens when consumer reads the message?

Consumer reading a message does not delete it immediately.

This is very important.

RabbitMQ has the concept of acknowledgement.

consumer receives message != message deleted
consumer sends ack = message can be removed

Flow:

A queue delivering a message to a consumer and deleting it only after the consumer acknowledgement

If the consumer crashes before ack:

message goes back to queue
another consumer can retry

If the consumer acks and then your application crashes:

RabbitMQ thinks work is done
message is gone

So do not ack before actually completing the work.

Bad:

receive -> ack -> process -> crash

Good:

receive -> process -> ack

For quorum queues, the ack/removal state is also replicated. Internally it may clean disk bytes later because logs get compacted, but logically the message is gone once the acknowledgement is committed.

Publisher confirms

Consumer ack is from consumer to RabbitMQ.

Publisher confirm is from RabbitMQ to producer.

Do not mix them.

Publisher confirms flow from RabbitMQ to producer, while consumer acknowledgements flow from consumer to RabbitMQ

Publisher confirm answers:

Did RabbitMQ safely accept my message?

Consumer ack answers:

Did the consumer successfully process the message?

If you care about reliability, use both.

For quorum queues, publisher confirms are even more important because a confirm means the message has gone through the quorum queue safety path.

Without publisher confirms, the producer is basically throwing messages and hoping for the best.

Hope is not a reliability strategy lol.

What if a node dies?

Let's say we have a quorum queue with 3 members:

rabbit1
rabbit2
rabbit3

If rabbit2 dies:

still 2 nodes alive
majority exists
queue works

If rabbit2 and rabbit3 die:

only 1 node alive
majority lost
queue unavailable

This is not RabbitMQ being stupid. This is the whole point of quorum.

If the minority side was allowed to continue accepting writes, then when the other nodes come back you could have two different versions of truth.

Distributed systems are annoying exactly because of this.

What if I add more nodes later?

This was another thing that I misunderstood.

Suppose your cluster has 3 nodes and your quorum queue has 3 members.

Then you add 2 more nodes:

rabbit1
rabbit2
rabbit3
rabbit4
rabbit5

Existing quorum queues do not automatically become 5 member queues.

Cluster membership is not same as queue membership.

A five node RabbitMQ cluster where only the first three nodes are quorum queue members

The queue still has its old members until you explicitly grow/rebalance the queue.

You can add members to a quorum queue, but do it intentionally.

Do not make every queue live on every node because it feels safe. More replicas means more writes, more coordination, more disk, and more latency.

Typical quorum queue sizes:

Members Majority Failure tolerance
3 2 1 node
5 3 2 nodes
7 4 3 nodes

Even numbers are usually not worth it.

4 members -> majority 3 -> still tolerates only 1 failure
6 members -> majority 4 -> still tolerates only 2 failures

So 3 or 5 is usually the sane choice.

Classic queue vs quorum queue

Here is the simple mental model.

Use classic queue when:

  • queue is not super critical
  • you want lower overhead
  • you have temporary queues
  • you have high churn queues
  • you do not need queue-level replication

Use quorum queue when:

  • message loss hurts
  • queue is long lived
  • queue is important to business flow
  • you need replication
  • you are ready to use publisher confirms and consumer acknowledgements

Do not use quorum queues just because "distributed sounds cool".

If you use quorum queues without publisher confirms and manual acks, you are paying the cost but not getting the full reliability benefit.

Sideline queue / dead letter queue

Sometimes a message is bad.

Maybe payload is wrong. Maybe downstream service is down. Maybe the consumer has a bug. Maybe this one message keeps failing again and again and blocks the flow.

This is called a poison message.

Instead of retrying forever like an idiot, we send it aside.

That aside queue is often called:

  • dead letter queue
  • sideline queue
  • parking lot queue
  • error queue
  • quarantine queue

The idea is:

A failed message being routed from the main orders queue to a sideline queue through a dead letter exchange

RabbitMQ's mechanism is dead lettering.

"Sideline queue" is usually just the business name teams give to the queue where failed messages are parked.

This helps because the main queue can continue processing good messages.

Without this:

one bad message -> retry loop -> consumer busy -> queue grows -> everyone cries

With sideline:

bad message -> parked aside -> main flow continues

This is one of those reliability patterns that sounds small but saves production systems.

Reliability checklist

If I had to build a serious RabbitMQ flow, I would check these things:

1. Durable queues

Queue should survive restart.

durable = true

For quorum queues this is already the model.

2. Persistent messages

Message should survive restart.

delivery_mode = 2

3. Publisher confirms

Producer should know RabbitMQ safely accepted the message.

Without this, producer failure cases become guesswork.

4. Manual consumer acknowledgements

Consumer should ack only after work is done.

Not before.

5. Prefetch

Do not let one consumer take unlimited messages and sit on them.

Prefetch controls how many unacked messages a consumer can hold.

prefetch = 10

means one consumer can hold 10 unacked messages at a time.

6. Dead letter / sideline queue

Bad messages should not destroy the main queue.

Put failed messages somewhere visible.

7. Idempotent consumers

Even with RabbitMQ reliability, duplicates can happen.

At-least-once delivery means:

message can be delivered more than once

So consumer should be able to safely process duplicate messages.

Example:

payment_completed event comes twice
do not ship two products bro

Use idempotency keys, unique constraints, processed message tables, or whatever fits your system.

8. Monitoring

Watch these:

  • ready messages
  • unacked messages
  • consumer count
  • publish rate
  • deliver rate
  • ack rate
  • node memory
  • disk free
  • connection count
  • queue leader
  • quorum queue members online

If ready messages keep growing, consumers are not keeping up.

If unacked messages keep growing, consumers are taking messages but not finishing.

If publishers are blocked, check memory and disk alarms.

Management UI, what to actually look at

RabbitMQ UI looks like a cockpit when you first open it.

But for queues, focus on these:

Ready

Messages waiting in queue.

ready = not delivered yet

Unacked

Messages delivered to consumers but not acknowledged yet.

unacked = consumer has it, RabbitMQ is waiting

Consumers

Number of consumers attached to the queue.

If this is zero, nobody is reading.

Type

Classic or quorum.

Leader

For quorum queue, tells which node is currently leading the queue.

Members / online

For quorum queue, tells which nodes are members and which are currently reachable.

If your quorum queue has 3 members but only 1 online, you have a problem.

How this shit helps

RabbitMQ is useful because it decouples services.

Without queue:

checkout service -> payment service -> invoice service -> email service

If email service is down, checkout might become slow or fail.

With queue:

Checkout service publishing to an exchange, with payment, invoice and email workers consuming from separate queues

Now checkout can publish an event and move on.

If email service is slow, email queue grows, but checkout does not need to wait for it.

This gives:

  • async processing
  • buffering during spikes
  • retry
  • failure isolation
  • independent scaling
  • better user experience

But RabbitMQ is not a magic reliability machine.

You still need to design for failures.

The broker can help you, but it will not fix a stupid consumer that acks too early, or a producer that never checks confirms, or a team that has no dead letter queue.

Final mental model

Classic queue:

simple queue, usually one queue member, lower overhead

Quorum queue:

replicated durable queue, Raft based, majority decides truth

Consumer acknowledgement:

consumer tells RabbitMQ work is done

Publisher confirm:

RabbitMQ tells producer message is safely accepted

Dead letter / sideline queue:

place where failed messages go so main flow does not get stuck

Reliability:

durable queue
+ persistent messages
+ publisher confirms
+ manual consumer acks
+ dead letter queue
+ idempotent consumers
+ monitoring

That is the whole game.

RabbitMQ is not hard because queues are hard.

RabbitMQ is hard because reliability is hard.

References

view raw