Skip to content

Azure Storage grain persistence

The Microsoft.Orleans.Persistence.AzureStorage package contains providers for Azure Table Storage and Azure Blob Storage. Both implement optimistic concurrency using storage ETags.

Table storage keeps one state record in an entity and splits serialized state across properties when needed. Azure Table Storage limits an entity to 1 MiB, so use Blob Storage or another provider for larger records.

Configure a named provider with AddAzureTableGrainStorage. Token credentials are preferred over secrets:

using Azure.Data.Tables;
using Azure.Identity;
siloBuilder.AddAzureTableGrainStorage(
"profileStore",
options => options.TableServiceClient = new TableServiceClient(
new Uri("https://account.table.core.windows.net"),
new DefaultAzureCredential()));

Assign the storage account identity the data-plane permissions required to read and write table entities.

Blob storage keeps each state record in a blob and is appropriate when state can exceed the Table Storage entity limit:

using Azure.Identity;
using Azure.Storage.Blobs;
siloBuilder.AddAzureBlobGrainStorage(
"cartStore",
options => options.BlobServiceClient = new BlobServiceClient(
new Uri("https://account.blob.core.windows.net"),
new DefaultAzureCredential()));

Assign the storage account identity the data-plane permissions required to read and write blobs.

Connection strings are useful for local emulators and constrained environments, but contain secrets and shouldn’t be committed:

var builder = Host.CreateApplicationBuilder();
builder.UseOrleans(siloBuilder =>
{
siloBuilder.AddAzureTableGrainStorage(
name: "profileStore",
configureOptions: options =>
{
options.TableServiceClient = new TableServiceClient(
"DefaultEndpointsProtocol=https;AccountName=data1;AccountKey=SOMETHING1");
})
.AddAzureBlobGrainStorage(
name: "cartStore",
configureOptions: options =>
{
options.BlobServiceClient = new BlobServiceClient(
"DefaultEndpointsProtocol=https;AccountName=data2;AccountKey=SOMETHING2");
});
});
using var host = builder.Build();

Use a secret store when a connection string is unavoidable.

  • Use separate provider names when records belong in different accounts, containers, or tables.
  • Treat an InconsistentStateException as an optimistic-concurrency conflict, not a transient timeout.
  • Configure storage redundancy and account failover according to the application’s durability requirements.
  • Test record-size limits using serialized production-shaped state.