Why an Azure Service Bus queue loses messages during peaks
At peak load, an Azure Service Bus queue behaves differently than in a test environment: depth grows faster than consumers can drain messages, and message loss does not appear immediately. First it is processing delay, then a rise in delivery attempts, then the dead-letter queue.
The cause is almost never the broker itself — subscription limits, no concurrency limit on the consumer, and too short message time-to-live add up.
Then the cascade starts: a message is not acknowledged on time, the lock expires, the broker redelivers it, and the handler runs the same operation a second time. Redelivery turns into duplicate charges while the on-call engineer sorts out the incident manually.
Some messages go to the dead-letter queue, some expire by time-to-live — there is no longer any way to restore them.
The cost of failure is not only exchange downtime. Each incident is analyzed manually: pull metrics, match identifiers, find the step where the message was lost, deliver it again, and at the same time not duplicate the operation.
If queue permissions are granted broadly, risk is added: any service can read and delete someone else's.
So we start not with settings, but with analyzing the flow: where a message can be lost, which retries are safe, what limits the consumer at peak, and by what signal the on-call engineer understands it is time to intervene. Below is how this is addressed.
What a managed bus provides: resilience and observability
A managed Azure Service Bus removes a whole class of integration failures through delivery acknowledgments and retries with increasing delay.
A message leaves the queue only after successful processing, and when attempts are exhausted it goes to the dead-letter queue with a reason code.
Duplicates are cut off by an idempotency key on the consumer side — re-running after a failure does not create second orders or charges.
Observability is built on the bus's own metrics: queue length, age of the oldest message, number of retries, circuit breaker trip rate.
A correlation identifier links the bus record to processing in the service, so the incident is analyzed from the log, not from scattered application logs. We set an alert on dead-letter queue growth — the on-call engineer learns about the problem before the business notices it.
Access is separated by roles: senders only write, consumers only read, dead-letter handling is a separate role for the on-call engineer. Keys live in a secret store and are rotated; direct connection strings do not remain in service configuration.
We run the load profile before production: peak input, failure of one consumer, long processing delay.
After such a run, recovery time after failure is measured in minutes, the share of lost messages for acknowledged sends is zero, and the volume of manual investigations drops to a few per week.
Delivery modes and queue topologies
Queue topology determines not only the message route but also how the system behaves during a failure. An incorrectly chosen delivery mode only shows up under load: duplicate orders, a lost event, a stuck offload queue. Below are the options among which people actually choose when designing message exchange.
Exchange options and their limitations
| Option | When it fits | Limitations |
|---|---|---|
| Point-to-point queue | one producer — one consumer: commands, processing jobs | competing consumers do not preserve order by business key |
| Topic with multiple subscriptions | an event must independently reach several systems | each subscription stores its own copy; volume grows linearly with the number of consumers |
| Sessions | strict order within a key: one order, one invoice | a session is processed by one consumer — lower concurrency |
| Deduplication | the source repeats a send during its own failure | the key retention window is limited, and the key must be set explicitly |
| Auto-forwarding | offloading an input queue and routing by attribute | the chain hides the message path and complicates incident analysis |
Modes are combined: sessions on top of subscriptions give order by key and independent consumers, while deduplication together with auto-forwarding closes redelivery at the junction of services.
Importantly, each combination also changes the load profile — the number of concurrent handlers, message time-to-live, and the threshold for moving to the dead-letter queue.
We record the chosen topology in the exchange schema and in settings so that failure behavior is predictable. On the Azure Service Bus platform, these modes are configured declaratively, so the schema remains reproducible between environments.
Implementation: from audit to handover to operations
Before making changes in the production environment, we record the current state of message exchange: who publishes, who reads, where messages are lost or processed twice.
An integration audit produces a flow map and a list of bottlenecks, so each subsequent step relies on verified data, not assumptions.
- Integration audit. We analyze publishers and subscribers, Azure Service Bus topics and queues, client retries, and areas without duplicate protection. The output is a flow diagram with processing time and the volume of messages that went to deferred.
- Schema design. We distribute load across queues and subscriptions, define redelivery rules, message time-to-live, and duplicate suppression.
- Queue and access configuration. We create entities and configure separation of rights between services and people: each gets exactly the operations they need.
- Load testing. We run peak flow and failure scenarios: unavailable subscriber, slow processing, queue growth. We check whether the system recovers on its own and in what time.
- Operations regulations. We describe trigger thresholds, on-call actions, the procedure for releasing messages from deferred, and rules for changing settings.
- Handover to support. We walk through the schema with your team, hand over documentation, and remain in touch during the first changes.
Access policies, roles, and regulations: what we hand over
At the output, the customer receives not a 'configured queue' but a package by which the service can be operated, handed over to another team, and pass a security review.
For Azure Service Bus this is especially important: access here is granted at the level of the namespace and individual entities, and without explicit regulations rights spread within a couple of months. Below is the minimum set of artifacts that we hand over together with a working bus.
- Access policies — a separate policy for send, listen, and manage for each service instead of one shared key for the whole environment.
- Roles and keys — binding services through managed identities where supported; where not — a key with owner, rotation period, and revocation procedure indicated.
- Permission separation — a matrix 'service → queue or topic → allowed operations', which shows who can write or read where.
- Message flow schema — senders, subscribers, filter rules, retry handling, and dead-letter queue.
- Entity description — queues, topics, subscriptions, message time-to-live, duplicate detection, session mode: all with justification for the chosen values.
- Documentation — topology, naming rules, list of service owners, and instructions for connecting a new consumer.
- Maintenance regulations — key rotation frequency, procedure for removing a service from access, handling accumulated dead-letter messages, and alert thresholds.
Such a package removes the main operational problem — when the bus works, but no one knows which rights were granted to whom and what to do during a failure. Describe your task — we will come back with an estimate and work plan.
Case: order flow under peak load
Before the changes, the entire order flow went through one Azure Service Bus queue: there were two subscribers, and a connection string with both send and read rights was issued into a shared config.
During peak, one of the subscribers failed, messages returned to the queue and were processed again — customers received two identical orders, and the on-call engineer handled duplicates manually.
Recovery after such a failure took up to 40 minutes, and there was no way to understand the cause: no one was collecting queue metrics.
What exactly was changed
- We separated flows: priority orders, background analytics, and notifications went into separate queues with their own subscribers and limits.
- We enabled deduplication by order identifier and a limited number of retries with transfer to the dead-letter queue.
- We replaced the connection string with role-based permission grants: each service has only the operations it needs.
- We configured alerts on queue length, age of the oldest message, and the share of records in the dead-letter queue.
What the metrics showed
The duplicate share fell from 1.4% to 0.02% — reprocessing stopped being a standard scenario. Recovery time after a subscriber failure dropped from 40 minutes to 4 minutes: messages remain in the queue until the service returns.
During peak, the queue is drained in a matter of minutes, the age of the oldest message stays within the alert threshold, and orders requiring manual intervention are measured in units per day instead of hundreds.
How is Service Bus different from a DIY queue?
A DIY queue is usually a table in a database and a background poll on a schedule. While there is one consumer and the flow is small, the scheme holds, but with a second handler races for the row appear, along with double processing and 'stuck' records that no one returns to work.
A managed bus, such as Azure Service Bus, handles delivery itself: processing acknowledgment, automatic retry on failure, a separate queue for unprocessed messages, ordering, and duplicate protection. Idempotency of the handler itself remains with you — no broker can replace it.
Operational load is distributed differently: you maintain a DIY queue entirely — cleaning up accumulated data, schema migrations, backup, on-call duty.
With a bus, this is on the provider side, while queue depth, retry count, and dead letters are visible in metrics, and access to topics is granted by roles through a directory.
Limitations also exist: a shared service sets ceilings on message size, number of subscriptions, and deployment region. The choice is summarized in the table.
| Option | When it fits | Limitations |
|---|---|---|
| Queue in your own database | one consumer, low flow, everything in one environment | no delivery guarantees, manual retry, races for the row |
| Broker on your own servers | data placement requirements, non-standard rules | fault tolerance and on-call duty are entirely on you |
| Managed bus | multiple consumers, retries needed, ordering, dead-letter handling | limits of a shared service, platform dependency |
Technical doubts before starting work
Before starting work on Azure Service Bus queues, customers and their engineers raise approximately the same set of questions: how to migrate without stopping services, what to do with already written consumers and producers, who will get access, and what the on-call engineer will see at the moment of failure. Below are short answers so that fewer unknowns remain before the estimate.
Can queues be migrated without stopping services?
Yes, queue migration proceeds through a parallel environment: we bring up a new namespace next to the production one, enable message forwarding from the old to the new, switch consumers one by one, and keep the previous environment available for rollback for an agreed period.
The application does not stop — unprocessed messages wait in the queue for the switch, and redelivery is suppressed by configuring the number of attempts and the lock window.
Will code have to be rewritten for another library?
Compatibility is determined by the messaging protocol, not by the version of the library in the application. In practice, the connection string, retry settings, and session completion flag change — the processing logic remains the same. We plan a full rewrite only where custom wrappers over queues are currently used.
How are accesses granted and where are keys stored?
We grant access through a role model with separation of rights for send, listen, and manage, separately for the namespace and for a specific queue. Rights are granted to service accounts, not people: when an employee leaves, access to queues does not change.
Secrets live in a secret store and are injected into the runtime; they are not in the repository or configurations.
What does the on-call engineer see and when does an alert fire?
Monitoring is built on four signals: queue length, age of the oldest message, share of messages in the unprocessed queue, and growth in the number of redeliveries. For each, we set a threshold and a hold time so that on-call duty is not triggered by a short spike.
We attach a troubleshooting instruction to the alerts: where to look, what to restart, and in which case to call the developer.
Let's discuss your queues task
An estimate for queues almost never comes down to 'set up Azure Service Bus for us': the scope of work is determined by the load profile and delivery requirements.
The more precise the inputs at the start, the fewer assumptions in the plan — and the fewer reworks after the first runs under real traffic.
Below is what is worth attaching to the task description. Some data is already in monitoring; for some it is enough to estimate by order of magnitude:
- flow schema: who publishes messages, who reads them, how many queues and subscriptions are needed, what goes to deferred processing;
- load profile: average and peak intensity, daily and weekly rhythm, acceptable delivery delay during peak hours;
- delivery requirements: whether duplicates are acceptable, whether strict processing order is needed, after how many failed attempts a message goes to the unprocessed queue;
- access model: which services and teams access which entities, whether separate rights for send and listen are needed;
- response mode: where alerts go, who watches them outside working hours, which thresholds are considered emergencies;
- current state and constraints: whether queues are already deployed, how many messages have accumulated, whether migration from another broker is planned, how configurations are rolled out.
Then we compare the inputs with platform limitations and come back with a work estimate, stage composition, and timelines. If some data is missing, write so: we will suggest how to collect it or what to replace it with so as not to slow down the start.







