Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3e468c1
Review of Azure Storage Queues multi-accounts
helenktsai Oct 28, 2025
069467b
Update multi-storage account support documentation
helenktsai Oct 28, 2025
aa004ca
Delete transports/azure-storage-queues/azure01.png
helenktsai Oct 30, 2025
27df4e8
Improve clarity on multi-storage account support
helenktsai Oct 30, 2025
fc46d49
Refine multi-storage account support documentation
helenktsai Oct 31, 2025
f24e928
move scale units down
helenktsai Oct 31, 2025
a8f5d77
Update multi-storageaccount-support.md with scaling info
helenktsai Oct 31, 2025
5ad9e57
Clarify NServiceBus routing with multiple storage accounts
helenktsai Oct 31, 2025
45924aa
Update multi-storageaccount-support.md
helenktsai Oct 31, 2025
a8c5deb
Removed obsolete partials and images
ngallegos Nov 3, 2025
50c90f0
Merge branch 'master' into helenktsai-patch-1
ngallegos Nov 3, 2025
b6219c1
Fixed broken link
ngallegos Nov 3, 2025
e1ec6e6
Another link
ngallegos Nov 3, 2025
661a337
removed unused snippet
ngallegos Nov 3, 2025
0340dd2
Apply suggestions from code review
helenktsai Nov 4, 2025
bd8117c
Apply suggestions from code review
helenktsai Nov 4, 2025
2d08896
update reg endpoint with explicit aliases used
helenktsai Nov 4, 2025
4837a9e
Update link for multi-account support in documentation
helenktsai Nov 4, 2025
00bb617
Fix link to multi-account support documentation
helenktsai Nov 4, 2025
4d21861
Fix grammar in multi-storage account support documentation
helenktsai Nov 4, 2025
2e8540d
Fixing the ASQ build
ngallegos Nov 4, 2025
4ebf5f3
Clarify endpoint registration and message routing details
helenktsai Nov 4, 2025
b10d456
Apply suggestions from code review
helenktsai Nov 10, 2025
ce8ac51
Update transports/azure-storage-queues/multi-storageaccount-support.md
helenktsai Nov 10, 2025
b0929ee
Update multi-storageaccount-support.md for clarity
helenktsai Nov 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Snippets/ASQ/ASQN_10/Usage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,11 @@ void RegisterEndpoint(EndpointConfiguration configuration)
var transportConfig = configuration.UseTransport<AzureStorageQueueTransport>();

var routing = transportConfig
.ConnectionString("connectionString")
.ConnectionString("account_A_connection_string")
.DefaultAccountAlias("account_A")
.AccountRouting();
var anotherAccount = routing.AddAccount("AnotherAccountName", "anotherConnectionString");

var anotherAccount = routing.AddAccount("account_B","account_B_connection_string");
anotherAccount.AddEndpoint("Receiver");

transportConfig.Routing().RouteToEndpoint(typeof(MyMessage), "Receiver");
Expand Down
9 changes: 5 additions & 4 deletions Snippets/ASQ/ASQN_11/Usage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,13 @@ void RegisterEndpoint(EndpointConfiguration configuration)
{
#region storage_account_routing_registered_endpoint

var transport = new AzureStorageQueueTransport("connectionString");
var transport = new AzureStorageQueueTransport("account_A_connection_string");
transport.AccountRouting.DefaultAccountAlias = "account_A";

var anotherAccount = transport.AccountRouting.AddAccount(
"AnotherAccountName",
new QueueServiceClient("anotherConnectionString"),
CloudStorageAccount.Parse("anotherConnectionString").CreateCloudTableClient());
"account_B",
new QueueServiceClient("account_B_connection_string"),
CloudStorageAccount.Parse("account_B_connection_string").CreateCloudTableClient());

anotherAccount.AddEndpoint("Receiver");

Expand Down
9 changes: 5 additions & 4 deletions Snippets/ASQ/ASQN_12/Usage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,13 @@ void RegisterEndpoint(EndpointConfiguration configuration)
{
#region storage_account_routing_registered_endpoint

var transport = new AzureStorageQueueTransport("connectionString");
var transport = new AzureStorageQueueTransport("account_A_connection_string");
transport.AccountRouting.DefaultAccountAlias = "account_A";

var anotherAccount = transport.AccountRouting.AddAccount(
"AnotherAccountName",
new QueueServiceClient("anotherConnectionString"),
new TableServiceClient("anotherConnectionString"));
"account_B",
new QueueServiceClient("account_B_connection_string"),
new TableServiceClient("account_B_connection_string"));
anotherAccount.AddEndpoint("Receiver");

var routingConfig = configuration.UseTransport(transport);
Expand Down
9 changes: 5 additions & 4 deletions Snippets/ASQ/ASQN_13/Usage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,13 @@ void RegisterEndpoint(EndpointConfiguration configuration)
{
#region storage_account_routing_registered_endpoint

var transport = new AzureStorageQueueTransport("connectionString");
var transport = new AzureStorageQueueTransport("account_A_connection_string");
transport.AccountRouting.DefaultAccountAlias = "account_A";

var anotherAccount = transport.AccountRouting.AddAccount(
"AnotherAccountName",
new QueueServiceClient("anotherConnectionString"),
new TableServiceClient("anotherConnectionString"));
"account_B",
new QueueServiceClient("account_B_connection_string"),
new TableServiceClient("account_B_connection_string"));
anotherAccount.AddEndpoint("Receiver");

var routingConfig = configuration.UseTransport(transport);
Expand Down
9 changes: 5 additions & 4 deletions Snippets/ASQ/ASQN_14/Usage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,13 @@ void RegisterEndpoint(EndpointConfiguration configuration)
{
#region storage_account_routing_registered_endpoint

var transport = new AzureStorageQueueTransport("connectionString");
var transport = new AzureStorageQueueTransport("account_A_connection_string");
transport.AccountRouting.DefaultAccountAlias = "account_A";

var anotherAccount = transport.AccountRouting.AddAccount(
"AnotherAccountName",
new QueueServiceClient("anotherConnectionString"),
new TableServiceClient("anotherConnectionString"));
"account_B",
new QueueServiceClient("account_B_connection_string"),
new TableServiceClient("account_B_connection_string"));
anotherAccount.AddEndpoint("Receiver");

var routingConfig = configuration.UseTransport(transport);
Expand Down
6 changes: 4 additions & 2 deletions Snippets/ASQ/ASQN_9/Usage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,11 @@ void RegisterEndpoint(EndpointConfiguration configuration)

var transportConfig = configuration.UseTransport<AzureStorageQueueTransport>();
var routing = transportConfig
.ConnectionString("connectionString")
.ConnectionString("account_A_connection_string")
.DefaultAccountAlias("account_A")
.AccountRouting();
var anotherAccount = routing.AddAccount("AnotherAccountName","anotherConnectionString");

var anotherAccount = routing.AddAccount("account_B","account_B_connection_string");
anotherAccount.RegisteredEndpoints.Add("Receiver");

transportConfig.Routing().RouteToEndpoint(typeof(MyMessage), "Receiver");
Expand Down
17 changes: 4 additions & 13 deletions Snippets/ASQ/ASQ_8/Usage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,6 @@ void AccountAliasesInsteadOfConnectionStrings(EndpointConfiguration endpointConf
#endregion
}

async Task SendToMulitpleAccountUsingConnectionSTring(IEndpointInstance endpointInstance)
{
#region storage_account_routing_send_options_full_connectionstring

await endpointInstance.Send(
destination: "sales@DefaultEndpointsProtocol=https;AccountName=[ACCOUNT];AccountKey=[KEY];",
message: new MyMessage());

#endregion
}

async Task SendToMulitpleAccountUsingAlias(IEndpointInstance endpointInstance)
{
#region storage_account_routing_send_options_alias
Expand All @@ -67,9 +56,11 @@ void RegisterEndpoint(EndpointConfiguration configuration)

var transportConfig = configuration.UseTransport<AzureStorageQueueTransport>();
var routing = transportConfig
.ConnectionString("connectionString")
.ConnectionString("account_A_connection_string")
.DefaultAccountAlias("account_A")
.AccountRouting();
var anotherAccount = routing.AddAccount("AnotherAccountName","anotherConnectionString");

var anotherAccount = routing.AddAccount("account_B","account_B_connection_string");
anotherAccount.RegisteredEndpoints.Add("Receiver");

transportConfig.Routing().RouteToEndpoint(typeof(MyMessage), "Receiver");
Expand Down
Binary file removed transports/azure-storage-queues/azure01.png
Binary file not shown.
Binary file removed transports/azure-storage-queues/azure02.png
Binary file not shown.
Binary file removed transports/azure-storage-queues/azure04.png
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ snippet: AzureStorageQueueUseAccountAliasesInsteadOfConnectionStrings
> [!NOTE]
> For the default connection string without an additional storage account specified, an empty string is the default alias.

See also [Using aliases instead of connection strings](/transports/azure-storage-queues/multi-storageaccount-support.md#cross-namespace-routing-aliases-instead-of-connection-strings) for multi-account support.
See also [Using aliases instead of connection strings](/transports/azure-storage-queues/multi-storageaccount-support.md#nservicebus-routing-with-multiple-storage-accounts-connection-string-aliases) for multi-account support.
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ Storage account aliases are enforced by default. The alias is mapped to the phys
> [!NOTE]
> the default alias is an empty string.

See also [Using aliases instead of connection strings](/transports/azure-storage-queues/multi-storageaccount-support.md#cross-namespace-routing-aliases-instead-of-connection-strings) for multi-account support.
See also [Using aliases instead of connection strings](/transports/azure-storage-queues/multi-storageaccount-support.md#nservicebus-routing-with-multiple-storage-accounts-connection-string-aliases) for multi-account support.
59 changes: 33 additions & 26 deletions transports/azure-storage-queues/multi-storageaccount-support.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Multiple storage accounts
title: Multiple storage accounts with Azure Storage Queues
summary: Use multiple Azure storage accounts for scale out
component: ASQ
reviewed: 2025-10-30
Expand All @@ -12,56 +12,63 @@ related:
- transports/azure-storage-queues/configuration
---

Endpoints running on the Azure Storage Queues transport using a single storage account are subject to potential throttling once the maximum number of concurrent requests to the storage account is reached. Multiple storage accounts can be used to overcome this limitation. To better understand scale out options with storage accounts, it is advised to first read carefully the [Azure storage account scalability and performance targets](https://docs.microsoft.com/en-us/azure/storage/common/storage-scalability-targets) article.
> [!IMPORTANT]
> Using multiple storage accounts is currently NOT compatible with ServiceControl. Either multiple installations of ServiceControl for monitoring or the [ServiceControl transport adapter](/servicecontrol/transport-adapter.md) are required for these situations.

It is common for systems running on Azure Storage Queues to depend on a single storage account. However, there is a potential for throttling issues once the maximum number of concurrent requests to the storage account is reached, causing the storage service to respond with an [HTTP 503 Server Busy message](https://docs.microsoft.com/en-us/azure/media-services/media-services-encoding-error-codes). Multiple storage accounts can be used to overcome this.

## Azure Storage Scalability and Performance
![Scale out with multiple storage accounts](azure03.png "width=500")

All messages in a queue are accessed via a single queue partition. A single queue is targeted to process up to 2,000 messages per second. Scalability targets for storage accounts can vary based on the region, reaching up to 20,000 messages per second (throughput achieved using an object size of 1KB). This is subject to change and should be periodically verified.
To determine whether your system may benefit from scaling out to multiple storage accounts, refer to the the Scale targets table in the Azure [Scalability and performance targets for Queue Storage](https://learn.microsoft.com/en-us/azure/storage/queues/scalability-targets) article, which define when throttling starts to occur.

When the number of messages per second exceeds this quota, the storage service responds with an [HTTP 503 Server Busy message](https://docs.microsoft.com/en-us/azure/media-services/media-services-encoding-error-codes). This message indicates that the platform is throttling the queue. If a single storage account is unable to handle an application's request rate, the application could leverage several different storage accounts using a storage account per endpoint. This ensures application scalability without saturating a single storage account. This also gives a discrete control over queue processing, based on the sensitivity and priority of the messages that are handled by different endpoints. For example, high priority endpoints could have more dedicated workers than low priority endpoints.
For additional guidance on considerations when developing a system using Azure Storage Queues, see the article on [Performance and scalability checklist for Queue Storage](https://learn.microsoft.com/en-us/azure/storage/queues/storage-performance-checklist).

> [!NOTE]
> Using multiple storage accounts is currently NOT compatible with ServiceControl, it is necessary to use [ServiceControl transport adapter](/servicecontrol/transport-adapter.md) or multiple installations of ServiceControl for monitoring in such situation.
> Use real Azure storage accounts. Do not use Azure storage emulator as it only supports a single fixed account named "devstoreaccount1".

> [!NOTE]
> There are limits to how much increasing the number of storage accounts increases throughput. Consider using [scale units as part of a comprehensive scaling strategy](https://learn.microsoft.com/en-us/azure/well-architected/performance-efficiency/scale-partition#choose-a-scaling-strategy) to address higher throughput and reliability needs.

## Scaling Out
## NServiceBus routing with multiple storage accounts

A typical implementation uses a single storage account to send and receive messages. All endpoints are configured to receive and send messages using the same storage account.
The preferred way to route when using multiple accounts is to register endpoints with their associated storage accounts. This should be done using aliases in place of raw connection strings.

![Single storage account](azure01.png "width=500")
### Connection string aliases

When the number of instances of endpoints is increased, all endpoints continue reading and writing to the same storage account. Once the limit of 2,000 message/sec per queue or 20,000 message/sec per storage account is reached, Azure storage service throttles messages throughput.
Using distinct aliases for each storage account connection string prevents exposing sensitive data and is required when using multiple accounts.

![Single storage account with scaled out endpoints](azure02.png "width=500")
To enable sending from an endpoint using `account_A` to an endpoint using `account_B`, the following configuration needs to be applied in the `account_A` endpoint:

While an endpoint can only read from a single Azure storage account, it can send messages to multiple storage accounts. This way one can set up a solution using multiple storage accounts where each endpoint uses its own Azure storage account, thereby increasing message throughput.
snippet: AzureStorageQueueUseMultipleAccountAliasesInsteadOfConnectionStrings1

![Scale out with multiple storage accounts](azure03.png "width=500")
> [!NOTE]
> The examples above use different values for the default account aliases. Using the same name, such as `default`, to represent different storage accounts in different endpoints is highly discouraged as it introduces ambiguity in resolving addresses like `queue@default` and may cause issues when e.g. replying. In that case an address is interpreted as a reply address, the name `default` will point to a different connection string.

> [!NOTE]
> This feature is currently NOT compatible with ServiceControl. A [ServiceControl transport adapter](/servicecontrol/transport-adapter.md) is required in order to leverage both.

## Scale Units
### Using registered endpoints

Scaleout and splitting endpoints over multiple storage accounts work to a certain extent, but it cannot be applied infinitely while expecting throughput to increase linearly. Each resource and group of resources has certain throughput limitations.
In order to route a message to an endpoint without having to specify the destination each time, the endpoint can be registered for a given command type, assembly, or namespace.

A suitable technique to overcome this problem includes resource partitioning and usage of scale units. A scale unit is a set of resources with well determined throughput, where adding more resources to this unit does not result in increased throughput. When the scale unit is determined, to improve throughput more scale units can be created. Scale units do not share resources.
snippet: storage_account_routing_registered_endpoint

An example of a partitioned application with a different number of deployed scale units is an application deployed in various regions.
Once the endpoint is registered, no send options need to be specified.

![Scale units](azure04.png "width=500")
snippet: storage_account_routing_send_registered_endpoint

> [!NOTE]
> Use real Azure storage accounts. Do not use Azure storage emulator as it only supports a single fixed account named devstoreaccount1.".
### Using send options

Although registering endpoints to route messages is preferred, NServiceBus allows specifying destination addresses using the `<endpoint>@<physicallocation>` notation when messages are dispatched. Using this notation, it is possible to route messages to any endpoint hosted in any storage account. The `physicallocation` element represents the location where the endpoint's infrastructure is hosted, such as a storage account alias.

## Cross namespace routing
snippet: storage_account_routing_send_options_alias

NServiceBus allows to specify destination addresses using an `"endpoint@physicallocation"` when messages are dispatched, in various places such as the [Send](/nservicebus/messaging/send-a-message.md) and [Routing](/nservicebus/messaging/routing.md) API or the `MessageEndpointMappings`. In this notation the `physicallocation` section represents the location where the endpoint's infrastructure is hosted, such as a storage account.
### Publishers

Using this notation it is possible to route messages to any endpoint hosted in any storage account.
Similar to sending to an endpoint, the transport can also be configured to subscribe to events published by endpoints in another storage account, using:

partial: routing-send-options-full-connectionstring
snippet: storage_account_routing_registered_publisher

partial: aliases
Aliases can be provided for both the endpoint's connection strings as well as other accounts' connection strings. This enables using the `@` notation for destination addresses like `queue_name@accountAlias`.

partial: registered-endpoint
snippet: storage_account_routing_send_options_alias

This file was deleted.

Loading