RabbitMQ Quorum Queues — A Simple Guide with Go (Beginner Friendly)
If you’ve used RabbitMQ before, you’re probably familiar with classic queues. They are simple, fast, and work well — until something goes…
RabbitMQ Quorum Queues — A Simple Guide with Go (Beginner Friendly)
If you’ve used RabbitMQ before, you’re probably familiar with classic queues. They are simple, fast, and work well — until something goes wrong.
The moment a RabbitMQ node crashes, classic queues can lose messages. For systems that deal with payments, orders, or critical workflows, this is unacceptable.
This is exactly why Quorum Queues were introduced.
In this article, we’ll cover:
- Why quorum queues exist
- The problem they solve (in very simple terms)
- How to deploy RabbitMQ in high availability (HA) mode using Kubernetes
- How to create and use a quorum queue in Go
- How to verify everything using the RabbitMQ Management UI
No deep RabbitMQ knowledge required. If you know basic messaging concepts, you’re good to go.
1. The Problem with Classic Queues
Let’s start with a simple scenario:
- A producer publishes a message
- RabbitMQ stores it on one node
- That node crashes 💥
- The message is gone ❌
This happens because classic queues live on a single node. Even if your RabbitMQ cluster has multiple nodes, a classic queue belongs to exactly one of them.
This behavior is risky for:
- Payment processing systems
- Order management systems
- Event-driven workflows where data loss is unacceptable
You can mirror classic queues, but they are deprecated and come with their own operational issues.
So what’s the modern solution?
2. What Is a Quorum Queue?
A quorum queue is a replicated, fault-tolerant queue built on the Raft consensus algorithm.
In simple words:
- Messages are stored on multiple RabbitMQ nodes
- A majority of nodes must agree before a message is accepted
- The queue continues working even if a node crashes
Example
If you have 3 RabbitMQ nodes:
- At least 2 nodes must be alive (majority)
- If 1 node crashes, messages are still safe ✅
- If 2 nodes crash, the queue becomes unavailable (no majority)
You can think of quorum queues as distributed consensus for messages.
Reliability over raw speed — that’s the tradeoff quorum queues make.
3. Running RabbitMQ in HA Mode (Kubernetes)
To use quorum queues properly, you need a RabbitMQ cluster.
The easiest way to run RabbitMQ in Kubernetes is by using the RabbitMQ Cluster Operator.
Step 1: Install the RabbitMQ Cluster Operator
kubectl apply -f "https://github.com/rabbitmq/cluster-operator/releases/latest/download/cluster-operator.yml"
This installs all the required CRDs and controllers.
Step 2: Create a RabbitMQ Cluster
Create a file called cluster.yaml:
apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
name: rabbitmqcluster
spec:
replicas: 3
resources:
requests:
cpu: 500m
memory: 2Gi
limits:
cpu: 700m
memory: 2Gi
persistence:
storage: 1Gi
Apply it:
kubectl apply -f cluster.yaml
What Happens After Deployment?
- Kubernetes creates 3 RabbitMQ pods

- Each pod runs a RabbitMQ broker
- When you create a quorum queue
- One node is elected as the leader
- Other nodes act as followers (replicas)
When a message is published:
- The leader receives the message
- The message is replicated to follower nodes
- Once a majority confirms, the message is accepted
If the leader crashes:
- One of the followers becomes the new leader
- The queue continues serving requests
This guarantees high availability and data safety.
4. Creating a Quorum Queue in Go
Now let’s write a simple Go program that:
- Connects to RabbitMQ
- Creates an exchange
- Creates a quorum queue
- Publishes a message
- Consumes the message
We’ll use the official Go client:
go get github.com/rabbitmq/amqp091-go
Step 1: Connect to RabbitMQ
url := "amqp://username:password@localhost:5672/"
conn, err := amqp091.Dial(url)
if err != nil {
panic(err)
}
defer conn.Close()
ch, err := conn.Channel()
if err != nil {
panic(err)
}
defer ch.Close()
This establishes a connection and opens a channel.
Step 2: Declare an Exchange
err = ch.ExchangeDeclare(
"quorum-exchange",
"direct",
true,
false,
false,
false,
nil,
)
if err != nil {
panic(err)
}
This creates a durable direct exchange.
Step 3: Declare a Quorum Queue (Important Part)
This is the only difference compared to a classic queue.
_, err = ch.QueueDeclare(
"quorum-q",
true,
false,
false,
false,
amqp091.Table{
"x-queue-type": amqp091.QueueTypeQuorum,
},
)
if err != nil {
panic(err)
}
📌 Important Notes
- Queue type must be defined at creation time
- A classic queue cannot be converted to a quorum queue later
Step 4: Bind Queue to Exchange
err = ch.QueueBind(
"quorum-q",
"rk",
"quorum-exchange",
false,
nil,
)
if err != nil {
panic(err)
}
Step 5: Publish a Message
err = ch.PublishWithContext(
context.Background(),
"quorum-exchange",
"rk",
false,
false,
amqp091.Publishing{
ContentType: "text/plain",
Body: []byte("message"),
},
)
if err != nil {
panic(err)
}
Step 6: Consume the Message
msgs, err := ch.Consume(
"quorum-q",
"consumer",
false,
false,
false,
false,
nil,
)
if err != nil {
panic(err)
}
for msg := range msgs {
fmt.Printf("message from queue: %s\n", string(msg.Body))
msg.Ack(false)
}
Using manual acknowledgements ensures:
- Messages aren’t lost if the consumer crashes
- Messages are re-delivered if processing fails
5. Full Working Example
package main
import (
"context"
"fmt"
"github.com/rabbitmq/amqp091-go"
)
func main() {
url := "amqp://username:password@localhost:5672/"
conn, err := amqp091.Dial(url)
if err != nil {
panic(err)
}
defer conn.Close()
ch, err := conn.Channel()
if err != nil {
panic(err)
}
defer ch.Close()
err = ch.ExchangeDeclare(
"quorum-exchange",
"direct",
true,
false,
false,
false,
nil,
)
if err != nil {
panic(err)
}
_, err = ch.QueueDeclare(
"quorum-q",
true,
false,
false,
false,
amqp091.Table{
"x-queue-type": amqp091.QueueTypeQuorum,
},
)
if err != nil {
panic(err)
}
err = ch.QueueBind(
"quorum-q",
"rk",
"quorum-exchange",
false,
nil,
)
if err != nil {
panic(err)
}
err = ch.PublishWithContext(
context.Background(),
"quorum-exchange",
"rk",
false,
false,
amqp091.Publishing{
ContentType: "text/plain",
Body: []byte("message"),
},
)
if err != nil {
panic(err)
}
msgs, err := ch.Consume(
"quorum-q",
"consumer",
false,
false,
false,
false,
nil,
)
if err != nil {
panic(err)
}
for msg := range msgs {
fmt.Printf("message from queue: %s\n", string(msg.Body))
msg.Ack(false)
}
}
Final Thoughts
Quorum queues are the recommended choice for:
- Mission-critical messaging
- Distributed systems
- Production workloads where data loss is unacceptable
They trade a bit of throughput for strong consistency and fault tolerance — a tradeoff that is usually worth it.
If you’re building serious systems with RabbitMQ today, quorum queues should be your default choice.
Happy messaging 🚀
메타데이터
- post_id
- 7c6473d8bf10
- slug
- rabbitmq-quorum-queues-a-simple-guide-with-go-beginner-friendly-7c6473d8bf10
- url
- https://medium.com/@sharmasarvesh826/rabbitmq-quorum-queues-a-simple-guide-with-go-beginner-friendly-7c6473d8bf10
- canonical_url
- https://medium.com/@sharmasarvesh826/rabbitmq-quorum-queues-a-simple-guide-with-go-beginner-friendly-7c6473d8bf10
- author_url
- https://medium.com/@sharmasarvesh826
- status
- ok
- fetched_at
- 2026-06-09 15:37:30