10 AMQP Delivery: Sizing and Reliability Contracts
Start with the story: An AMQP message is a parcel with a label, a body, and a delivery receipt path. The chapter is about choosing when that parcel may be dropped, retried, acknowledged, or parked for investigation.
10.1 Start With the Decision
Five hundred machines can turn a safe message rule into a queue bottleneck. Size, rate, prefetch, and failure flow must be counted.
10.2 Route Overview
This is part 2 of 2. Review AMQP Delivery: Guarantees and Acknowledgements for the preceding evidence.
10.3 Learning Objectives
- Calculate smart-factory message volume and throughput.
- Verify an AMQP reliability chain with explicit contracts.
10.4 Chapter Roadmap
- Worked Example: Smart Factory Message Sizing
- Putting Numbers to It
- Checkpoint: Sizing the Reliability Chain
- Knowledge Check
- Quiz: Match Concepts to Definitions
- Label the Diagram
- Code Challenge
- Order the Steps
- Design Contract: Delivery Reliability Chain
- AMQP Delivery Reliability Chain Contracts
- Summary
- What’s Next
- Key Takeaway
10.5 Worked Example: Smart Factory Message Sizing
Now combine structure, guarantees, acknowledgments, and DLQ handling. Pay for reliability where the consequence justifies it.
Scenario: A pharmaceutical manufacturing plant monitors 500 machines via AMQP. Each machine publishes three types of messages with different delivery requirements:
| Message Type | Frequency | Payload | Delivery Need |
|---|---|---|---|
| Temperature telemetry | Every 5 seconds | 120 bytes JSON | Occasional loss OK |
| Quality alert | On threshold breach (~10/hour/machine) | 350 bytes JSON | Must not be lost |
| Batch completion event | ~2 per hour per machine | 800 bytes JSON | Exactly once (audit trail) |
Step 1: Calculate message rates and bandwidth
- Telemetry: 500 machines x 1 msg/5s = 100 msg/s
- Quality alerts: 500 x 10/hr = 5,000/hr = 1.39 msg/s
- Batch events: 500 x 2/hr = 1,000/hr = 0.28 msg/s
- Total: ~102 msg/s
Step 2: Calculate per-message AMQP overhead
Each AMQP message includes:
- Frame header: 8 bytes
- Method frame (basic.publish): ~40 bytes (exchange name, routing key)
- Content header: ~60 bytes (properties: delivery_mode, content_type, timestamp, message_id, correlation_id)
- Content body frame: 8 bytes header + payload
Total overhead per message: ~116 bytes
Step 3: Choose delivery mode for each type
| Type | QoS | Overhead per msg | Rationale |
|---|---|---|---|
| Telemetry | At-most-once | 120 + 116 = 236 B | Next reading in 5s replaces any loss |
| Quality alert | At-least-once | 350 + 116 = 466 B + ACK (40 B) | Must arrive; dedup via message_id |
| Batch event | Exactly-once | 800 + 116 = 916 B + TX overhead (~200 B) | Audit requires no duplicates |
Step 4: Aggregate bandwidth
- Telemetry: 100 msg/s x 236 B = 23,600 B/s = 189 Kbps
- Alerts: 1.39 msg/s x 506 B = 703 B/s = 5.6 Kbps
- Batch: 0.28 msg/s x 1,116 B = 312 B/s = 2.5 Kbps
- Total: ~197 Kbps (well within a 1 Mbps LAN connection)
AMQP protocol overhead per message consists of multiple frame components:
Message efficiency for different payload sizes:
| Payload | Total Size | Efficiency |
|---|---|---|
| 10 B | 126 B | 7.9% (poor) |
| 120 B | 236 B | 50.8% (moderate) |
| 800 B | 916 B | 87.3% (good) |
For telemetry with 120-byte payloads, nearly half the bandwidth is protocol overhead. Batching 10 readings into one 1,200-byte message improves efficiency from 51% to 91%, saving per 10 readings (44% reduction).
10.5.1 Interactive Calculator: AMQP Message Sizing
10.5.2 Interactive Calculator: AMQP Bandwidth Requirements
Step 5: Broker memory for persistent sessions
Quality alerts and batch events use persistent delivery (delivery_mode=2):
- Persistent messages per second: 1.39 + 0.28 = 1.67 msg/s
- Average persistent message size: ~810 bytes (weighted: (1.39 x 506 + 0.28 x 1116) / 1.67 ≈ 608 B on-wire; ~810 B with broker internal framing)
- If a consumer goes offline for 10 minutes: 1.67 x 600 = 1,002 messages ≈ ~810 kB queued
- With 5 consumers, worst case: ~4 MB broker memory for queued messages
Conclusion: The telemetry (98% of messages) uses fire-and-forget, keeping broker load minimal. Only the 2% of messages that matter (alerts + batch events) use persistent delivery, consuming modest broker resources. Using exactly-once for all messages would increase bandwidth to ~360 Kbps (vs. 197 Kbps) — roughly 1.8x — due to adding ~200 bytes of transaction overhead per telemetry message. That overhead is waste for data that is replaced every 5 seconds.
Checkpoint: Sizing the Reliability Chain
You now know:
- The factory example produces about 102 messages per second from 500 machines.
- Telemetry is 98% of traffic, so at-most-once keeps the total near 197 Kbps.
- Persistent alerts and batch events are only 2% of messages, but they drive offline broker memory.
10.6 Knowledge Check
Test your understanding of message structure and delivery guarantees.
10.7 Design Contract: Delivery Reliability Chain
Reliable AMQP delivery is a chain, not one setting. The deeper treatment now lives in AMQP Delivery Reliability Chain Contracts, covering publisher confirms, durable queues, persistent messages, manual acknowledgments, idempotent redelivery, and DLX retry/parking evidence paths.
10.8 AMQP Delivery Reliability Chain Contracts
Start with the story: Reliability in AMQP is not one magic checkbox. It is a chain of receipts: the producer needs a broker confirm, the broker needs durable storage, the message needs persistence, and the consumer must acknowledge only after the real work is done.
10.8.1 Learning Objectives
Prove Every Link in One Alarm Chain
Picture a site unit sending a valve alarm to an incident worker. The message reaches a queue, the worker writes the incident, and then the worker crashes before it says the job is done. The next worker may see the same alarm, so the team must prevent a second incident without losing the first.
Trace one alarm from local copy to accepted store, waiting queue, worker, final record, and done signal. Name what each signal proves and what it does not. Keep one alarm id through every link so a resend or redelivery can be joined to the same physical event.
Test a failed send, a store restart, a message held only in memory, a worker crash before and after the write, a rejected item, and a full retry path. Check both loss and duplicate effects. One durable setting cannot protect a different gap in the chain.
Keep any urgent safe valve action local when delivery is late. The chain can notify and retain evidence, but it must not be the sole route to stop immediate harm.
This opening does not promise exactly-once physical action. Practitioner maps each loss window and owner. Under the Hood examines confirms, durable definitions, stored bodies, worker replies, repeated delivery, dead-letter paths, and safe replay.
After this page, you should be able to:
- Explain why publisher confirms, queue durability, message persistence, and consumer acknowledgments protect different loss windows.
- Diagnose which reliability link is missing from an AMQP delivery path.
- Explain why at-least-once delivery requires idempotent consumers.
- Identify the exact broker triggers that dead-letter a message.
- Design a DLX retry and parking flow that preserves investigation evidence.
10.8.2 Why This Follows AMQP Message Delivery
AMQP Message Delivery teaches message structure, delivery modes, publisher confirms, consumer acknowledgments, prefetch, dead-letter queues, and delivery sizing. This page tightens the contract that connects those pieces: no single flag makes delivery reliable, and each acknowledgment only means something inside its scope.
Use it when a deployment needs a zero-loss audit, when a durable queue still loses messages after restart, when repeated deliveries must be made safe with idempotency, or when a DLQ runbook needs to distinguish retryable failures from messages that should move to parking.
10.8.3 Overview: “Reliable” Is a Chain, Not a Switch
A message survives from producer to processed only if every link in a chain holds. There is no single “reliable” flag. At-least-once delivery end to end needs four independent mechanisms lined up:
- Publisher confirms so the producer knows the broker actually took the message.
- A durable queue so the queue definition survives a broker restart.
- A persistent message (
delivery-mode = 2) so the message body is written to disk, not just held in RAM. - Consumer acknowledgements so the broker only removes a message after it has been processed, not merely delivered.
Break any one and you open a silent loss window: no confirm means the producer never learns a publish was dropped; a non-durable queue vanishes on restart; a transient message in a durable queue is still lost on restart; auto-ack deletes a message the instant it is sent, before the consumer has done anything with it.
Concrete chain example: a gateway sends a valve-state alarm and keeps its local copy until the broker sends a publisher confirm. If the broker crashes before the confirm, the gateway resends from local storage. If the broker confirms and then restarts, the durable queue and persistent message are what make the alarm still appear after recovery. If the consumer receives the alarm, writes it to an incident table, and crashes before basic.ack, the broker requeues the unacknowledged delivery on channel close. That is why the downstream incident write needs an alarm id: the next consumer may see the same delivery again, and the database should treat it as the same alarm, not a second incident.
The practical audit question is therefore always, “where can the message be acknowledged but not yet recoverable?” Publisher confirms answer the producer side, persistence answers the broker restart side, and manual acknowledgements answer the consumer side. Treating those as one chain keeps operators from over-trusting a single durable setting.
10.8.4 Practitioner: What Each Link Protects Against
| Mechanism | Protects against | If you omit it |
|---|---|---|
Publisher confirm (confirm.select) | Broker rejected, unroutable, or crashed mid-publish | Producer believes a lost message was delivered |
Durable queue (declare durable=true) | Broker restart wiping the queue definition | Queue and all its messages disappear on restart |
Persistent message (delivery-mode=2) | Broker restart wiping in-memory message bodies | Durable queue comes back empty |
Manual consumer ack (basic.ack) | Consumer crashing after delivery, before processing | Message removed on delivery; work is lost |
The two disk settings only work together: a persistent message in a transient queue is not saved, and a transient message in a durable queue is not saved either. You need both. Publisher confirms even close the fsync race — the broker only confirms a persistent message routed to a durable queue after it is on disk.
Worked example — a meter reading that must not be lost. The producer opens confirm mode, publishes with delivery-mode=2 to a durable queue, and waits for the broker’s confirm before deleting its local copy. The consumer reads, writes the reading to the database, and only then sends basic.ack. If the consumer dies between read and ack, the broker sees the channel drop, requeues the unacked message, and another consumer reprocesses it.
Now add sizing. If four consumers each use basic.qos(prefetch_count=20), as many as 80 messages can be delivered but unacknowledged at once. A worker crash with 17 unacked messages does not lose those messages; they return to the queue when the channel closes. A producer using confirms in batches of 50 should keep at least the current unconfirmed batch locally, because a connection drop after publishing message 1,237 but before its confirm leaves the producer unsure whether the broker stored it. Retrying from a message id makes the duplicate detectable.
That requeue is why at-least-once can duplicate. AMQP 0-9-1 does not give true exactly-once on its own; you reach “effectively once” by making the consumer idempotent — dedupe on the message-id or a natural key so a reprocessed message is harmless.
10.8.5 Under the Hood: When a Message Is Dead-Lettered
A queue configured with x-dead-letter-exchange re-publishes a message to that exchange on exactly three triggers — nothing else:
- the consumer rejects or nacks it with
requeue=false(a poison message it cannot process), - the message’s TTL expires (a per-message
expirationor the queue’sx-message-ttl), or - the queue overflows a length limit (
x-max-lengthorx-max-length-bytes), which by default drops the oldest message.
On dead-lettering, the broker routes the message to the dead-letter exchange using x-dead-letter-routing-key if set, otherwise the message’s original routing key, and prepends an x-death header recording the reason, the source queue, a count, and a timestamp. That count is what lets a retry loop give up after N attempts instead of cycling a poison message forever. A common design is: main queue -> DLX -> a “retry” queue with a short TTL that dead-letters back to the main exchange, plus a “parking” queue for messages whose x-death count has exceeded the limit.
For example, a decoder that cannot parse a payload should send basic.nack(requeue=false), not keep requeueing the same poison message at the head of the main queue. The DLX can route it to a retry queue with x-message-ttl=30000; after 30 seconds that retry queue dead-letters it back to the main exchange. When the message fails again, the next x-death entry or count tells the retry policy it has already been through the loop. After three failed attempts, the handler can route it to a parking queue with the original body, headers, failure reason, and source queue preserved for investigation. The DLQ is therefore an evidence path as much as a reliability path: it stops one bad message from blocking good traffic while keeping enough context to repair the producer or consumer.
Inspect Figure 10.1 to separate a bounded retry loop from the terminal evidence path for a poison message.
-
One message fails. Bex moves it off the main line.
-
The broker saves why it failed and adds one count.
-
A retry line waits, then sends the message back.
-
It fails again. The saved count reaches the limit.
-
Bex parks it with its evidence. Good work can pass.
Trace Figure 10.1 from the main queue through rejection, expiry, or overflow into the dead-letter exchange. Retryable work waits in the TTL queue and returns; excessive failures move to parking with context intact. This route turns x-death history into a retry decision instead of an infinite cycle.
10.9 Summary
This chapter covered AMQP message structure and delivery guarantees:
First: Message Format: Analyzed header (durable, priority, TTL), properties (content-type, correlation-ID, message-ID), and body sections
Next: Delivery Modes: Compared at-most-once (fast, may lose), at-least-once (guaranteed, may duplicate), and exactly-once (no loss, no duplicates)
Then: Publisher Confirms: Implemented broker acknowledgment to producers for reliable publishing
After that: Consumer Acknowledgments: Applied manual ACK/NACK with prefetch control for processing guarantees
Also inspect: Dead Letter Queues: Configured DLQ for capturing failed messages for investigation
Finally: Idempotency: Used message-ID for deduplication in at-least-once scenarios
10.10 What’s Next
| Chapter | Focus | Why Read It |
|---|---|---|
| AMQP Frames and Reliability | SASL authentication, TLS encryption, and AMQP 1.0 frame types | Secure and inspect the protocol layer that carries the messages you configured here |
| AMQP Core Architecture | Exchanges, queues, bindings, and the producer-broker-consumer model | Reinforce how message routing decisions upstream affect the delivery guarantees you set |
| AMQP Implementations and Labs | RabbitMQ broker setup, hands-on queue and DLQ configuration | Apply publisher confirms and DLQ patterns in a running broker environment |
| MQTT QoS Levels | MQTT at-most-once, at-least-once, and exactly-once equivalents | Compare AMQP delivery semantics with the constrained-device protocol used across most IoT edge devices |
| CoAP Fundamentals and Architecture | RESTful CoAP reliability via CON/NON message types | Contrast AMQP’s broker-mediated guarantees with CoAP’s direct endpoint reliability model |
| Protocol Integration Patterns | Bridging AMQP with MQTT, CoAP, and HTTP in IoT gateways | See how the delivery guarantees studied here propagate (or break) across protocol translation boundaries |
10.11 Key Takeaway
Reliable AMQP delivery depends on explicit acknowledgement and redelivery behavior. Choose prefetch, acknowledgement timing, and dead-letter handling together so slow consumers do not turn into hidden message loss or unbounded backlog.
10.12 Continue Your Route
This final part closes the route from Worked Example: Smart Factory Message Sizing through Key Takeaway. Return to AMQP Delivery: Guarantees and Acknowledgements or continue from the amqp module index.
