Queue and process back-end operations with Azure Service Bus
Queues, topics, and subscriptions
A Python service that has to charge a card, re-index a document, or call a partner API that takes eight seconds should not do any of that inside the web request. It should hand the work to a broker and answer immediately. Azure Service Bus is the broker Azure provides for that hand-off, and the very first thing it makes you decide is the shape of the delivery: does exactly one worker act on this, or does every interested consumer get its own copy?
This page owns Service Bus itself: the entity types, the settlement and expiry rules that decide whether a message survives a crashed worker, and the Python client you drive them from, so that by the end you can pick an entity shape, write a handler that cannot silently lose work, and diagnose a message that never arrived. Its siblings own the neighbours you will reach for next: Azure Event Grid owns reactive event routing with its own event subscriptions, schemas, and retry story, and Azure Functions owns triggers and bindings, including the Service Bus trigger that can drive a receive loop on your behalf. What follows is the service-side behaviour both of those pages assume you already have.
Message or event: the word that separates the two products
Microsoft draws the line explicitly, and it is worth pinning before the vocabulary of two services starts overlapping. An event is "a lightweight notification of a condition or state change"[1] where "the publisher has no expectation about how the event is handled". A message is "raw data produced by a service to be consumed or stored elsewhere", and for it "a contract exists between publisher and consumer". Service Bus carries messages. The sender expects a particular consumer to do a particular job, and in the reliable receive mode it expects to learn that the job was done. That expectation is why almost everything else on this page exists.
Queues: competing consumers
A queue is point-to-point. Microsoft's wording is that queues "offer First In, First Out (FIFO) message delivery to one or more competing consumers"[2], and that "only one message consumer receives and processes each message". Ten worker processes reading one queue therefore give you ten times the throughput and not ten times the work: this is the competing consumers shape, and it is the reason a queue is the default answer for a back-end operation that must happen once.
The same page names the other things you get for free from that arrangement. Temporal decoupling, because "producers (senders) and consumers (receivers) don't have to send and receive messages at the same time". Load leveling, because "the consuming application only needs to handle average load instead of peak load". Loose coupling, because a consumer can be upgraded without touching the producer.
Topics and subscriptions: fan-out
A topic is the publish side and nothing else. Consumers "don't receive messages directly from the topic. Instead, consumers receive messages from subscriptions of the topic", and "a topic subscription resembles a virtual queue that receives copies of the messages that are sent to the topic". That is the fan-out shape: one send, one copy per subscription, each copy independently locked, retried, and dead-lettered by whoever reads that subscription. Because a subscription behaves like a queue, it also supports competing consumers inside itself, so the two shapes compose rather than compete. The figure below draws both, with the delivery count that matters marked on each.
The word "subscription" is overloaded across Azure, and on this page it always means the Service Bus sense: a named child entity of a topic that holds its own copy of the messages it matched. It is not your Azure subscription, the billing and resource container the namespace lives in. It is not an Event Grid event subscription, which is a routing rule binding a source to a handler endpoint and holds nothing. Readers who know one of the other two will silently map this term onto it, so where this page needs those senses it spells them out in full.
Subscription rules: choosing which copies arrive
A subscription starts out taking everything. "Each newly created topic subscription has an initial default subscription rule", and without an explicit filter condition "the applied filter is the true filter that enables all messages to be selected into the subscription". You narrow it by adding named rules, where each rule is "a filter condition that selects particular messages, and optionally contains an action that annotates the selected message"[3].
Service Bus supports exactly three filter types, and that list is complete:
| Filter type | What it matches | When to reach for it |
|---|---|---|
| Correlation filter | "a set of conditions that it matches against one or more of an arriving message's user and system properties"; multiple properties combine as a logical AND, and string comparison is case-sensitive | Routing on exact values such as CorrelationId, Subject, or your own application properties. This is the default choice. |
| SQL filter | "a SQL-like conditional expression that the broker evaluates against the arriving messages' user-defined properties and system properties", supporting EXISTS, IS NULL, NOT/AND/OR, relational operators, arithmetic, and LIKE | Ranges, negation, pattern matching: anything an equality test cannot express. |
| Boolean filter | TrueFilter selects every arriving message, FalseFilter selects none; both "derive from the SQL filter" | Making the default behaviour explicit, or parking a subscription without deleting it. |
Two constraints on that table decide real designs. First, Microsoft's own guidance is that "applications should choose correlation filters over SQL-like filters because they're much more efficient in processing and have less impact on throughput", and that SQL filter rules "cause lower overall message throughput at the subscription, topic, and namespace level". Second, and more often fatal: "All filters evaluate message properties. Filters can't evaluate the message body." If your routing key lives inside the JSON payload, no filter can see it. Promote it to an application property at send time or route in code after receiving.
Rule counting surprises people, so it is worth stating exactly. "All rules without actions are combined using an OR condition and result in a single message on the subscription even if you have multiple matching rules." But "each rule with an action produces a copy of the message." Microsoft's worked case: a subscription with five rules, two carrying actions and three not, receiving one message that matches all five, yields three messages on the subscription, one for the three action-free rules collectively and one each for the two rules with actions.
So the shape you choose is a statement about consumers, not about volume. One consumer that must act once means a queue. Several consumers that must each act on the same fact means a topic with one subscription apiece, narrowed by rules so each consumer only wakes for what concerns it.
How a receive ends: receive modes and settlement
Receiving a message from Service Bus is not one operation, it is a negotiation with an outcome. Microsoft calls that outcome a settlement: when the broker hands a message over, "the broker and client want to establish an understanding of whether the message is successfully processed and can be removed, or whether the message delivery or processing failed, and thus the message might have to be delivered again", and that acknowledgment "settles the understanding of both the client and broker"[4]. Two receive modes exist, and they differ entirely in when that settlement happens.
The two modes, before any detail
ReceiveAndDelete settles at delivery. It "instructs the broker to consider all messages it sends to the receiving client as settled when sent", so "the message is consumed as soon as the broker puts it onto the wire. If the message transfer fails, the message is lost." The queues article[2] names this at-most once processing. It is fast, it costs no round trip, and it is only defensible when the data "has low value or is only meaningful for a very short time".
PeekLock settles after your work. It "tells the broker that the receiving client wants to settle received messages explicitly. The message is available for the receiver to process, while the service holds an exclusive lock so that other competing receivers can't see it." A crash after processing but before settlement means "Service Bus redelivers the message when the application restarts", which the same article names at-least once processing. In the Python client, PeekLock is the default[5]: receive_mode defaults to ServiceBusReceiveMode.PEEK_LOCK, and you have to opt into RECEIVE_AND_DELETE deliberately.
At-least once is the ceiling PeekLock itself gives you, and nothing on the receive side configures it away. Because a redelivery is always possible, Microsoft's advice is blunt: "Designing for idempotent message handling becomes critical." Make your handler safe to run twice on the same message_id and the whole model becomes comfortable. Microsoft's pages do use the phrase exactly once about Service Bus, but they attach it to different features; the last section of this page sets out that disagreement rather than resolving it.
The word "peek" in PeekLock is not the peek operation
This catches people, so it is worth killing on first contact. PeekLock is a receive mode that takes a lock. peek_messages()[6] is a different, browsing call: "Peeked messages are not removed from queue, nor are they locked. They cannot be completed, deferred or dead-lettered." Peeking is for looking at a backlog in a support tool. It never delivers work.
The four settlement outcomes
Once a message is locked, exactly four explicit actions can end that delivery, and each is a real API call on the receiver. The figure below traces the two-stage receive through all four, plus the fifth path where you settle nothing at all.
complete_message(message)indicates "to the broker that the message is successfully processed and the message is removed from the queue or subscription". This is the only outcome that consumes the message.abandon_message(message)asks "for the message to be released and unlocked instantly". Microsoft's own summary of the effect: "This message will be returned to the queue and made available to be received again." Use it for a failure you expect to succeed on retry, such as a downstream timeout.dead_letter_message(message, reason=None, error_description=None)is for the case where "redelivering the message and retrying the operation won't help". It moves the message to the entity's dead-letter queue (DLQ), a holding area for messages that cannot be delivered or processed, which The dead-letter queue section below covers in full. The two optional arguments are recorded on the message and are what an operator reads later.defer_message(message)sets the message aside without consuming it. The Python reference is precise about the cost: "This message will remain in the queue but must be requested specifically by its sequence number in order to be received." A sequence number is the gap-free identifier the broker stamps on every message as it arrives; the Sessions and ordered processing section below explains what it does and does not guarantee. Deferral is the right answer when a message arrived out of order relative to some state you do not have yet, but you must persist that sequence number somewhere durable or the message is effectively stranded.
The fifth path is the one you get by accident. "When the lock on a message is explicitly released or when the lock expires, the message goes back to the front of the retrieval order for redelivery." Doing nothing is therefore not neutral. It is a slow abandon.
Settle before you close anything
One concrete failure mode is worth memorising because its symptom points at the wrong cause. "In peek-lock mode, the lock on a received message is tied to the receiver and its connection. If you close the receiver or its connection before you settle a message (complete, abandon, defer, or dead-letter), the settlement doesn't reach the service, so the message stays locked until the lock expires." The message is then redelivered, its delivery count climbs (that is the broker's per-message counter of delivery attempts, which the next section covers in full), and it eventually lands in the DLQ[7] with the reason MaxDeliveryCountExceeded, which reads like a poison-message problem when it is really a lifetime bug. Microsoft's rules are to settle each message before you close the receiver or the ServiceBusClient, never to hold a received message and settle it later, and, if settlement fails because the lock was lost, to "receive the message again and process it, rather than retrying the settlement on the original message". Note also that "the service closes an idle connection after 10 minutes, which also releases the lock".
The takeaway for a handler you are about to write: choose PeekLock, make the work idempotent, and make sure every branch, including the exception branch, reaches exactly one of the four settlement calls while the receiver is still open.
The message lock and the delivery count
A lock is a deadline, and the delivery count is what happens when you keep missing it. These two together are the entire retry mechanism, and tuning them badly is the most common reason a healthy queue fills its dead-letter queue.
Lock duration is a property of the entity, not of the receive call
"The queue or subscription initially defines the duration of the lock." Its default value is one minute, and the maximum is five minutes[4]; you set it on the queue or on the subscription, not per receive. Microsoft's sizing rule is to "set the lock duration to a value that's higher than your normal processing time, so you don't have to renew the lock", with an explicit cost on the other side: "when your client stops working, the message becomes available again only after the lock duration passes". A five-minute lock therefore buys you headroom and costs you five minutes of stalled work every time a worker dies.
For work that genuinely runs longer than the maximum, you renew instead. The client owning the lock can call renew_message_lock(message)[6], which returns the new expiry instant, but note two constraints from the same reference: "an expired lock cannot be renewed", and "messages received via RECEIVE_AND_DELETE mode are not locked, and therefore cannot be renewed". The hands-off alternative is the automatic renewal feature, which in Python is an AutoLockRenewer passed as the auto_lock_renewer keyword[5] when you create the receiver, so "messages are automatically registered on receipt".
The delivery count counts attempts, and only some of them
"Whenever a message is delivered under a peek-lock, but is either explicitly abandoned or the lock has expired, the delivery count on the message is incremented. When the delivery count exceeds the limit, the message is moved to the DLQ" with the reason MaxDeliveryCountExceeded. The default limit is 10[7], and the escape hatch is narrow: "This behavior can't be disabled, but you can set the max delivery count to a large number." There is no way to run a queue with unlimited retries; you can only push the ceiling up.
One carve-out looks like a contradiction of that rule, so read the two together. A PeekLock or session lock "is volatile and can be lost" during a service update, an OS update, a change to the entity's properties while you hold the lock, a dropped connection, or a session whose SessionIdleTimeout is shorter than the message lock duration. In those cases the client sees a MessageLockLostException or SessionLockLostException, and, crucially, "the delivery count of the message isn't incremented". So the rule is not "any lost lock counts against you". It is that an abandon or an ordinary lock expiry counts, while a broker-side lock loss that raises one of those exceptions does not. Your retry budget is spent by your handler's failures, not by Azure's maintenance.
What the numbers should be
Pick the lock duration from your processing time and the max delivery count from how many attempts a genuinely transient failure deserves. A handler that normally finishes in two seconds and occasionally waits on a flaky dependency wants a short lock so a dead worker's message comes back fast, and the default 10 attempts is generous. A handler that does a two-minute model inference wants a long lock or an AutoLockRenewer, because with a one-minute lock the message is redelivered while the first attempt is still running, which produces duplicate work and burns the delivery budget until the message dead-letters. That combination, slow handler plus default lock, is the single most common way a working queue starts dead-lettering everything.
Message expiry and time to live
Time to live is the other clock, and it is unrelated to the one in the previous section. The message lock bounds a single attempt; time to live (TTL) bounds the message's whole existence, however many attempts it has had.
You set TTL as a relative duration on the message, and it becomes absolute at enqueue[8]: "the expires-at-utc property takes on the value enqueued-time-utc + time-to-live". Past that instant "messages become ineligible for retrieval".
The entity's default is a ceiling, and it is enforced silently
Every queue and topic carries a default expiration that "applies to all messages sent to the entity where time-to-live isn't explicitly set". The part worth memorising is the second job that setting does: "The default expiration also functions as a ceiling for the time-to-live value. If a message has a longer time-to-live expiration than the default value, the system silently adjusts it to the default message time-to-live value before enqueuing the message." No error, no warning. A sender asking for a seven-day TTL on an entity whose default is one hour gets one hour and is never told.
The defaults themselves rarely bite, but the basic tier does: the default TTL for a brokered message and for standard and premium entities is "the largest possible value for a signed 64-bit integer", whereas "for the basic tier, the default (also maximum) expiration time is 14 days". And when a topic and a subscription disagree, "if the topic specifies a smaller TTL than the subscription, the topic TTL is applied".
Expiring is not the same as disappearing
Four behaviours around expiry are all documented and all catch people:
- Nothing expires if nobody is listening. "The time-to-live (TTL) setting on a brokered message isn't enforced when no clients are actively listening." A quiet queue does not self-clean.
- Removal is lazy. "The broker might choose to lazily expire these messages", so "you might observe an incorrect message count when using message expiration, and you might even see these messages during a peek operation. However, when receiving messages, the expired message isn't included." A message count that disagrees with what you can receive is expected, not a bug.
- A locked message is protected. "The expiration doesn't affect messages that are currently locked for delivery." If the lock expires or the message is abandoned, the expiration takes immediate effect, and if the message is successfully settled the system does not move it, because "settlement assumes that the application successfully handled the message, in spite of the nominal expiration".
- Expiry destroys by default. "You can optionally move expired messages to a dead-letter queue... If you leave the option disabled, expired messages are dropped." This is a per-entity setting and it is the difference between a diagnosable outage and silent data loss.
Two interactions round it out. For a scheduled message the clock starts at the scheduled instant, not at send: Microsoft's example is a ScheduledEnqueueTimeUtc five minutes out with a TimeToLive of 10 minutes, giving an expiry "after 5 + 10 = 15 minutes from now". And on a session-enabled entity[9], meaning one where related messages are grouped under a shared session id and locked to a single receiver (the next section but one), expiry is collective: "if there's a single message in the session that passes the TTL, all the messages in the session expire".
The practical reading is that TTL is a business deadline, not a cleanup mechanism. Set it to the point past which processing the message would be wrong rather than merely late, and turn on dead-lettering on expiration so you find out that it happened.
The dead-letter queue
When a message cannot be delivered or cannot be processed, Service Bus does not discard it. It moves it to a dead-letter queue (DLQ), which is a secondary sub-queue belonging to the queue or subscription itself rather than a separate entity you provision. From an API perspective the DLQ "is mostly similar to any other queue"[7], with three differences that matter: "messages can only be submitted via the dead-letter operation of the parent entity", "time-to-live isn't observed", and "you can't dead-letter a message from a DLQ". It also "can't be deleted or managed independently of the main entity", and there is "no automatic cleanup of the DLQ": messages sit there until you retrieve and complete them.
Two Microsoft pages describe the sub-queue's existence differently, and neither supersedes the other. The dead-letter-queues article says the DLQ "doesn't need to be explicitly created", while the message transfers, locks, and settlement article[4] states that "a dead-letter subqueue exists for a queue or a topic subscription only when you enable the dead-letter feature for the queue or subscription". Both were revised within days of each other, so there is no newer-wins tiebreak, and nothing on this page depends on which reading is right. What you actually configure and what you can actually be asked about are the settings, which are undisputed: the max delivery count, dead-lettering on message expiration, and dead-lettering on filter-evaluation exceptions. Treat the sub-queue as something you turn on and read, never as something you create.
The five system reasons, and yours
When the broker dead-letters a message it records two properties, and "applications can define their own codes for the dead-letter reason property, but the system sets the following values". This is the complete published set:
| Dead-letter reason | Dead-letter error description |
|---|---|
HeaderSizeExceeded | The size quota for this stream exceeded the limit. |
TTLExpiredException | The message expired and was dead-lettered. |
Session ID is null | Session enabled entity doesn't allow a message whose session identifier is null. |
MaxTransferHopCountExceeded | The maximum number of allowed hops when forwarding between queues exceeded the limit. This value is set to 4. |
MaxDeliveryCountExceeded | Message couldn't be consumed after maximum delivery attempts. |
On top of those, your own code can reject a message explicitly, which Microsoft calls application-level dead-lettering and recommends for "messages that hold malformed payloads" or that "fail authentication when some message-level security scheme is used". A sixth trigger sits behind a setting rather than a message property: "if you enable dead-lettering on filter evaluation exceptions, any errors that occur while a subscription's SQL filter rule executes are captured in the DLQ along with the offending message". The figure below collects all six paths in and shows how you read the result.
When you dead-letter deliberately, put diagnosis in the payload. Microsoft's guidance is to "include the type of the exception in the DeadLetterReason and the stack trace of the exception in the DeadLetterDescription", with the caveat that doing so "might result in some messages exceeding the 256 KB quota limit for the Standard tier".
Reading the dead-letter queue
The DLQ is addressable by path, which is what the portal, CLI, and tools such as Service Bus Explorer use:
<queue path>/$deadletterqueue
<topic path>/Subscriptions/<subscription path>/$deadletterqueue
From the SDK you do not build that string yourself. In Python you pass the sub_queue keyword when you create the receiver, using the ServiceBusSubQueue[10] enum, whose two members are DEAD_LETTER and TRANSFER_DEAD_LETTER. The same keyword is documented on get_queue_receiver and get_subscription_receiver and accepts "equivalent string values "deadletter" and "transferdeadletter"".
Reading a dead-letter queue from Python
from azure.identity import DefaultAzureCredential
from azure.servicebus import ServiceBusClient, ServiceBusSubQueue
client = ServiceBusClient(
fully_qualified_namespace="<namespace>.servicebus.windows.net",
credential=DefaultAzureCredential(),
)
with client:
# sub_queue is what targets the DLQ; without it you would read the main entity.
with client.get_queue_receiver(
queue_name="orders", sub_queue=ServiceBusSubQueue.DEAD_LETTER
) as dlq:
for message in dlq.receive_messages(max_wait_time=5):
# These two properties are why the message is here.
print(message.dead_letter_reason, message.dead_letter_error_description)
# ...triage, then settle so it leaves the DLQ.
dlq.complete_message(message)
The sub_queue keyword in that listing is the whole trick, and dead_letter_reason maps to the DeadLetterReason column in the table above. Completing the message is what removes it, since the DLQ never cleans itself.
The transfer dead-letter queue is somewhere else
One distinction is easy to get backwards. When auto-forwarding or a send-via transaction cannot deliver to its destination, "the message is placed in the transfer dead-letter queue (TDLQ) of the source entity that did the forwarding, not on the destination entity". If you are hunting for messages that vanished on their way into a queue, look at the entity they came from. Its path is <queue path>/$Transfer/$DeadLetterQueue, and in Python it is ServiceBusSubQueue.TRANSFER_DEAD_LETTER.
Finally, do not go looking for a topic-level DLQ count. "Obtaining the count of messages in the dead-letter queue at the topic level isn't applicable because messages don't sit at the topic level", so the counts you want live on each subscription.
Treat the DLQ as instrumentation rather than as a failure. A queue whose dead-letter queue is empty is not necessarily healthy, because dead-lettering on expiration and on filter-evaluation exceptions are both settings you have to turn on, and with them off those messages are simply dropped. Enable them, alert on the dead-letter count, and read DeadLetterReason before you change any code: it distinguishes a handler that keeps failing, a message that ran out of time, a sender that forgot a session id, and an oversized payload, and those four have nothing in common except where they landed.
Sessions and ordered processing
Ordering is where Service Bus surprises careful engineers, because the service really does hand messages out in order and that still is not enough.
Every entity stamps each message with a sequence number: "the broker stamps messages with a gap-free, increasing sequence number relative to the queue or topic", and it "represents the true order of arrival"[11]. But the same article draws the line immediately: "Sequence number on its own guarantees the queuing order and the extractor order of messages, but not the processing order, which requires sessions." Microsoft's own walkthrough is the clearest statement of the problem. Two consumers, three messages: consumer 1 takes message 1, consumer 2 takes message 2, finishes, takes message 3, finishes, and only then does consumer 1 finish message 1. Processing order: 2, 3, 1. Nothing malfunctioned. "If messages just need to be retrieved in order, you don't need to use sessions. If messages need to be processed in order, use sessions."
Sessions: a lock over a group instead of over a message
A sender opens a session "by setting the session ID property to unique identifier defined by the application"; at the wire level that maps to the group-id property of AMQP 1.0[9], the Advanced Message Queuing Protocol that every Service Bus client speaks. A receiver then takes the whole group: "When the client accepts and holds a session, it holds an exclusive lock on all messages with that session's session ID in the queue or subscription. It holds exclusive locks on all messages with the session ID that arrive later."
That last sentence is the mechanism. The session lock is not a bigger message lock, it is a lock over a stream that has not fully arrived yet, which is why later messages in the group cannot overtake earlier ones. Microsoft frames the relationship exactly: "The session lock held by the session receiver is an umbrella for the message locks used by the peek-lock settlement mode. Only one receiver can have a lock on a session." Different sessions are still fully parallel, so throughput comes from having many session ids rather than many receivers per session, and "an interleaved message stream in one queue or subscription is cleanly demultiplexed to different receivers". The figure below shows that demultiplexing, including a session that nobody currently holds and from which "no messages are delivered".
Sessions are a creation-time decision, and they change the contract
"You can't enable or disable message sessions after the queue or subscription is created. You can only do so at the time of creating the queue or subscription", which the enable-message-sessions article[12] states as an explicit Important block. The property is requiresSession in an ARM or Bicep template and --enable-session true on az servicebus queue create or az servicebus topic subscription create. Sessions are also unavailable on the basic tier; standard and premium support them.
Turning them on is not additive. "When you enable sessions on a queue or a subscription, client applications can no longer send or receive regular messages. Clients must send messages as part of a session by setting the session ID and receive messages by accepting the session." A sender that forgets gets its message dead-lettered with the reason Session ID is null. The one thing that still works normally is peeking, since "clients can still peek a queue or subscription that has sessions enabled".
Taking a session from Python
You do not call a separate accept method in Python; the session is chosen through the session_id keyword on the ordinary receiver factory. Pass a specific id to take that session, or pass the NEXT_AVAILABLE_SESSION sentinel to take whichever session is free: the reference[5] says "a specific session from which to receive. This must be specified for a sessionful queue, otherwise it must be None. In order to receive messages from the next available session, set this to ~azure.servicebus.NEXT_AVAILABLE_SESSION."
Receiving from the next free session
from azure.servicebus import ServiceBusClient, NEXT_AVAILABLE_SESSION
# ...client constructed as in the previous section...
with client.get_queue_receiver(
queue_name="order-steps", session_id=NEXT_AVAILABLE_SESSION, max_wait_time=30
) as receiver:
# receiver.session is None on a non-sessionful receiver.
checkpoint = receiver.session.get_state()
for message in receiver:
# ...do the work...
receiver.complete_message(message)
receiver.session.set_state(b"last-step-done")
Two details in that listing are load-bearing. max_wait_time on the constructor is the timeout for connecting to a session, which is different from the per-receive timeout: setting it on receive_messages "will not impact the timeout for connecting to a session". And receiver.session is the session object; it "is only available to session-enabled entities, it would return None if called on a non-sessionful receiver".
The get_state and set_state calls are the session state facility, "an application-defined annotation of a message session inside the broker, so that the recorded processing state relative to that session becomes instantly available when the session is acquired by a new processor". It is an opaque binary object the size of one message, 256 KB on standard and 100 MB on premium, it survives after every message in the session is consumed, and it counts toward the entity's storage quota, so clear it when the workflow ends.
One Python-specific trap follows from the umbrella model: renew_message_lock raises TypeError "if the message is sessionful" and "is only available for non-sessionful messages". On a session you renew the session lock, not the individual message lock.
Delivery count and dead-lettering behave slightly differently in a session
The rules shift enough to be worth stating. Accepting a session and then letting the session lock expire on timeout does increment the delivery count of its messages; accepting a session, leaving its messages uncompleted, and closing the session does not. Abandoned session messages still count against MaxDeliveryCount, still default to 10, and still dead-letter once exceeded, after which "the receiver continues receiving subsequent messages from the session". And recovery has a sting: "if a dead-lettered message is later moved back to the original queue for reprocessing, the original ordering relative to other session messages is lost because the resubmitted message receives a new enqueue time and sequence number".
Ordering, partition keys, and a documented disagreement
One more claim circulates and you should know its status. The partitioning article[13] explains that a partition key pins related messages to one broker, and, describing what you lose without one, says you "don't achieve the guaranteed ordering that a partition key provides". The sequencing and sessions articles say plainly that processing order "requires sessions". Microsoft has not reconciled these two statements, and this guide does not pick a winner. They were revised in the same period, so neither is the later word.
What is not in dispute is enough to design with. A partition key controls placement: "Service Bus assigns all messages that use the same partition key to the same partition", and where a session id is set, "Service Bus uses it as the partition key", so that "the same message broker handles all messages that belong to the same session". If you set both, "both properties must be identical" or the send fails with an invalid-operation exception. And the mechanism Microsoft's FIFO article prescribes for ordered processing, the one that holds a single receiver over a group of related messages, is sessions. Design for sessions when processing order matters, and treat a partition key as an affinity and availability decision rather than a substitute.
The Python client model
The Python surface is small and consistent, which makes it easy to reason about once you know which object owns what.
ServiceBusClient is the connection factory and nothing else. You build it from a fully qualified namespace and a credential[5], ServiceBusClient(fully_qualified_namespace, credential), where the namespace takes the form <yournamespace>.servicebus.windows.net and the credential is anything from azure-identity, typically DefaultAzureCredential. The connection-string route is the class method from_connection_string(conn_str). The client is genuinely shared machinery rather than a handle you make per operation: closing it "shuts down" all "spawned senders, receivers and underlying connection".
From that client you get exactly four things, and the naming tells you the entity type:
| Call | Returns | Reads or writes |
|---|---|---|
get_queue_sender(queue_name) | ServiceBusSender | Sends to a queue |
get_topic_sender(topic_name) | ServiceBusSender | Sends to a topic |
get_queue_receiver(queue_name, ...) | ServiceBusReceiver | Receives from a queue |
get_subscription_receiver(topic_name, subscription_name, ...) | ServiceBusReceiver | Receives from a subscription |
There is no topic receiver, which follows directly from the entity model in the first section: nothing reads a topic. The figure below lays the whole object graph out, from the one client down to the message types each branch deals in. What you send is a ServiceBusMessage, and the sender's send_messages[14] takes one message, a list, or a ServiceBusMessageBatch built with create_message_batch(), "throwing a ValueError" if a list "cannot fit in a single batch".
Two ways to receive, and one knob that can lose messages
The receiver reference names both channels: "receive() to make a single request for messages, and for message in receiver: to continuously receive incoming messages in an ongoing fashion"[6]. The batch call is receive_messages(max_message_count=1, max_wait_time=None), and it "will prioritize returning quickly over meeting a specified batch size", so asking for 50 and getting 3 is normal behaviour and not a bug.
prefetch_count is the performance knob and it carries a documented hazard. It defaults to 0, meaning messages are "received from the service and processed one at a time". Raise it and the client caches messages locally, which improves throughput but "increase[s] the chance that messages will expire while they are cached". The warning that matters: "If prefetch_count > 0 and RECEIVE_AND_DELETE mode is used, all prefetched messages will stay in the in-memory prefetch buffer until they're received into the application. If the application ends before the messages are received into the application, those messages will be lost." Prefetch plus ReceiveAndDelete is the one combination that loses messages without any failure occurring, which is why the reference recommends "that PEEK_LOCK mode be used with prefetch".
The processor model, and why Python does not have one
Other Azure SDKs offer an event-driven pump that owns the receive loop for you. In .NET this is ServiceBusProcessor[15], which "provides an abstraction around a set of ServiceBusReceiver that allows using an event based model": you attach a message handler to ProcessMessageAsync and an error handler to ProcessErrorAsync, both of which the reference marks "Implementation is mandatory", then call StartProcessingAsync. Its AutoCompleteMessages property decides settlement for you, and it is carefully scoped: it makes the processor "automatically complete messages after the message handler has completed processing", but "if the message handler triggers an exception, the message will not be automatically completed". A .NET processor also defaults to PeekLock and exposes MaxConcurrentCalls and MaxAutoLockRenewalDuration.
Python has no equivalent class. You own the loop, which means you also own every settlement: complete_message is what "removes the message from the queue", and there is no auto-complete switch to fall back on. Practically, for message in receiver: is the Python spelling of the continuous pump, and your try/except around the body is the error handler. The mental model to carry into the exam is that auto-completion is a processor feature and not a Service Bus feature, so the broker's rules from the settlement section apply unchanged either way.
If you would rather not own the loop at all, the Azure Functions Service Bus trigger wraps this machinery and is covered on that sibling page. The service-side behaviour it wraps, locks, delivery counts, dead-lettering, is exactly what this page describes.
What the sender controls before a message enters the queue
Most of this page has been about the receive side. Two send-side features change what a message is before any receiver ever sees it, and both come up constantly because they solve problems that look like application bugs. They are not an exhaustive list of sender options, but they are the two that alter enqueue behaviour itself.
Duplicate detection: making a retried send safe
The problem is stated precisely in the docs: an application can crash "immediately after sending a message", restart believing the send did not happen, and send again; or the acknowledgment can be lost in transit, "leaving the client in doubt about the outcome of the send operation". Duplicate detection[16] resolves both "by enabling the sender to resend the same message, and the queue or topic discards any duplicate copies".
The mechanism is a window over one field. Enabling it "helps keep track of the application-controlled MessageId of all messages sent into a queue or topic during a specified time window. If any new message is sent with MessageId that was logged during the time window, Service Bus reports the message as accepted (the send operation succeeds), but the newly sent message is instantly ignored and dropped." Two consequences follow, and both are exam-shaped. First, a suppressed duplicate looks like a success to the sender, so you cannot detect suppression from the send result. Second, "no other parts of the message other than the MessageId are considered", so two messages with different bodies and the same id collapse to one.
Is that "exactly once"? Microsoft's own pages give three different answers, and this guide keys none of them. The queues, topics, and subscriptions article[2] points the reader at duplicate detection and calls it "exactly once processing". The service comparison article[1] instead lists the Service Bus delivery guarantee as "At least once (optional ordered, exactly once with sessions)", attaching the phrase to sessions. The duplicate-detection article and the sessions article never use the term at all. What all four pages do agree on is the mechanism described just above: a bounded window, keyed on MessageId, that suppresses a repeat send. It says nothing about how many times a surviving message is delivered, which stays governed by the lock and delivery-count rules earlier on this page. Design against the mechanism, not the label.
That makes the MessageId your responsibility. "Application control of the identifier is essential because it allows the application to tie the MessageId to a business process context from which it can be predictably reconstructed when a failure occurs." Microsoft's example of a good id is a composite of a business key and the message subject, such as 12345.2017/payment. A random GUID generated fresh on each attempt defeats the feature entirely, and this is easy to do by accident: the partitioning article[13] notes that "the Microsoft client libraries automatically assign a message ID if the sending application doesn't", so leaving the field alone gives you a different id on every retry.
The numbers are small enough to remember. The detection history window "defaults to 10 minutes for queues and topics, with a minimum value of 20 seconds and a maximum value of 7 days", and the window is a throughput cost, because "all recorded message IDs must be matched against the newly submitted message identifier". Keep it just longer than your longest retry chain. The basic tier does not support duplicate detection; standard and premium do. And on a partitioned entity the uniqueness key widens: "when partitioning is enabled, MessageId+PartitionKey is used to determine uniqueness", while with partitioning disabled, which is the default, "only MessageId is used".
Scheduled messages: choosing when the message appears
A scheduled message is submitted now and enqueued later. "Scheduled messages don't materialize in the queue until the defined enqueue time. Before that time, scheduled messages can be canceled. Cancellation deletes the message." There are two documented ways to do it[11], and they differ in what you get back:
- Set the
ScheduledEnqueueTimeUtcproperty on the message and send it normally. Simple, and the only option for large messages, since "messages that are larger than 1 MB can only be scheduled using the regular API". - Call the schedule API, passing the message and the time. "The API returns the scheduled message's
SequenceNumber, which you can later use to cancel the scheduled message if needed." In Python this isschedule_messages(messages, schedule_time_utc)[14], which "returns a list of the sequence numbers of the enqueued messages", paired withcancel_scheduled_messages(sequence_numbers).
Three limits keep this from being a general scheduler. The sequence number you were handed is temporary: it "is only valid while the message is in the scheduled state", because on activation "the message is appended to the queue as if it had been enqueued at the current instant, which includes assigning a new SequenceNumber". There are no repeats, since "Service Bus doesn't support recurring schedules for messages". And activation and cancellation "are independent operations without mutual locking", so cancelling at the moment of activation may not take effect and Microsoft recommends avoiding "scheduling activation and cancellation operations in close succession".
One interaction ties the two features together: "Scheduled messages are included in duplicate detection", so a scheduled message and a later non-scheduled message sharing a MessageId will suppress one another in whichever order they arrive. If you use both features, make the id encode the intent and not the timing.
Which delivery shape a back-end operation needs
| Requirement | Queue | Topic and subscriptions | Session-enabled queue or subscription |
|---|---|---|---|
| Consumers that act on one message | Exactly one | One per subscription, each on its own copy | Exactly one, and always the receiver holding that session |
| Scale-out shape | Competing consumers on one entity | Fan-out across subscriptions, competing consumers inside each | One receiver per session, many sessions in parallel |
| Selective delivery | None; every receiver draws from the same stream | Subscription rules with SQL, correlation, or boolean filters | Same filtering as the underlying queue or subscription |
| Processing order | Retrieval order only, not processing order | Retrieval order only, per subscription | FIFO within each session id |
| Can it be turned on after creation | n/a | Subscriptions and rules can be added at any time | No; sessions are fixed at entity creation |
Decision tree
Sharp facts the exam loves — give these one last read before exam day.
Cheat sheet
Sharp facts the exam loves — scan these before test day.
- A Service Bus queue delivers each message to exactly one competing consumer (point-to-point)
A Service Bus queue is a point-to-point channel: many consumers can compete for messages, but each message is delivered to and processed by only one receiver. Choose a queue (not a topic) when a back-end operation must be handled exactly once by a single worker.
Trap A topic with multiple subscriptions is fan-out — each subscription gets its own copy — so it violates single-consumer delivery.
5 questions test this
- A billing back end publishes every invoice to a Service Bus topic that also feeds an analytics subscription and a compliance subscription, and both of those must keep receiving all invoices. Three mor
- An order-fulfillment worker has consumed from a Service Bus queue named orders for a year. Last week a newly built audit service was pointed at the same queue. Since then the fulfillment worker proces
- A document-ingestion worker runs as a container replica set that scales between two and ten instances. Every uploaded document must be embedded and written to the vector store exactly one time, no ins
- A consolidation project sets the forwarding target of an intake queue to a shared work queue in the same namespace so that older producers can keep sending to intake unchanged. The long-running worker
- A platform team is designing the notification path for a support-ticket system. Every ticket event must reach three independent consumers, each consumer must be able to abandon and retry a message and
- A topic with subscriptions is publish/subscribe fan-out — each subscription receives its own copy
A Service Bus topic delivers a published message to every matching subscription, and each subscription is itself an independent queue read by its own consumer(s). Use topics + subscriptions when several independent consumers must each process the same event.
13 questions test this
- A notification service reads from a subscription named notify on a Service Bus topic named events, while two other subscriptions on the same topic stay healthy. Repeated processing failures in the not
- Two Service Bus namespaces serve two regions. Every event published to the orders topic in the first namespace must also reach a topic in the second namespace, the publishers in the first region must
- A billing back end publishes every invoice to a Service Bus topic that also feeds an analytics subscription and a compliance subscription, and both of those must keep receiving all invoices. Three mor
- An order topic feeds a single subscription named enrich, and your team defined three separately named rules on it, each carrying an annotation action that stamps routing metadata onto the messages it
- An order-fulfillment worker has consumed from a Service Bus queue named orders for a year. Last week a newly built audit service was pointed at the same queue. Since then the fulfillment worker proces
- A document-ingestion worker runs as a container replica set that scales between two and ten instances. Every uploaded document must be embedded and written to the vector store exactly one time, no ins
- A Python provisioning job onboards each new tenant by creating a dedicated Service Bus namespace together with the tenant's topic and its per-consumer subscriptions. The job authenticates to Azure wit
- An event-distribution topic in a Service Bus namespace already carries close to the maximum number of subscriptions that a topic accepts, and a partner onboarding program will add several thousand mor
- A claims topic feeds four subscriptions. The consumer of the fraud-review subscription needs every message it receives to carry a channel property whose value is priority, the publishing application c
- A platform team is designing the notification path for a support-ticket system. Every ticket event must reach three independent consumers, each consumer must be able to abandon and retry a message and
- A deployment identity creates subscriptions on an events topic every night and must now create one whose messages are forwarded into a reporting queue in the same namespace. The identity holds Manage
- An Azure Functions app is configured to trigger on a Service Bus topic named telemetry, and the app's managed identity already holds the Azure Service Bus Data Owner role on the namespace. The topic's
- An order-routing solution publishes every order to a Service Bus topic. Three regional back ends must divide that stream so that the broker delivers each order only to the region that owns it, publish
- Subscription rules (SQL and correlation filters) select which topic messages a subscription receives
Each subscription can carry rules that filter the topic stream: a correlation filter matches system/application properties (fast, exact match), while a SQL filter evaluates a SQL-like expression over message properties. A subscription with no filter receives every message via the default TrueFilter.
Trap Assuming a subscription with no rule receives nothing until a filter is added.
7 questions test this
- An order topic receives messages whose JSON payload contains a priority field. A new subscription must receive only the high-priority orders, and the broker itself must perform the selection so that c
- Messages published to a Service Bus topic in a document-processing pipeline carry a correlation identifier naming the tenant that submitted the work. One subscription must select only the messages of
- An order topic feeds a single subscription named enrich, and your team defined three separately named rules on it, each carrying an annotation action that stamps routing metadata onto the messages it
- A rush-order subscription on a Service Bus topic has to hold each selected message briefly before its consumer sees it. A developer added a rule action on that subscription which sets the scheduled en
- A subscription named alerts was created on a Service Bus topic without any rule being specified, and its consumer now receives every message published to the topic. It should receive only the messages
- A claims topic feeds four subscriptions. The consumer of the fraud-review subscription needs every message it receives to carry a channel property whose value is priority, the publishing application c
- An order-routing solution publishes every order to a Service Bus topic. Three regional back ends must divide that stream so that the broker delivers each order only to the region that owns it, publish
- PeekLock is a two-stage receive: the message is locked, then explicitly Completed after processing succeeds
In PeekLock mode the broker hands the message to one receiver and locks it for the lock duration; the receiver must call complete after successful work to remove it. This makes processing at-least-once and safe: a crash before Complete releases the lock so the message is redelivered.
Trap Treating a PeekLock receive as having already removed the message from the queue.
14 questions test this
- A queue consumer posts each message to an internal REST service that returns HTTP 503 for about two minutes every night while that service restarts. During the window the handler fails, and the team w
- During a scale-in event, several orders that a queue consumer had already pulled never reached the order database. The team checks the queue and finds no active messages for those orders, and the dead
- Your team must add a second, throughput-oriented drain path for an existing Azure Service Bus queue: a Python job that reads batches and treats every delivered message as consumed the moment it arrive
- An orchestration service defers Azure Service Bus messages whose prerequisite work has not finished, recording each sequence number in a local cache. A container restart wipes that cache, and the defe
- An invoicing consumer receives from a Service Bus queue in PeekLock mode and writes each invoice to Azure Cosmos DB before completing the message. Logs show the Complete call sometimes failing after t
- Your team plans to raise the lock duration on a live Azure Service Bus queue because handlers occasionally exceed the current value. Roughly two hundred messages are under peek-lock at any moment, and
- Enrichment work in a queue consumer calls an embedding model and routinely runs longer than the lock duration configured on the queue. Completing the message then throws a lock-lost error, the same en
- A Python consumer on Azure Container Apps pulls documents from an Azure Service Bus queue and generates embeddings, which takes roughly forty seconds per message. To lift throughput the team raised th
- An Azure Service Bus consumer logs message-lock-lost errors on its completion calls several times an hour, and the captured locked-until timestamps still show time remaining on those locks. The team a
- A queue consumer hands each received message to a background task and lets the receiver's context manager close as soon as the batch loop ends; the background task calls Complete when its own work fin
- A telemetry pipeline on Azure Container Apps drains a Service Bus queue of device heartbeat readings and writes rolling averages to a cache. Heartbeats are re-sent every few seconds, so losing an occa
- A code review covers a Python queue consumer that raises prefetch_count above zero to lift throughput on a busy Service Bus queue. The container hosting the consumer is restarted during every deployme
- An enrichment consumer calls a model whose latency ranges from seconds to several minutes, so the team attaches an automatic lock renewer to the Azure Service Bus receiver. Renewal occasionally stops
- A generic retry decorator wraps an Azure Service Bus handler and replays the whole handler body, including its completion call, whenever any exception escapes. On second attempts the consumer now logs
- ReceiveAndDelete settles the message at delivery, so a mid-processing failure loses it
ReceiveAndDelete removes the message from the queue the instant it is delivered, before processing begins. It is faster and simpler but offers no retry: if the consumer crashes mid-work the message is gone. Use it only for high-throughput, loss-tolerant data.
Trap Reliable single-consumer processing needs PeekLock + Complete, never ReceiveAndDelete.
7 questions test this
- An on-call engineer must read the payloads of the oldest messages sitting on a busy production Azure Service Bus queue to confirm a suspected producer bug. The live consumers must keep receiving norma
- During a scale-in event, several orders that a queue consumer had already pulled never reached the order database. The team checks the queue and finds no active messages for those orders, and the dead
- Your team must add a second, throughput-oriented drain path for an existing Azure Service Bus queue: a Python job that reads batches and treats every delivered message as consumed the moment it arrive
- An invoicing consumer receives from a Service Bus queue in PeekLock mode and writes each invoice to Azure Cosmos DB before completing the message. Logs show the Complete call sometimes failing after t
- A telemetry pipeline on Azure Container Apps drains a Service Bus queue of device heartbeat readings and writes rolling averages to a cache. Heartbeats are re-sent every few seconds, so losing an occa
- A code review covers a Python queue consumer that raises prefetch_count above zero to lift throughput on a busy Service Bus queue. The container hosting the consumer is restarted during every deployme
- A moderation consumer reads Azure Service Bus messages and calls a classifier that is unavailable for a few minutes whenever a new model version is swapped in. Only the messages needing the classifier
- A locked message can be Completed, Abandoned, Dead-lettered, or Deferred
Beyond complete, the SDK exposes abandon (release the lock for immediate redelivery, incrementing the delivery count), dead_letter (move to the DLQ with a reason), and defer (set aside for later retrieval by sequence number). Each is an explicit settlement action only valid on a PeekLock message.
Trap Calling abandon or dead_letter on a message received in ReceiveAndDelete mode.
12 questions test this
- An on-call engineer must read the payloads of the oldest messages sitting on a busy production Azure Service Bus queue to confirm a suspected producer bug. The live consumers must keep receiving norma
- About one message in a thousand on an orders queue carries a payload that fails schema validation, and the identical payload fails on every delivery. Support engineers must later be able to see which
- A queue consumer posts each message to an internal REST service that returns HTTP 503 for about two minutes every night while that service restarts. During the window the handler fails, and the team w
- An internal repair tool your team maintains receives from a queue's dead-letter subqueue, fixes each payload, and sends the corrected message back to the main queue. The main queue drains normally, bu
- An orchestration service defers Azure Service Bus messages whose prerequisite work has not finished, recording each sequence number in a local cache. A container restart wipes that cache, and the defe
- Your team plans to raise the lock duration on a live Azure Service Bus queue because handlers occasionally exceed the current value. Roughly two hundred messages are under peek-lock at any moment, and
- Enrichment work in a queue consumer calls an embedding model and routinely runs longer than the lock duration configured on the queue. Completing the message then throws a lock-lost error, the same en
- An Azure Service Bus consumer logs message-lock-lost errors on its completion calls several times an hour, and the captured locked-until timestamps still show time remaining on those locks. The team a
- A fulfillment workflow receives a payment-confirmation message from the provider before the matching purchase order has propagated from the storefront, so the handler cannot process the confirmation y
- A queue consumer hands each received message to a background task and lets the receiver's context manager close as soon as the batch loop ends; the background task calls Complete when its own work fin
- A moderation consumer reads Azure Service Bus messages and calls a classifier that is unavailable for a few minutes whenever a new model version is swapped in. Only the messages needing the classifier
- A generic retry decorator wraps an Azure Service Bus handler and replays the whole handler body, including its completion call, whenever any exception escapes. On second attempts the consumer now logs
- Locks expire after the lock duration unless renewed; the max delivery count governs redelivery
A PeekLock lock is held only for the entity's lock duration; long-running work must renew the lock (for example with an auto lock renewer) or the message unlocks and is redelivered. Repeated redeliveries eventually exceed the max delivery count.
- The dead-letter queue is a secondary sub-queue of its parent entity, not an entity you provision
Dead-lettered messages land in a dead-letter queue (DLQ), a secondary sub-queue belonging to the queue or topic subscription itself rather than a separate entity. It can't be deleted or managed independently of the main entity, messages can only be submitted to it via the dead-letter operation of the parent, time-to-live isn't observed there, and there's no automatic cleanup: messages stay until you receive and complete them. What you configure is the entity's dead-lettering settings, never the sub-queue itself.
Trap Expecting dead-lettered messages to age out of the DLQ on their own.
12 questions test this
- You develop a Python service that consumes an Azure Service Bus topic named Events through a subscription named Scoring, one of three subscriptions on that topic. Only Scoring dead-lettered a batch of
- You develop a Python batch-scoring worker that reads an Azure Service Bus queue. Each request message carries a short time to live because a stale score is worthless to the caller. Compliance now requ
- You develop a Python triage service for an Azure Service Bus namespace in which eight queues each forward their dead-lettered messages to one queue named repairs. The service receives from repairs and
- You develop a Python reprocessing job that runs nightly in Azure Container Apps and must replay the messages that an Azure Service Bus queue named Ingest rejected during the day. You use the azure-ser
- You develop a Python consumer on Azure Container Apps that receives Azure Service Bus messages in PeekLock mode and parses each JSON payload before enriching it with an embedding. A small share of mes
- You develop a Python scoring worker that reads an Azure Service Bus topic subscription whose SQL rule selects messages by an application property. A recent publisher change makes that rule fail while
- You develop a Python producer that sends inference requests to an Azure Service Bus queue named Intake. Intake is configured to autoforward into a session-enabled queue named Ordered, where a downstre
- You build a Python order-fulfillment worker that consumes an Azure Service Bus queue. When a payment notification arrives before its matching purchase order, the worker defers the message and moves on
- You support thirty Azure Service Bus queues in one namespace that feed Python inference workers on Azure Container Apps. The platform team must be paged as soon as messages start landing in any queue'
- You develop a Python triage job for an Azure Service Bus namespace in which a subscription named Handoff autoforwards enriched inference results into a partner-owned queue. That queue was disabled for
- You develop a Python service that consumes an Azure Service Bus topic named Telemetry through a subscription named Enrich. A dashboard shows the topic's active message count sitting at zero while the
- Your team builds a Python worker on Azure Functions that reads an Azure Service Bus queue and calls a model endpoint. A design review proposes provisioning a second Service Bus queue named orders-fail
- Messages dead-letter when the max delivery count is exceeded, TTL expires, or the app calls dead_letter
Service Bus moves a message to the DLQ automatically when it exceeds the max delivery count (repeated abandon/lock loss) or when its time-to-live expires with dead-lettering on expiration enabled. The application can also dead-letter a message explicitly (for example, a poison/unparseable payload).
Trap Expecting an expired message to reach the DLQ with dead-lettering on expiration switched off.
10 questions test this
- You develop a Python service on Azure Kubernetes Service that receives Azure Service Bus messages in PeekLock mode, hands each one to a background task, and closes the receiver as soon as the batch lo
- You develop a Python batch-scoring worker that reads an Azure Service Bus queue. Each request message carries a short time to live because a stale score is worthless to the caller. Compliance now requ
- You develop a Python consumer on Azure Container Apps that receives Azure Service Bus messages in PeekLock mode and parses each JSON payload before enriching it with an embedding. A small share of mes
- You develop a Python scoring worker that reads an Azure Service Bus topic subscription whose SQL rule selects messages by an application property. A recent publisher change makes that rule fail while
- You develop a Python producer that sends inference requests to an Azure Service Bus queue named Intake. Intake is configured to autoforward into a session-enabled queue named Ordered, where a downstre
- You develop a Python drain job that reads an Azure Service Bus queue's dead-letter subqueue in PeekLock mode and republishes each repaired message. Some messages can never be repaired. The team assume
- You develop a Python coordinator on Azure Container Apps that defers each Azure Service Bus request whose tenant index is still rebuilding, recording the sequence number of every deferred request. The
- You build a Python order-fulfillment worker that consumes an Azure Service Bus queue. When a payment notification arrives before its matching purchase order, the worker defers the message and moves on
- You develop a Python worker that consumes an Azure Service Bus queue in a Standard-tier namespace. When enrichment raises an unrecoverable error the worker dead-letters the message and puts the failur
- Your team builds a Python worker on Azure Functions that reads an Azure Service Bus queue and calls a model endpoint. A design review proposes provisioning a second Service Bus queue named orders-fail
- Read the DLQ by opening a receiver on the entity's /$deadletterqueue sub-path
To inspect or reprocess dead-lettered messages you open a receiver against the sub-queue formatted as /$DeadLetterQueue (or /subscriptions//$DeadLetterQueue). In the Python SDK this is done via the sub_queue=ServiceBusSubQueue.DEAD_LETTER option when creating the receiver.
8 questions test this
- You develop a Python service that consumes an Azure Service Bus topic named Events through a subscription named Scoring, one of three subscriptions on that topic. Only Scoring dead-lettered a batch of
- You develop a Python triage service for an Azure Service Bus namespace in which eight queues each forward their dead-lettered messages to one queue named repairs. The service receives from repairs and
- You develop a Python reprocessing job that runs nightly in Azure Container Apps and must replay the messages that an Azure Service Bus queue named Ingest rejected during the day. You use the azure-ser
- You develop a Python repair job for a session-enabled Azure Service Bus queue in which each session carries one customer's ordered updates. The job receives from the dead-letter subqueue, corrects eac
- You develop a Python drain job that reads an Azure Service Bus queue's dead-letter subqueue in PeekLock mode and republishes each repaired message. Some messages can never be repaired. The team assume
- You develop a Python coordinator on Azure Container Apps that defers each Azure Service Bus request whose tenant index is still rebuilding, recording the sequence number of every deferred request. The
- You develop a Python triage job for an Azure Service Bus namespace in which a subscription named Handoff autoforwards enriched inference results into a partner-owned queue. That queue was disabled for
- You develop a Python service that consumes an Azure Service Bus topic named Telemetry through a subscription named Enrich. A dashboard shows the topic's active message count sitting at zero while the
- Dead-lettered messages carry DeadLetterReason and DeadLetterErrorDescription properties
When a message is dead-lettered the broker (or app) records DeadLetterReason and DeadLetterErrorDescription in the message's application properties, letting an operator triage why delivery failed before reprocessing.
- Sessions provide guaranteed FIFO ordering for all messages sharing a session id
Enabling sessions on a queue/subscription groups messages by SessionId and locks an entire session to one receiver, guaranteeing first-in-first-out processing within that session. This is how you achieve ordered, related-message processing that a plain competing-consumer queue cannot.
Trap A session must be enabled at entity creation; you cannot get per-key ordering from a non-session queue just by setting SessionId.
10 questions test this
- A Python worker on Azure Container Apps holds one session of a session-enabled Azure Service Bus queue in which each session carries one patient's chart updates. To raise throughput the team now recei
- A partner's ordering system publishes to your session-enabled Azure Service Bus queue over AMQP 1.0 using a generic client library rather than an Azure SDK. Its orders must join the same per-customer
- An order pipeline uses a session-enabled Azure Service Bus queue keyed by order identifier, and some sessions sit idle for hours while an upstream approval completes. Auditors report that an entire or
- An Azure Service Bus queue in production has been receiving unordered work items for two years. A porting team now stamps every message with a session ID matching the tenant and rewrites the consumer
- A telemetry publisher packs several hundred device readings into a single Azure Service Bus message batch and sends it to a session-enabled queue, using each device's identifier as the session ID so a
- A claims service on Azure Kubernetes Service runs eight workers, each holding one session of a session-enabled Azure Service Bus queue keyed by claim number. A worker keeps its session receiver open a
- An order pipeline uses one Azure Service Bus session per order on a session-enabled queue, and an order's messages can arrive over several hours. A worker must recognize when an order is complete so t
- A worker pool on Azure Kubernetes Service drains a session-enabled Azure Service Bus queue in which each session corresponds to one customer order. The publisher creates sessions dynamically, so no wo
- A pricing service publishes quote requests from many client instances onto one Azure Service Bus request queue, and a pool of workers writes each answer onto a shared, session-enabled reply queue. Eve
- A billing reconciliation service on Azure Container Apps runs several replicas that all consume one Azure Service Bus queue. Ledger events for a single account must be applied in the order the publish
- A session receiver accepts a specific or the next available session and holds a session lock
To read a session-enabled entity you create a session receiver, which locks one session and delivers its messages in order. In Python there is no separate accept call: the session is chosen through the same receiver factory, either get_queue_receiver(queue_name=..., session_id="") for a named session or session_id=NEXT_AVAILABLE_SESSION to take whichever session is free. Session state can be persisted on the broker through the receiver's session object to checkpoint per-session progress.
Trap Hunting for a separate accept-session call in the Python SDK.
10 questions test this
- A Python worker on Azure Container Apps holds one session of a session-enabled Azure Service Bus queue in which each session carries one patient's chart updates. To raise throughput the team now recei
- A loan-origination workflow on Azure Container Apps records each application's progress in the session state of its Azure Service Bus session, keyed by application number, so a restarted worker resume
- A support dashboard must list the messages still waiting on a session-enabled Azure Service Bus queue, grouped by session, and refresh that list every minute. The worker fleet processing those session
- An Azure Service Bus queue in production has been receiving unordered work items for two years. A porting team now stamps every message with a session ID matching the tenant and rewrites the consumer
- A long-running document-approval workflow processes one Azure Service Bus session per case, and a case's messages can arrive over several hours. When a worker instance is evicted mid-case, another ins
- A claims service on Azure Kubernetes Service runs eight workers, each holding one session of a session-enabled Azure Service Bus queue keyed by claim number. A worker keeps its session receiver open a
- An order pipeline uses one Azure Service Bus session per order on a session-enabled queue, and an order's messages can arrive over several hours. A worker must recognize when an order is complete so t
- A worker pool on Azure Kubernetes Service drains a session-enabled Azure Service Bus queue in which each session corresponds to one customer order. The publisher creates sessions dynamically, so no wo
- A pricing service publishes quote requests from many client instances onto one Azure Service Bus request queue, and a pool of workers writes each answer onto a shared, session-enabled reply queue. Eve
- A billing reconciliation service on Azure Container Apps runs several replicas that all consume one Azure Service Bus queue. Ledger events for a single account must be applied in the order the publish
- Message time-to-live expires undelivered messages, optionally routing them to the DLQ
Each message has a time-to-live (defaulting to the entity's default TTL, capped by it); once it expires the message is removed, and if dead-lettering on message expiration is enabled it is moved to the DLQ instead of being silently dropped.
Trap Setting a per-message TTL longer than the entity's default and expecting it to hold.
6 questions test this
- An ingestion queue in Azure Service Bus was created with a default message time-to-live of one hour. A new publisher sets a seven-day time-to-live on every message it sends so that a weekend outage of
- An order pipeline uses a session-enabled Azure Service Bus queue keyed by order identifier, and some sessions sit idle for hours while an upstream approval completes. Auditors report that an entire or
- An operations console browses an Azure Service Bus queue with the peek operation and lists, for every waiting message, how long is left before it expires. The queue carries a default message time-to-l
- A pricing service sends quotes to an Azure Service Bus queue with a short message time-to-live and dead-lettering on message expiration enabled, so a quote nobody consumed in time is captured for audi
- A media-transcoding consumer reads from an Azure Service Bus queue in peek-lock mode, and some jobs take longer than the message time-to-live configured on the queue. Dead-lettering on message expirat
- An audit rule states that no copy of an event delivered through an Azure Service Bus topic may stay retrievable for longer than one hour, and product teams keep creating new subscriptions on that topi
- ServiceBusClient is the connection factory that creates senders and receivers
A single ServiceBusClient (built from a namespace + DefaultAzureCredential or a connection string) is the entry point; you call get_queue_sender / get_queue_receiver (or the topic/subscription variants) to obtain a ServiceBusSender for publishing and a ServiceBusReceiver for consuming. Messages are ServiceBusMessage objects.
8 questions test this
- You are moving a Python message producer from a developer laptop to Azure Container Apps, where the revision runs under a user-assigned managed identity holding the Azure Service Bus Data Sender role
- Your ingestion service publishes extraction requests to an Azure Service Bus queue from a Python worker. The excerpt carried by each request varies widely in size, and the worker currently passes a li
- An Azure Service Bus queue drives a batch-scoring service that you develop in Python. When a job is accepted, the service must place a cleanup message that becomes available to consumers six hours lat
- A Python service publishes to an Azure Service Bus queue that has duplicate detection enabled. For each accepted transcription job it schedules a follow-up message for two hours later and immediately
- A nightly reconciliation job must read the messages that Azure Service Bus moved to the dead-letter subqueue of a queue named invoices, inspect each dead-letter reason, and complete the ones it can di
- Your document-extraction service publishes work items to an Azure Service Bus topic named jobs and consumes them from a subscription named ocr on that topic. The Python worker authenticates with Defau
- A Python web API that you maintain on Azure Container Apps publishes one Azure Service Bus message per HTTP request. Each request handler constructs a ServiceBusClient, obtains a queue sender from it,
- You deploy a Python consumer for an Azure Service Bus queue to an Azure Kubernetes Service cluster whose egress firewall permits outbound TCP 443 only. The pod authenticates with a managed identity an
- The processor model registers message/error handler callbacks and can auto-complete on success
The event-driven ServiceBusProcessor (.NET) registers a message handler and an error handler and continuously pumps messages, auto-completing them on success unless auto-complete is disabled; the Python SDK achieves the same by iterating a ServiceBusReceiver and settling each message explicitly.
Trap Expecting the Python receiver to auto-complete messages the way the .NET processor does.
6 questions test this
- You maintain a long-running Python consumer for an Azure Service Bus queue that has to keep pulling and settling messages for as long as its container runs, rather than issuing a fresh request for eac
- You are adding a support endpoint to a Python service that lists the messages currently waiting in an Azure Service Bus queue so the team can see what is backed up. The listing must not consume or loc
- A team is porting a Service Bus consumer from the .NET ServiceBusProcessor, which completed each message automatically when the handler returned successfully, to Python with the azure-servicebus libra
- A summarization worker receives Service Bus messages in the default peek-lock mode and calls a model that routinely runs longer than the queue's lock duration, which is already at the documented maxim
- A .NET back-end service registers a message handler and an error handler on a Service Bus processor and leaves the processor's automatic completion enabled. The handler wraps its whole body in a catch
- A .NET consumer on Azure Container Apps processes Azure Service Bus messages through a processor whose handlers are registered once at startup. A downstream vector store is taken offline for maintenan
- Duplicate detection discards messages with a repeated MessageId within a configured time window
When duplicate detection is enabled on a queue/topic, the broker ignores any incoming message whose MessageId matches one seen within the configured detection history window (default 10 minutes, minimum 20 seconds, maximum 7 days). A suppressed send still reports success to the sender, and no part of the message other than the MessageId is considered, so the sender must set a stable, reconstructible MessageId for this to work.
Trap Expecting a send suppressed by duplicate detection to surface as an error.
6 questions test this
- Your ingestion worker receives Service Bus messages in peek-lock mode, writes each result to Azure Cosmos DB, and then completes the message. When a replica is evicted mid-batch, the broker redelivers
- An Azure Service Bus queue that is partitioned and has duplicate detection enabled receives enrichment requests. The publisher stamps every message with a stable MessageId and with a partition key equ
- An existing Azure Service Bus queue named ingest was created without duplicate detection. Its publisher has started resending messages after transient failures, and the team now wants the broker itsel
- A Python service publishes to an Azure Service Bus queue that has duplicate detection enabled. For each accepted transcription job it schedules a follow-up message for two hours later and immediately
- You are sizing duplicate detection for an Azure Service Bus queue that carries a high, steady volume of embedding requests. The publisher's retry policy gives up a few minutes after the first attempt,
- A publisher writes enrichment requests to an Azure Service Bus queue on the Standard tier that has duplicate detection enabled. When the network drops an acknowledgment, the publisher's retry policy s
- Messages can be scheduled for future enqueue via a scheduled enqueue time
A sender can schedule a message to become available at a future instant by setting its scheduled enqueue time (or calling schedule_messages), which returns a sequence number that can be used to cancel the scheduled delivery before it fires.
References
- Compare Azure Event Grid, Event Hubs, and Service Bus
- Azure Service Bus queues, topics, and subscriptions
- Azure Service Bus topic filters and actions
- Message transfers, locks, and settlement in Azure Service Bus
- azure.servicebus.ServiceBusClient class (Python SDK reference)
- azure.servicebus.ServiceBusReceiver class (Python SDK reference)
- Azure Service Bus dead-letter queues
- Azure Service Bus message expiration and time to live
- Enable FIFO with Azure Service Bus message sessions
- azure.servicebus.ServiceBusSubQueue enum (Python SDK reference)
- Azure Service Bus message sequencing and timestamps
- Enable Azure Service Bus message sessions
- Create partitioned Azure Service Bus topics and queues
- azure.servicebus.ServiceBusSender class (Python SDK reference)
- ServiceBusProcessor class (Azure.Messaging.ServiceBus .NET reference)
- Azure Service Bus duplicate message detection