diff --git a/Snippets/ASQ/ASQN_10/Usage.cs b/Snippets/ASQ/ASQN_10/Usage.cs index 5359bdc270f..fbb4da7cc2b 100644 --- a/Snippets/ASQ/ASQN_10/Usage.cs +++ b/Snippets/ASQ/ASQN_10/Usage.cs @@ -70,9 +70,11 @@ void RegisterEndpoint(EndpointConfiguration configuration) var transportConfig = configuration.UseTransport(); 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"); diff --git a/Snippets/ASQ/ASQN_11/Usage.cs b/Snippets/ASQ/ASQN_11/Usage.cs index 9acf6f1d32a..dfe9fe58c3d 100644 --- a/Snippets/ASQ/ASQN_11/Usage.cs +++ b/Snippets/ASQ/ASQN_11/Usage.cs @@ -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"); diff --git a/Snippets/ASQ/ASQN_12/Usage.cs b/Snippets/ASQ/ASQN_12/Usage.cs index 8c74f4451c6..f01bf620e9e 100644 --- a/Snippets/ASQ/ASQN_12/Usage.cs +++ b/Snippets/ASQ/ASQN_12/Usage.cs @@ -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); diff --git a/Snippets/ASQ/ASQN_13/Usage.cs b/Snippets/ASQ/ASQN_13/Usage.cs index 8c74f4451c6..f01bf620e9e 100644 --- a/Snippets/ASQ/ASQN_13/Usage.cs +++ b/Snippets/ASQ/ASQN_13/Usage.cs @@ -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); diff --git a/Snippets/ASQ/ASQN_14/Usage.cs b/Snippets/ASQ/ASQN_14/Usage.cs index 8c74f4451c6..f01bf620e9e 100644 --- a/Snippets/ASQ/ASQN_14/Usage.cs +++ b/Snippets/ASQ/ASQN_14/Usage.cs @@ -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); diff --git a/Snippets/ASQ/ASQN_9/Usage.cs b/Snippets/ASQ/ASQN_9/Usage.cs index 10e1b767cb0..1e4c271c152 100644 --- a/Snippets/ASQ/ASQN_9/Usage.cs +++ b/Snippets/ASQ/ASQN_9/Usage.cs @@ -69,9 +69,11 @@ void RegisterEndpoint(EndpointConfiguration configuration) var transportConfig = configuration.UseTransport(); 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"); diff --git a/Snippets/ASQ/ASQ_8/Usage.cs b/Snippets/ASQ/ASQ_8/Usage.cs index 42be03abd56..34000d54cfb 100644 --- a/Snippets/ASQ/ASQ_8/Usage.cs +++ b/Snippets/ASQ/ASQ_8/Usage.cs @@ -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 @@ -67,9 +56,11 @@ void RegisterEndpoint(EndpointConfiguration configuration) var transportConfig = configuration.UseTransport(); 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"); diff --git a/transports/azure-storage-queues/azure01.png b/transports/azure-storage-queues/azure01.png deleted file mode 100644 index d21b8f646f4..00000000000 Binary files a/transports/azure-storage-queues/azure01.png and /dev/null differ diff --git a/transports/azure-storage-queues/azure02.png b/transports/azure-storage-queues/azure02.png deleted file mode 100644 index 6882a1bbda5..00000000000 Binary files a/transports/azure-storage-queues/azure02.png and /dev/null differ diff --git a/transports/azure-storage-queues/azure04.png b/transports/azure-storage-queues/azure04.png deleted file mode 100644 index c06e2abef4d..00000000000 Binary files a/transports/azure-storage-queues/azure04.png and /dev/null differ diff --git a/transports/azure-storage-queues/configuration_aliases_asq_[7,8].partial.md b/transports/azure-storage-queues/configuration_aliases_asq_[7,8].partial.md index dd868ed024a..66bab6f19cd 100644 --- a/transports/azure-storage-queues/configuration_aliases_asq_[7,8].partial.md +++ b/transports/azure-storage-queues/configuration_aliases_asq_[7,8].partial.md @@ -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. diff --git a/transports/azure-storage-queues/configuration_aliases_asqn_[9,).partial.md b/transports/azure-storage-queues/configuration_aliases_asqn_[9,).partial.md index 8d0b165be63..a18b7c2b745 100644 --- a/transports/azure-storage-queues/configuration_aliases_asqn_[9,).partial.md +++ b/transports/azure-storage-queues/configuration_aliases_asqn_[9,).partial.md @@ -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. \ No newline at end of file +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. diff --git a/transports/azure-storage-queues/multi-storageaccount-support.md b/transports/azure-storage-queues/multi-storageaccount-support.md index 3c81228eb10..65b812e69bf 100644 --- a/transports/azure-storage-queues/multi-storageaccount-support.md +++ b/transports/azure-storage-queues/multi-storageaccount-support.md @@ -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 @@ -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 `@` 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 diff --git a/transports/azure-storage-queues/multi-storageaccount-support_aliases_asq_[7,8].partial.md b/transports/azure-storage-queues/multi-storageaccount-support_aliases_asq_[7,8].partial.md deleted file mode 100644 index 0b0fcc488bb..00000000000 --- a/transports/azure-storage-queues/multi-storageaccount-support_aliases_asq_[7,8].partial.md +++ /dev/null @@ -1,17 +0,0 @@ -### Aliases instead of connection strings - -In order to prevent accidentally leaking connection string values, it is recommended to use aliases instead of raw connection strings. When applied, raw connection string values are replaced with registered aliases removing the possibility of leaking a connection string value. When using a single account, aliasing connection string is limited to just enabling it. When multiple accounts are used, an alias has to be registered for each storage account. - -To enable sending from `account_A` to `account_B`, following configuration has to be applied in the `account_A` endpoint - -snippet: AzureStorageQueueUseMultipleAccountAliasesInsteadOfConnectionStrings1 - -Aliases can be provided for both the endpoint's connection string as well as other accounts' connection strings. This enables using `@` notation for destination addresses `queue_name@accountAlias`. - -snippet: storage_account_routing_send_options_alias - -> [!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 NOT compatible with ServiceControl. A Consider the [ServiceControl transport adapter](/servicecontrol/transport-adapter.md) is required in order to leverage both. diff --git a/transports/azure-storage-queues/multi-storageaccount-support_aliases_asqn_[9,).partial.md b/transports/azure-storage-queues/multi-storageaccount-support_aliases_asqn_[9,).partial.md deleted file mode 100644 index 207406c6383..00000000000 --- a/transports/azure-storage-queues/multi-storageaccount-support_aliases_asqn_[9,).partial.md +++ /dev/null @@ -1,18 +0,0 @@ -### Aliases instead of connection strings - -To avoid connection strings leaking, aliases are always used, using an empty string as the default. -Therefore, when multiple accounts are used, an alias has to be registered for each storage account. - -To enable sending from `account_A` to `account_B`, the following configuration needs to be applied in the `account_A` endpoint: - -snippet: AzureStorageQueueUseMultipleAccountAliasesInsteadOfConnectionStrings1 - -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`. - -snippet: storage_account_routing_send_options_alias - -> [!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. diff --git a/transports/azure-storage-queues/multi-storageaccount-support_registered-endpoint_asq_[8,).partial.md b/transports/azure-storage-queues/multi-storageaccount-support_registered-endpoint_asq_[8,).partial.md deleted file mode 100644 index d595f52be9a..00000000000 --- a/transports/azure-storage-queues/multi-storageaccount-support_registered-endpoint_asq_[8,).partial.md +++ /dev/null @@ -1,15 +0,0 @@ -## Using registered endpoints - -In order to route message to endpoints without having to specify the destination at all times, it is also possible to register the endpoint for a given command type, assembly or namespace - -snippet: storage_account_routing_registered_endpoint - -Once the endpoint is registered no send options need to be specified. - -snippet: storage_account_routing_send_registered_endpoint - -### Publishers - -Similar to sending to an endpoint, the transport can also be configured to subscribe to events published by endpoints in another storage account, using: - -snippet: storage_account_routing_registered_publisher diff --git a/transports/azure-storage-queues/multi-storageaccount-support_registered-endpoint_asqn_[9,).partial.md b/transports/azure-storage-queues/multi-storageaccount-support_registered-endpoint_asqn_[9,).partial.md deleted file mode 100644 index d595f52be9a..00000000000 --- a/transports/azure-storage-queues/multi-storageaccount-support_registered-endpoint_asqn_[9,).partial.md +++ /dev/null @@ -1,15 +0,0 @@ -## Using registered endpoints - -In order to route message to endpoints without having to specify the destination at all times, it is also possible to register the endpoint for a given command type, assembly or namespace - -snippet: storage_account_routing_registered_endpoint - -Once the endpoint is registered no send options need to be specified. - -snippet: storage_account_routing_send_registered_endpoint - -### Publishers - -Similar to sending to an endpoint, the transport can also be configured to subscribe to events published by endpoints in another storage account, using: - -snippet: storage_account_routing_registered_publisher diff --git a/transports/azure-storage-queues/multi-storageaccount-support_routing-send-options-full-connectionstring_asq_[7,8].partial.md b/transports/azure-storage-queues/multi-storageaccount-support_routing-send-options-full-connectionstring_asq_[7,8].partial.md deleted file mode 100644 index cbed08e6ec7..00000000000 --- a/transports/azure-storage-queues/multi-storageaccount-support_routing-send-options-full-connectionstring_asq_[7,8].partial.md +++ /dev/null @@ -1,5 +0,0 @@ -### Using send options - -The use of send options enables routing messages to any endpoint hosted in another storage account by specifying the storage account using the `@` notation. - -snippet: storage_account_routing_send_options_full_connectionstring \ No newline at end of file diff --git a/transports/azure-storage-queues/multi-storageaccount-support_routing-send-options-full-connectionstring_asqn_[9,).partial.md b/transports/azure-storage-queues/multi-storageaccount-support_routing-send-options-full-connectionstring_asqn_[9,).partial.md deleted file mode 100644 index a314cdffd6a..00000000000 --- a/transports/azure-storage-queues/multi-storageaccount-support_routing-send-options-full-connectionstring_asqn_[9,).partial.md +++ /dev/null @@ -1,6 +0,0 @@ -### Using send options - -The use of send options enables routing messages to any endpoint hosted in another storage account by specifying the storage account using the `@` notation. -The `@` notation is used to point to a connection string represented by a specified alias. - -snippet: storage_account_routing_send_options_alias \ No newline at end of file