Message Persistence
IntermediateSet delivery_mode=2 (persistent) on messages AND use a durable queue to survive broker restart; fsync overhead makes persistent messages slower than transient ones.
Overview
RabbitMQ message persistence requires two independent settings to work together: the queue must be declared durable (survives broker restart) and each message must have delivery_mode=2 (written to disk before the broker acknowledges the publish). If either is missing — transient message on a durable queue, or persistent message on a non-durable queue — messages are lost on restart. Disk writes are batched for throughput, but this means persistence adds latency compared to in-memory transient queuing. For maximum durability guarantees, combine persistence with publisher confirms: the broker only sends an ack after the message is fsynced to disk. Quorum queues (RabbitMQ 3.8+) are the modern alternative — they replicate to a majority of nodes before acking, providing stronger guarantees than classic durable queues.
Declaring a durable queue with persistent messages
Both the queue durability flag and per-message delivery_mode must be set. Spring AMQP's MessageProperties.PERSISTENT_TEXT_PLAIN is shorthand for delivery_mode=2.
@Configuration
public class RabbitConfig {
// durable=true: queue survives broker restart
// autoDelete=false: not deleted when last consumer disconnects
@Bean
public Queue ordersQueue() {
return QueueBuilder.durable("orders.queue")
.withArgument("x-queue-type", "quorum") // Recommended: quorum queue
.build();
}
}
// Publishing a persistent message
rabbitTemplate.convertAndSend("orders.exchange", "new-order", event, message -> {
message.getMessageProperties()
.setDeliveryMode(MessageDeliveryMode.PERSISTENT); // delivery_mode=2
return message;
});
// Or use the built-in MessagePostProcessor shorthand
rabbitTemplate.convertAndSend("orders.exchange", "new-order",
event, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message msg) {
msg.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return msg;
}
});Publisher confirms for end-to-end durability
Persistence alone does not guarantee delivery — the publish can succeed but the broker can crash before fsyncing. Publisher confirms close this gap: the broker only acks after writing to disk.
@Bean
public CachingConnectionFactory connectionFactory() {
CachingConnectionFactory cf = new CachingConnectionFactory("localhost");
cf.setPublisherConfirmType(
CachingConnectionFactory.ConfirmType.CORRELATED); // async confirms
return cf;
}
@Bean
public RabbitTemplate rabbitTemplate(CachingConnectionFactory cf) {
RabbitTemplate template = new RabbitTemplate(cf);
template.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
log.info("Message confirmed: {}", correlationData.getId());
} else {
log.error("Nack received — requeue or alert: {}", cause);
// trigger retry logic or dead-letter processing
}
});
template.setMandatory(true); // trigger returns for unroutable messages
template.setReturnsCallback(returned ->
log.error("Message returned unrouted: {}", returned.getMessage()));
return template;
}Classic durable vs Quorum queues — when to use which
Classic mirrored queues are deprecated since RabbitMQ 3.9. Quorum queues use Raft consensus for replication, providing stronger durability and are the recommended replacement.
# Classic durable queue (single node or classic mirrored — deprecated)
# Survives restart but all data on one node; mirroring adds replication lag
# Quorum queue — recommended for production durability
@Bean
public Queue paymentsQueue() {
return QueueBuilder.durable("payments.queue")
.quorum() // x-queue-type: quorum
.deliveryLimit(5) // max redelivery attempts
.build();
}
# Quorum queues replicate to (N/2 + 1) nodes before acking
# No message loss on single-node failure
# Requires RabbitMQ 3.8+ with at least 3 nodes for full benefit
# application.properties tuning
spring.rabbitmq.template.default-receive-queue=payments.queue
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.simple.prefetch=10Key Points to Remember
- 1Both queue durability AND message delivery_mode=2 are required — one without the other is not persistence.
- 2Persistent messages add latency because RabbitMQ must fsync before acknowledging; batch fsyncing mitigates this.
- 3Publisher confirms provide at-least-once delivery: only send after the broker confirms disk write.
- 4Classic mirrored queues are deprecated since RabbitMQ 3.9; use Quorum queues for replicated persistence.
- 5Quorum queues require a majority of replicas to be available — plan your cluster size (3 or 5 nodes) accordingly.
- 6For very high throughput, consider separating persistent critical queues (payments) from transient queues (logs) on different vhosts.
Interview Questions
Sign in to ask AriaWhat are the two conditions required for a message to survive a RabbitMQ broker restart?
What is the difference between publisher confirms and transactions in RabbitMQ?
Why are classic mirrored queues deprecated and what should you use instead?
Explain how quorum queues use Raft consensus and what "delivery limit" prevents.
A persistent message was published but lost after a broker crash with no publisher confirms. What went wrong?
Ask Aria about Message Persistence
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.