RabbitMQ Message Acknowledgements
Message acknowledgements (acks) are the mechanism RabbitMQ uses to confirm that a consumer has successfully received and processed a message. Without acknowledgements, messages could vanish silently if a consumer crashes mid-task. Acknowledgements are the reliability backbone of RabbitMQ.
The Restaurant Receipt Analogy
Imagine a kitchen (RabbitMQ) sends a dish to a waiter (consumer). The waiter only gives the kitchen a thumbs up (ack) after the dish is safely placed on the customer's table and the customer has received it. If the waiter trips and drops the dish before reaching the table, the kitchen knows to prepare the dish again — no thumbs up means something went wrong.
What Happens Without Acknowledgements
By default in many examples, auto_ack=True is set. This tells RabbitMQ: "Consider the message delivered the moment you send it to the consumer." If the consumer crashes immediately after receiving the message but before processing it, the message is gone forever. For production systems, this is unacceptable.
Manual Acknowledgement Flow
[RabbitMQ]
|
| 1. Delivers message to consumer
v
[Consumer]
|
| 2. Consumer processes the message (does the actual work)
|
| 3. Consumer sends ack
v
[RabbitMQ receives ack]
|
| 4. RabbitMQ removes message from queue permanently
v
[Message gone from queue]
If step 2 or 3 fails (consumer crashes), RabbitMQ never receives the ack. It requeues the message and delivers it to another available consumer.
Positive Acknowledgement (basic.ack)
A positive ack tells RabbitMQ: "I received and processed this message successfully. Remove it."
def callback(ch, method, properties, body):
try:
process(body) # do the actual work
ch.basic_ack(delivery_tag=method.delivery_tag) # success
except Exception as e:
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True) # failure
Negative Acknowledgement (basic.nack)
A negative ack tells RabbitMQ: "I cannot process this message." The consumer decides whether to requeue it or discard it.
# Requeue the message (it will be delivered again) ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True) # Discard the message (or dead-letter it if DLX is configured) ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False)
Bulk Nack
Set multiple=True to nack all messages up to and including the given delivery tag in one call. This is efficient when you want to reject a batch of messages at once.
ch.basic_nack(delivery_tag=method.delivery_tag, multiple=True, requeue=False)
basic.reject vs basic.nack
basic.reject is the older, simpler form of negative acknowledgement. It only handles one message at a time and has the same requeue flag. basic.nack is newer and adds bulk rejection via the multiple flag. Use basic.nack in modern applications.
Delivery Tag
Every message delivered to a consumer carries a delivery tag — a unique integer per channel that identifies the specific delivery. Pass this tag back when acknowledging or rejecting so RabbitMQ knows exactly which message you are responding to.
def callback(ch, method, properties, body):
delivery_tag = method.delivery_tag # unique ID for this delivery
print(f"Delivery tag: {delivery_tag}")
ch.basic_ack(delivery_tag=delivery_tag)
Multiple Flag on Ack
Set multiple=True on a basic.ack to acknowledge all messages up to and including the given delivery tag in one shot. This reduces the number of network round-trips when processing messages in batches.
# Ack all messages with delivery_tag <= current delivery_tag ch.basic_ack(delivery_tag=method.delivery_tag, multiple=True)
Unacknowledged Messages
Messages delivered to a consumer but not yet acknowledged are called unacknowledged messages (Unacked). RabbitMQ holds them in a special state — they are no longer "Ready" but are not yet gone. If the consumer disconnects with unacked messages, those messages return to "Ready" state immediately and are redelivered.
Queue states of a message: Ready --> message is in the queue waiting for a consumer Unacked --> message is delivered but consumer has not acked yet Deleted --> ack received, message removed from queue
The Management UI shows Ready and Unacked counts separately on the Queues tab.
Redelivery Flag
When a message is redelivered after a consumer failure, the redelivered flag on the message is set to True. Consumers check this flag to detect duplicate deliveries and handle them appropriately (idempotent processing).
def callback(ch, method, properties, body):
if method.redelivered:
print("This message was already delivered once before. Handling carefully.")
# process idempotently...
ch.basic_ack(delivery_tag=method.delivery_tag)
Auto-Ack Mode
In auto-ack mode (auto_ack=True), RabbitMQ marks the message as acknowledged and removes it from the queue the instant it is delivered to the consumer, before the consumer even starts processing. This is faster but not safe — any crash or exception in the consumer loses the message permanently.
Only use auto_ack=True for truly unimportant, lossy data streams (e.g., real-time metrics where losing a data point is acceptable).
Acknowledgement Timeout
RabbitMQ 3.8.15 and later have a consumer acknowledgement timeout (default: 30 minutes). If a consumer holds an unacked message for longer than the timeout, RabbitMQ closes the channel. This protects the broker from consumers that receive messages but never acknowledge them (stuck consumers). Configure the timeout in rabbitmq.conf:
consumer_timeout = 1800000 # 30 minutes in milliseconds
Summary
Acknowledgements are the reliability guarantee in RabbitMQ. A positive ack removes a message permanently. A negative ack requeues or discards the message. Delivery tags uniquely identify each delivery on a channel. Unacknowledged messages stay in memory until the consumer acks or disconnects. The redelivered flag signals duplicate deliveries. Use manual acknowledgements in all production systems — auto-ack trades reliability for speed and is only appropriate for non-critical streams.
