---
title: "Configuration"
url: "https://nsta1.github.io/Orleans.Lattice/docs/lattice.storage.azuretable/configuration.html"
source: "https://github.com/NSTA1/Orleans.Lattice/blob/release/9.9/docs/lattice.storage.azuretable/configuration.md"
package: "Orleans.Lattice.Storage.AzureTable"
version: "9.9.0"
documents: "Orleans.Lattice 9.9.0 (release line 9.9)"
built: "2026-10-04"
all-pages: "https://nsta1.github.io/Orleans.Lattice/llms.txt"
bundle: "https://nsta1.github.io/Orleans.Lattice/docs/lattice.storage.azuretable/llms-full.txt"
---
# Configuration

Part of the [Storage.AzureTable documentation](README.md).

This document covers `AzureTableWalStorageOptions`, the public configuration surface for `Orleans.Lattice.Storage.AzureTable`. Register the provider with `AddAzureTableWalStorage`; see [API Reference](api.md) for public types and [Architecture](architecture.md) for behavioural details.

## Registering the provider

```csharp verify
using Orleans.Lattice.Storage.AzureTable;

siloBuilder.AddAzureTableWalStorage(o =>
{
    o.ConnectionString = "UseDevelopmentStorage=true";
    o.TableName = "OrleansLatticeWal";
});
```

Each registration must configure **exactly one** authentication mode. The connection-string form above is one option; the three registrations below show the other mutually exclusive alternatives - pick whichever one matches your deployment, not more than one:

```csharp verify
using Azure.Core;
using Azure.Data.Tables;
using Orleans.Lattice.Storage.AzureTable;

TokenCredential tokenCredential = null!;
TableSharedKeyCredential sharedKeyCredential = null!;
TableServiceClient serviceClient = null!;

siloBuilder.AddAzureTableWalStorage(o =>
{
    o.ServiceUri = new Uri("https://account.table.core.windows.net");
    o.TokenCredential = tokenCredential;
});

siloBuilder.AddAzureTableWalStorage(o =>
{
    o.ServiceUri = new Uri("https://account.table.core.windows.net");
    o.SharedKeyCredential = sharedKeyCredential;
});

siloBuilder.AddAzureTableWalStorage(o =>
{
    o.ServiceClient = serviceClient;
});
```

## Options Reference - `AzureTableWalStorageOptions`

### Authentication and client

| Option | Type | Default |
|---|---|---|
| [`ConnectionString`](#connectionstring) | `string?` | `null` |
| [`ServiceUri`](#serviceuri) | `Uri?` | `null` |
| [`TokenCredential`](#tokencredential) | `TokenCredential?` | `null` |
| [`SharedKeyCredential`](#sharedkeycredential) | `TableSharedKeyCredential?` | `null` |
| [`ServiceClient`](#serviceclient) | `TableServiceClient?` | `null` |
| [`TableName`](#tablename) | `string` | `"OrleansLatticeWal"` |
| [`ConfigureClientOptions`](#configureclientoptions) | `Action<TableClientOptions>?` | `null` |

### Retry options

| Option | Type | Default |
|---|---|---|
| [`RetryMaxAttempts`](#retrymaxattempts) | `int?` | `null` |
| [`RetryDelay`](#retrydelay) | `TimeSpan?` | `null` |
| [`RetryMaxDelay`](#retrymaxdelay) | `TimeSpan?` | `null` |
| [`RetryNetworkTimeout`](#retrynetworktimeout) | `TimeSpan?` | `10 s` |
| [`RetryMode`](#retrymode) | `RetryMode?` | `null` |

### Commit pipeline options

| Option | Type | Default |
|---|---|---|
| [`PipelinePhaseTwoCommits`](#pipelinephasetwocommits) | `bool` | `true` |
| [`EliminateCandidateRowOnHotPath`](#eliminatecandidaterowonhotpath) | `bool` | `true` |
| [`PipelinedPhaseTwoFaultHandler`](#pipelinedphasetwofaulthandler) | `Action<Exception>?` | `null` |
| [`PhaseTwoCoalescingWindow`](#phasetwocoalescingwindow) | `TimeSpan` | 5 ms |
| [`PhaseTwoCommitTimeout`](#phasetwocommittimeout) | `TimeSpan?` | 12 seconds |
| [`PhaseOneTransientRetryMaxAttempts`](#phaseonetransientretrymaxattempts) | `int` | `2` |
| [`PhaseOneTransientRetryBaseDelay`](#phaseonetransientretrybasedelay) | `TimeSpan` | 25 ms |

### Saturation options

| Option | Type | Default |
|---|---|---|
| [`HonorSaturationSignal`](#honorsaturationsignal) | `bool` | `true` |
| [`SaturationShortCircuitCooldown`](#saturationshortcircuitcooldown) | `TimeSpan` | 2 seconds |

### Compression options

| Option | Type | Default |
|---|---|---|
| [`Compression`](#compression) | `LatticeCompression` | `Zstd` |
| [`CompressionMinPayloadBytes`](#compressionminpayloadbytes) | `int` | 256 |

Every non-null default above is also published as a public member of `AzureTableWalStorageOptions`: the constants `DefaultTableName`, `DefaultPipelinePhaseTwoCommits`, `DefaultEliminateCandidateRowOnHotPath`, `DefaultHonorSaturationSignal`, `DefaultPhaseOneTransientRetryMaxAttempts`, `DefaultCompression`, and `DefaultCompressionMinPayloadBytes`, and the `static readonly` `TimeSpan` fields `DefaultRetryNetworkTimeout`, `DefaultPhaseTwoCoalescingWindow`, `DefaultPhaseTwoCommitTimeout`, `DefaultSaturationShortCircuitCooldown`, and `DefaultPhaseOneTransientRetryBaseDelay`. `DefaultPhaseOneTransientRetryMaxDelay` (250 ms) is not an option's default but the fixed cap on the phase-1 retry backoff.

## Option guidance

### `ConnectionString`

Storage account connection string. Use this for Azurite, development, or deployments that manage secrets outside the Azure identity stack. Mutually exclusive with `ServiceUri`, credentials, and `ServiceClient`.

### `ServiceUri`

Azure Table service endpoint. Pair it with exactly one credential: `TokenCredential` or `SharedKeyCredential`.

### `TokenCredential`

Azure identity credential used with `ServiceUri`. This is the preferred production shape for managed identity or workload identity.

### `SharedKeyCredential`

Shared-key credential used with `ServiceUri`. Mutually exclusive with `TokenCredential`.

### `ServiceClient`

Pre-built `TableServiceClient` supplied by the host. When set, the provider uses it verbatim. `ConfigureClientOptions`, retry knobs, and provider-attached Azure SDK policies do not modify the supplied client; the host owns its options, pipeline, and lifetime.

### `TableName`

Azure Table name used for WAL storage. Defaults to `AzureTableWalStorageOptions.DefaultTableName`. The table is created on first use. Use distinct table names when sharing one storage account across independent Lattice deployments.

### `ConfigureClientOptions`

Callback used only when the provider constructs a `TableServiceClient`. Retry knobs are applied before this callback, so callback changes have final say. Add custom policies with `AddPolicy` rather than replacing the whole retry setup unless you intentionally want to own it.

### `RetryMaxAttempts`

Overrides the Azure SDK retry count after the initial attempt. `null` leaves the SDK default. `0` disables retries. Must be non-negative.

### `RetryDelay`

Overrides the Azure SDK base retry delay. `null` leaves the SDK default. Must be non-negative and must not exceed `RetryMaxDelay` when both are set.

### `RetryMaxDelay`

Overrides the Azure SDK maximum retry delay. `null` leaves the SDK default. Must be non-negative and at least `RetryDelay` when both are set.

### `RetryNetworkTimeout`

Overrides the Azure SDK per-attempt network timeout. Defaults to `10 s` - a finite bound below the WAL flush budget so a stuck request surfaces a fault into the WAL shard's failure handler (which releases and recovers the slot) instead of being abandoned while the transport zombies on for the SDK's unbounded ~100 s default. Set to `null` to restore the SDK default; must be positive when set. Prevents one stuck request from occupying a WAL slot - and, under a sustained storage brown-out, accumulating into hundreds of zombie attempts that self-sustain the brown-out.

### `RetryMode`

Overrides the Azure SDK retry mode. `null` leaves the SDK default, usually exponential backoff.

```csharp verify
using Azure.Core;
using Orleans.Lattice.Storage.AzureTable;

siloBuilder.AddAzureTableWalStorage(o =>
{
    o.ConnectionString = "UseDevelopmentStorage=true";
    o.RetryMaxAttempts = 2;
    o.RetryDelay = TimeSpan.FromMilliseconds(50);
    o.RetryMaxDelay = TimeSpan.FromSeconds(1);
    o.RetryNetworkTimeout = TimeSpan.FromSeconds(5);
    o.RetryMode = RetryMode.Exponential;
});
```

### `PipelinePhaseTwoCommits`

When `true`, the provider can return from an append after the durable entry write and after observing the previous pending completion for the same shard. Each completion commit still writes its pending batches in ascending offset order, and failures remain sticky to a later append or the configured fault handler. Set to `false` when you want every append to wait for its own completion before returning.

Because the read path is derived from commit metadata, this option introduces a bounded read visibility lag: the trailing batch on a shard is durable when the append returns but is not readable until its completion lands, so a read can omit it. The reported highest offset does not lag - `GetHighestOffsetAsync` folds the already-durable batches the shard's completion worker has accepted over the stored tail, so it never reports an offset lower than one a completed append returned. Callers that need read-after-write should await the provider's phase-two flush barrier rather than sleeping or polling; see [Architecture](architecture.md#read-visibility-lag-under-pipelining). Set the option to `false` if you would rather pay the latency on every append than take a barrier where you need one.

### `EliminateCandidateRowOnHotPath`

When `true`, the provider removes an extra recovery-marker write from the normal append path. Recovery still detects interrupted batches using stored batch metadata and the committed shard tail. Upgrade from `false` to `true` is safe because both recovery shapes are recognized. Before moving from `true` back to `false`, drain pending appends and let reconciliation complete on a `true` deployment.

### `PipelinedPhaseTwoFaultHandler`

Optional observer invoked once for every pipelined completion that faults, whether or not a successor append later observes the same fault, so a fault on a shard that goes idle before a successor append arrives still reaches the application. Because one fault can be reported both here and to that successor, the delegate should be idempotent and observability-only. Exceptions thrown by the delegate are ignored. It is never invoked when `PipelinePhaseTwoCommits` is `false`, because every append then observes its own completion.

```csharp verify
using Orleans.Lattice.Storage.AzureTable;

siloBuilder.AddAzureTableWalStorage(o =>
{
    o.ConnectionString = "UseDevelopmentStorage=true";
    o.PipelinedPhaseTwoFaultHandler = static ex => _ = ex.Message;
});
```

### `PhaseTwoCoalescingWindow`

Maximum wait after the first pending completion arrives so more completions can be coalesced into the same Azure Table transaction. Default is 5 ms. Must be non-negative. Use `TimeSpan.Zero` to commit as soon as work is available.

### `PhaseTwoCommitTimeout`

Per-commit deadline for the ordered completion transaction. Default is 12 seconds. Set to `null` to make completion unbounded, or set a positive `TimeSpan` tuned above the worst commit latency a browned-out account produces and below the silo-level timeout.

The deadline bounds how long the shard waits, not the transaction: on a timeout the waiting batches fail fast, but the abandoned transaction keeps running to a real outcome, because the service may already hold it and can still apply it. The shard's post-failure resync waits for it before reading the log, so the writer never resumes beneath rows that transaction then writes. A transaction still running 60 seconds after it was abandoned is cancelled so the wait stays bounded.

The 12-second default comes from multi-silo real-Azure measurement: one storage account under about 36k keys/s had a 6-9 second latency brown-out in which completion transactions took up to 6 seconds. The former 3-second default tripped on every shard at once and turned that brown-out into a failure storm; with no deadline the same run failed no keys. 12 seconds leaves 2x headroom over that worst case while staying below the 15-second WAL flush timeout and the silo request timeout, so a genuinely wedged shard is still broken.

```csharp verify
using Orleans.Lattice.Storage.AzureTable;

var options = new AzureTableWalStorageOptions
{
    ConnectionString = "UseDevelopmentStorage=true",
    PhaseTwoCommitTimeout = TimeSpan.FromSeconds(30),
};
```

### `PhaseOneTransientRetryMaxAttempts`

Number of additional in-place retries the provider issues when a phase-1 batch commit faults transiently. Each retry resubmits the byte-identical batch at the same offsets, so an already-durable prior attempt is resolved as an idempotent-replay success and a genuine offset collision still surfaces immediately without retry. Default is 2 (three attempts total); each retry increments `orleans.lattice.provider.phase1.transient_retries`. Set to 0 to surface every transient fault immediately. This knob is not validated: a negative value is treated as 0.

### `PhaseOneTransientRetryBaseDelay`

Base delay for the jittered backoff between phase-1 transient retries. The wait before the n-th retry (1-based) is a random value in `[0, BaseDelay * n)`, capped at 250 ms (`AzureTableWalStorageOptions.DefaultPhaseOneTransientRetryMaxDelay`), so multiple hot shards do not retry in lockstep. Default is 25 ms. Set to `TimeSpan.Zero` to retry without delay. This knob is not validated: a negative value also retries without delay.

### `HonorSaturationSignal`

When `true`, and when `IWalSaturationSignal` is registered, the provider attaches `SaturationAwareRetryPolicy` to clients it constructs. The first attempt reaches the network; retry attempts can short-circuit while aggregate WAL state is saturated. Set to `false` to leave Azure SDK retries unguarded.

### `SaturationShortCircuitCooldown`

Sticky window after the last saturated observation during which retry attempts continue to short-circuit. Default is 2 seconds. Must be non-negative. Set to `TimeSpan.Zero` to consult only the present aggregate state.

```csharp verify
using Orleans.Lattice.Storage.AzureTable;

var optionsWithCooldownOverride = new AzureTableWalStorageOptions
{
    ConnectionString = "UseDevelopmentStorage=true",
    HonorSaturationSignal = true,
    SaturationShortCircuitCooldown = TimeSpan.FromSeconds(5),
};
```

### `Compression`

Stored WAL payload compression algorithm. Default is `LatticeCompression.Zstd`; set `LatticeCompression.None` to store payloads verbatim. Rows are self-describing, so changing this option affects newly written rows while older rows continue to decode with their recorded tags.

Compression also decides whether a large entry fits. Each entry is stored in one binary property, which the Azure Table service limits to 64 KiB, measured on the stored bytes. With `LatticeCompression.None`, or for a payload compression cannot shrink, an entry fails as soon as its encoded record exceeds 64 KiB; a compressible one fits while its compressed form stays under the limit. See [Architecture](architecture.md#transactional-batch-contract).

### `CompressionMinPayloadBytes`

Minimum encoded payload size at which compression is attempted. Default is 256 bytes. Must be non-negative. Ignored when `Compression` is `LatticeCompression.None`.

```csharp verify
using Orleans.Lattice;
using Orleans.Lattice.Storage.AzureTable;

siloBuilder.AddAzureTableWalStorage(o =>
{
    o.ConnectionString = "UseDevelopmentStorage=true";
    o.Compression = LatticeCompression.None;
    o.CompressionMinPayloadBytes = 0;
});
```

## Validation

When the provider is constructed it rejects a negative `CompressionMinPayloadBytes` (`ArgumentOutOfRangeException`), a registered `ILatticeCompressor` that claims `LatticeCompression.None` or shares an algorithm tag with another registered compressor (`ArgumentException`), and a `Compression` algorithm other than `None` for which no `ILatticeCompressor` is registered (`InvalidOperationException`).

At first use - when it builds its Azure SDK client and creates the table - the provider validates the following, throwing `InvalidOperationException` on a violation:

- Exactly one authentication mode is configured.
- `TableName` is non-empty.
- Credential modes are mutually exclusive.
- Retry values are within valid ranges.
- `RetryDelay` does not exceed `RetryMaxDelay` when both are set.
- `PhaseTwoCoalescingWindow` and `SaturationShortCircuitCooldown` are non-negative.
- `PhaseTwoCommitTimeout` is positive when set.
- `CompressionMinPayloadBytes` is non-negative.

## See also

- [Core WAL Storage Providers](../lattice/wal-storage-providers.md) - provider seam and placement.
- [WAL tuning](../lattice/wal-tuning.md) - batch sizing and provider saturation envelope.
- [WAL saturation signal](../lattice/wal-saturation-signal.md) - saturation classifier used by retry short-circuiting.
