Getting Started
Architecture
NServiceBus
Transports
Persistence
ServiceInsight
ServicePulse
ServiceControl
Monitoring
Samples

Azure Table Persistence

Target Version: NServiceBus 8.x

Certain features of NServiceBus require persistence to permanently store data. Among them are subscription storage, sagas, and outbox. Various storage options are available including Azure Table and Azure Cosmos DB Table API.

Azure Table Persistence stores NServiceBus data in Azure Table storage or Azure Cosmos DB using the Table API.

Persistence at a glance

For a description of each feature, see the persistence at a glance legend.

Feature
Supported storage typesSagas, Outbox, Subscriptions
TransactionsUsing TransactionalBatch, with caveats
Concurrency controlOptimistic concurrency
Scripted deploymentNot supported
InstallersSupported. Subscription, the default table or saga tables derived by convention when no default table is set are created at runtime, when enabled.

Enable Azure Table Persistence

First add a reference to the assembly that contains the Azure Table persistence, which is done by adding a NuGet package reference to NServiceBus.Persistence.AzureTable.

var persistence = endpointConfiguration.UsePersistence<AzureTablePersistence>();
persistence.ConnectionString("DefaultEndpointsProtocol=https;AccountName=[ACCOUNT];AccountKey=[KEY];");

Token-credentials

Enables usage of Microsoft Entra ID authentication such as managed identities for Azure resources instead of the shared secret in the connection string.

Use the corresponding TableServiceClient constructor overload when creating the client passed to the persistence.

Provisioned throughput rate-limiting with Azure Cosmos DB

When using the provisioned throughput feature, it is possible for the CosmosDB service to rate-limit usage, resulting in "request rate too large" StorageExceptions indicated by a 429 status code.

The Cosmos DB SDK provides a mechanism to automatically retry collection operations when rate-limiting occurs. Besides changing the provisioned RUs or switching to the serverless tier, those settings can be adjusted to help prevent messages from failing during spikes in message volume.

These settings may be set when initializing the CloudTableClient via the TableClientConfiguration.CosmosExecutorConfiguration MaxRetryAttemptsOnThrottledRequests and MaxRetryWaitTimeOnThrottledRequests properties:

endpointConfiguration.UsePersistence<AzureTablePersistence>()
    .UseCloudTableClient(storageAccount.CreateCloudTableClient(new TableClientConfiguration
    {
        CosmosExecutorConfiguration = new CosmosExecutorConfiguration
        {
            MaxRetryAttemptsOnThrottledRequests = 9,
            MaxRetryWaitTimeOnThrottledRequests = 30
        }
    }));

Transactions

The Azure Table persister supports using the Azure Table TableTransactionAction API. However, Azure Table only allows operations to be batched if all operations are performed within the same partition key. This is due to the distributed nature of the Azure Table service, which does not support distributed transactions.

The transactions documentation provides additional details on how to configure NServiceBus to resolve the incoming message to a specific partition key to take advantage of this Azure Table feature.

Saga concurrency

When simultaneously handling messages, conflicts may occur. See below for examples of the exceptions which are thrown. Saga concurrency explains how these conflicts are handled, and contains guidance for high-load scenarios.

Starting, updating or deleting saga data

Azure Table Persistence uses optimistic concurrency control when updating or deleting saga data.

Example exception:

Microsoft.WindowsAzure.Storage.StorageException: Element 0 in the batch returned an unexpected response code.

Request Information
RequestID:010c234e-3002-0145-06eb-72b85a000000
RequestDate:Tue, 24 Sep 2019 15:16:45 GMT
StatusMessage:The update condition specified in the request was not satisfied.
ErrorCode:
ErrorMessage:The update condition specified in the request was not satisfied.
RequestId:010c234e-3002-0145-06eb-72b85a000000
Time:2019-09-24T15:16:46.0746310Z

Supported saga properties' types

Azure Table Persistence supports the same set of types as Azure Table Storage and additional types that can be serialized into JSON using Json.NET. When a saga containing a property of an unsupported type is persisted, an exception containing the following information is thrown: The property type 'the_property_name' is not supported on Azure Table Storage and it cannot be serialized with JSON.NET.

Customization

Saga data serialization can be configured by providing a custom JsonSerializerSettings instance:

var persistence = endpointConfiguration.UsePersistence<AzureTablePersistence, StorageType.Sagas>();

persistence.JsonSettings(new JsonSerializerSettings
{
    Converters =
        {
            new IsoDateTimeConverter
            {
                DateTimeStyles = DateTimeStyles.RoundtripKind
            }
        }
});

or with a custom JsonReader instance:

var persistence = endpointConfiguration.UsePersistence<AzureTablePersistence, StorageType.Sagas>();

persistence.ReaderCreator(
    readerCreator: textReader =>
    {
        return new JsonTextReader(textReader);
    });

or with a custom JsonWriter instance:

var persistence = endpointConfiguration.UsePersistence<AzureTablePersistence, StorageType.Sagas>();

persistence.WriterCreator(
    writerCreator: writer =>
    {
        return new JsonTextWriter(writer)
        {
            Formatting = Formatting.None
        };
    });

Saga Correlation property restrictions

Saga correlation property values are subject to the underlying Azure Storage table PartitionKey and RowKey restrictions:

Samples

Related Articles