Formal proof of ACK temporal collision (at-least-once delivery double execution) with TLA+ and chaos cross-validation #16909
-
SummaryWe independently proved and reproduced the ACK temporal collision in RabbitMQ — a race condition where a consumer crashes after storing a result but before the AMQP acknowledgment reaches the broker, causing the redelivered message to be processed twice. This is documented behavior (RabbitMQ at-least-once delivery guarantees), not an implementation bug. Our contribution is:
The ScenarioFormal Proof (TLA+)We modeled the system as a TLA+ specification with three actions:
Results:
Chaos Cross-ValidationWe wrote a pilot (
Results:
Cross-Broker ValidationWe adapted the same TLA+ model and chaos pilot for NATS JetStream. The results matched:
Verdict: 4/4 TLC predictions matched 4/4 chaos experiments. The FixThe recommended consumer-side fix is the idempotent consumer pattern: def callback(ch, method, properties, body):
task_id = int(body.decode())
# Idempotent guard: check before processing
if should_skip(task_id):
ch.basic_ack(delivery_tag=method.delivery_tag)
return
store_result(task_id) # INSERT into DB
ch.basic_ack(delivery_tag=method.delivery_tag)The TLA+ model proved this fix under all possible failure interleavings — not just happy-path testing. RepositoryAll code is open-source at https://github.com/illyar80/utlp-lpm-agent:
DiscussionWe believe this is valuable as a formally verified demonstration of RabbitMQ's at-least-once delivery semantics and the critical importance of the idempotent consumer pattern. Our TLA+ model and chaos framework are portable to any message broker — we proved this by running the identical experiment on NATS JetStream. We would love feedback from the RabbitMQ team and community:
|
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
|
No kidding, consumers may get redeliveries and may need to be idempotent. Our tutorial 2 says as much. |
Beta Was this translation helpful? Give feedback.
No kidding, consumers may get redeliveries and may need to be idempotent. Our tutorial 2 says as much.