4.1.1. Azure Service Bus: Queues, Topics, and Dead-Lettering
💡 First Principle: Service Bus is a custody chain for work. A message is never casually handed over — it's locked, processed, and only released from the queue when the consumer formally completes it. Every feature (peek-lock, retries, dead-lettering, sessions) is a link in that chain, ensuring work can be slow, can fail, can retry — but cannot silently vanish.
The two entity types split by fan-out, as the parent's decision tree showed. This table's distinctions are tested verbatim:
| Queue | Topic + Subscriptions | |
|---|---|---|
| Delivery | Each message to exactly one of the competing consumers | Each subscription gets its own copy |
| Pattern | Work distribution (embedding jobs) | Pub/sub (order placed → billing, shipping, analytics) |
| Consumer scaling | Add consumers to drain faster | Add subscriptions to add audiences |
Custody mechanics: consumers receive in peek-lock mode (default) — the message becomes invisible to others while locked, and the consumer must complete (delete), abandon (unlock for retry), or dead-letter it. If the consumer crashes, the lock expires and the message reappears — at-least-once delivery, the same idempotency obligation as the change feed in 2.1.4. The alternative, receive-and-delete, trades safety for speed: a crash mid-processing loses the message. In Python (azure-servicebus):
from azure.servicebus import ServiceBusClient
with ServiceBusClient(ns, credential).get_queue_receiver("embed-jobs") as receiver:
for msg in receiver:
try:
process(msg) # generate embedding, write to store
receiver.complete_message(msg)
except TransientError:
receiver.abandon_message(msg) # unlock; retry increments count
The dead-letter queue (DLQ) is the chain's final link: every queue and subscription has one built in, and messages land there when max delivery count is exceeded, TTL expires, or the consumer explicitly dead-letters an unprocessable ("poison") message:
The DLQ is addressed as a sub-queue (queue/$deadletterqueue) and read with an ordinary receiver — nothing automatic touches it. A monitored DLQ is your poison-message hospital; an unmonitored one is a data black hole with a storage bill. Two more exam-favorite features: sessions guarantee ordered, single-consumer processing for messages sharing a session ID (all turns of one conversation, in order), and duplicate detection drops repeated message IDs within a time window.
⚠️ Exam Trap: Dead-lettered messages are not retried or discarded automatically — they sit until an application explicitly receives them from the DLQ. "Configure the DLQ to auto-replay" describes a feature that doesn't exist; the correct pattern is a separate process (often a Function) that inspects, repairs, and resubmits.
Numbers and knobs the exam expects: MaxDeliveryCount defaults to 10, and the peek lock lasts about a minute by default — long-running handlers renew the lock rather than letting it lapse into an accidental redelivery. Prefetch pulls batches of messages into the client ahead of processing to cut round trips (at the cost of holding more locks). Scheduled messages enqueue now but become visible at a future time — the mechanism behind "retry this in an hour" patterns. And defer is the fourth settlement verb: it parks a message retrievable only by sequence number, for workflows that must wait on something else before processing.
Reflection Question: A malformed document crashes every worker that receives it, and the queue has stopped draining. Which two Service Bus mechanisms interact to quarantine it, and what would happen without them?