Persist Messages with Delivery Mode 2

Persist messages with delivery mode 2 — Messaging & queues tutorial, lesson 7. Learn how to make your messages durable, ensure they survive broker restarts, and apply this in a hands-on exercise.

Focus: persist messages with delivery mode 2

Sponsored

You've built a producer, a consumer, and a queue that moves messages around nicely — until the broker restarts and every unacknowledged message vanishes into thin air. That's the exact pain point this lesson solves: how to make messages survive crashes and reboots using delivery mode 2 (also called persistent delivery). If you're running anything more than a toy demo, losing messages on restart is the kind of bug that gets you paged at 3 a.m. — so let's fix it properly.

The problem this lesson solves

Imagine you've just deployed a payment processing service. Your producer sends a "charge the customer" message to RabbitMQ, and the broker holds it until your consumer picks it up. Everything looks perfect — until the broker's host runs out of memory and restarts. When it comes back, that message is gone. No error, no trace, just a silent loss of critical data. That's the default behavior: delivery mode 1 (transient) keeps messages only in memory, so any broker restart wipes them out.

The lesson's pain is real and urgent: in production, message loss is often worse than downtime. A queue that seems to work in dev can fail catastrophically under restart conditions. You need a way to tell the broker, "This message matters — write it to disk before you acknowledge it." That's what delivery mode 2 does. It's the difference between a message that lives only in RAM and one that's safely stored and can be redelivered after a crash.

By the end of this lesson, you'll know how to persist messages with delivery mode 2, why it's essential for reliability, and how to apply it in a practical exercise. You'll also understand when persistent delivery is the right call and when it's overkill.

Core concept / mental model

Think of the broker as a post office. In transient mode (delivery mode 1), the post office holds your letters in a clerk's hands — fast to process, but if the clerk drops them, they're gone. In persistent mode (delivery mode 2), the clerk writes your letter into a ledger before promising delivery. The ledger lives on disk, so even if the post office burns down, the letter is recoverable later.

In RabbitMQ terms, a message marked with delivery_mode: 2 is written to the queue's durable storage before the broker sends an acknowledgment back to the producer. This ensures that the message survives broker restarts and can be redelivered if the consumer hasn't acked it yet. The key players are:

  • Producer — the client that sends the message.
  • Broker (RabbitMQ) — the intermediary that stores and forwards messages.
  • Queue durability — must be declared with durable=True so the queue itself survives a restart.
  • Delivery mode 2 — the per-message flag that tells the broker to persist that specific message.

Pro tip: Delivery mode 2 only works if the queue is also durable. A persistent message in a non-durable queue is like a letter in a ledger for a warehouse that's scheduled for demolition — the ledger survives, but the building doesn't.

A common misconception is that delivery mode 2 alone makes a message permanent. In reality, it's a two-part contract: the queue must be durable (declared with durable=True) and the message must be sent with delivery_mode=2. If either is missing, messages can still be lost on broker restart.

How it works step by step

The flow for persisting a message with delivery mode 2 is straightforward, but each step matters:

1. Declare a durable queue

The producer and consumer must declare the same queue with durable=True. This tells RabbitMQ to store the queue definition and its messages on disk. If you don't do this, the queue exists only in memory and will be deleted on restart — regardless of delivery mode.

2. Publish with delivery mode 2

When publishing, you set the delivery_mode property to 2. Most client libraries (like pika for Python) expose this as BasicProperties(delivery_mode=2). This marks the message as persistent.

3. Broker writes to disk (and confirms)

Upon receiving the message, the broker writes it to disk, then sends a publish confirmation back to the producer (if publisher confirms are enabled). This is a performance cost, but it's the price for durability.

4. Consumer receives and acknowledges

The consumer receives the message and, after processing it, sends an acknowledgment (basic_ack). Until that ack is received, the broker considers the message unprocessed.

5. Broker restart recovery

If the broker restarts before ack, it reloads the persistent message from disk and can redeliver it to the same or another consumer. Without delivery mode 2, the message is gone.

Hands-on walkthrough

Let's build a small RabbitMQ example in Python using pika. You'll need a running RabbitMQ server (Docker works great). We'll create a durable queue, publish a persistent message, verify it's still there after restart, and consume it.

Setup

First, install pika if you haven't already:

pip install pika

For a quick RabbitMQ server, use Docker:

docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management

Producer with delivery mode 2

Here's a complete producer that sends a message with delivery_mode=2:

import pika

# Connect to RabbitMQ
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

# Declare a durable queue
channel.queue_declare(queue='persistent_queue', durable=True)

# Publish a message with delivery mode 2
message = "payment: user_123, amount: 100.00"
channel.basic_publish(
    exchange='',
    routing_key='persistent_queue',
    body=message,
    properties=pika.BasicProperties(
        delivery_mode=2,  # Persistent delivery
    ),
)
print(f" [x] Sent '{message}' with delivery mode 2")

connection.close()

Expected output: [x] Sent 'payment: user_123, amount: 100.00' with delivery mode 2

Consumer with acknowledgment

Now a consumer that processes the message and acks it:

import pika
import time

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

# Declare the same durable queue
channel.queue_declare(queue='persistent_queue', durable=True)

def callback(ch, method, properties, body):
    print(f" [x] Received {body.decode()}")
    time.sleep(1)  # Simulate work
    ch.basic_ack(delivery_tag=method.delivery_tag)
    print(" [x] Done and acknowledged")

channel.basic_consume(queue='persistent_queue', on_message_callback=callback)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()

Run the producer, then the consumer. After the consumer processes, the message is gone from the queue.

Verify persistence across restart

To see durability in action:

  1. Send a few messages with delivery mode 2 (but don't run the consumer).
  2. Stop the RabbitMQ container: docker stop rabbitmq
  3. Start it again: docker start rabbitmq
  4. Check the queue — the messages should still be unacked and redeliverable.

You can inspect the queue with the management UI at http://localhost:15672 (guest/guest). You'll see the queue has messages waiting, proving they survived the restart.

Compare options / when to choose what

Delivery mode 2 is the default choice for critical messages, but it isn't the only option. Here's a comparison:

Aspect Delivery mode 1 (transient) Delivery mode 2 (persistent)
Storage Memory only Disk + memory
Survives broker restart? No Yes
Performance Faster (no disk I/O) Slower (disk write per message)
Use cases Logs, metrics, ephemeral data Orders, payments, events that must not lose
Queue durability required? Not necessarily Yes (durable=True)

When to choose what?

  • Use delivery mode 2 for any message where loss is unacceptable: payments, user actions, state changes.
  • Use delivery mode 1 for high-throughput, low-value messages where a little loss is tolerable (e.g., telemetry that's aggregated elsewhere).

Variations: Lazy queues and publisher confirms

  • Lazy queues: RabbitMQ can store queues entirely on disk (lazy queues) to reduce RAM pressure, at the cost of latency. This pairs well with delivery mode 2 for very large queues.
  • Publisher confirms: Enable publisher_confirms to know when a message is actually persisted. This gives stronger guarantees than delivery mode alone.
  • Dead-letter exchanges: For messages that can't be processed, combine with a dead-letter exchange — but it doesn't affect persistence.

Troubleshooting & edge cases

Message lost even with delivery mode 2

  • Queue not durable: If the queue is declared without durable=True, the queue itself won't survive a restart, and messages are lost. Ensure both producer and consumer declare the same durable queue.
  • Producer didn't set delivery_mode: Double-check your code — delivery_mode must be set to 2 in the BasicProperties. A typo or missing parameter silently defaults to mode 1.
  • Broker crashes before flush: Delivery mode 2 gives a strong guarantee, but it's not atomic. In RabbitMQ, a message is written to disk before ack, but there's a tiny window during a crash where the write may not be flushed. Use publisher confirms and a durable queue to minimize this.

Performance degradation

Persistent messages are slower. If throughput drops, consider batching or using a mix of transient and persistent queues, based on message importance.

"PRECONDITION_FAILED" error

If you try to declare a queue with different durability settings (e.g., one as durable, another as non-durable) with the same name, RabbitMQ throws an error. Always use the same settings across producer and consumer.

What you learned & what's next

You've mastered the core concept: persist messages with delivery mode 2 makes a message durable by writing it to disk, but only if the queue is also durable. You've seen the mental model, implemented it in Python with pika, and learned how to verify persistence across broker restarts. You also know when to use persistent delivery versus transient, and how to troubleshoot common pitfalls.

In the next lesson, we'll explore acknowledgment strategies — how to handle messages that fail processing, and why basic_ack vs basic_nack can be a matter of life or death for your queue. Stay tuned, and keep your messages safe!

Practice recap

Try this: send 10 messages with delivery mode 2 to a durable queue, then restart your RabbitMQ broker without running a consumer. Verify the messages are still in the queue. Then, run the same test with delivery mode 1 and see the difference. This hands-on contrast will make the concept stick.

Common mistakes

  • Declaring the queue without durable=True while using delivery_mode=2 — the queue itself won't survive a restart, so messages are lost.
  • Forgetting to set delivery_mode=2 in BasicProperties — the message stays transient, and you get no error.
  • Declaring a queue as durable in one part of your app and non-durable in another, causing a PRECONDITION_FAILED error on restart.

Variations

  1. Use lazy queues to store messages entirely on disk, reducing memory usage for large or slow-consuming queues.
  2. Enable publisher confirms to get a guarantee that your message was persisted before moving on.
  3. Combine delivery mode 2 with a dead-letter exchange to handle messages that fail processing without losing them.

Real-world use cases

  • E-commerce order placement — a payment message must not be lost, even if the broker restarts mid-transaction.
  • Financial transaction logging where every debit/credit event needs to be durable for audit compliance.
  • Inventory update notifications that trigger warehouse actions — losing them could mean overselling stock.

Key takeaways

  • Delivery mode 2 persists a message to disk, but only works if the queue itself is durable.
  • Always set delivery_mode=2 in BasicProperties and declare the queue with durable=True on both producer and consumer.
  • Persistent delivery has a performance cost — use it for critical messages, not for throwaway data.
  • Publisher confirms add an extra guarantee that your message was actually written to disk.
  • Test persistence by restarting the broker after sending messages, and check that they survive.
  • Mismatched durability settings between producer and consumer cause runtime errors — keep them consistent.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.