Skip to content

Use durable state

Install the pre-release Microsoft.Orleans.Journaling package in the silo project. Install a journal storage provider and configure it before activating a DurableGrain.

All Journaling APIs are experimental and carry diagnostic ORLEANSEXP005.

Inject durable states with FromKeyedServicesAttribute. The service key becomes the state’s stable name in the grain journal:

public sealed class ShoppingCartGrain(
[FromKeyedServices("cart-items")]
Orleans.Journaling.IDurableDictionary<string, int> items)
: Orleans.Journaling.DurableGrain, IShoppingCartGrain
{
public async ValueTask AddItem(string itemId, int quantity)
{
items[itemId] = quantity;
await WriteStateAsync();
}
public ValueTask<IReadOnlyDictionary<string, int>> GetItems() =>
ValueTask.FromResult<IReadOnlyDictionary<string, int>>(
new Dictionary<string, int>(items));
}

The dictionary mutation is immediately visible to the current activation. Awaiting WriteStateAsync establishes the durability point for every pending durable-state mutation on that grain.

State typeIn-memory APIJournaled operations
IDurableValue<T>One mutable valueSet
IDurableDictionary<T, U>IDictionary<T, U>Set, remove, clear, snapshot
IDurableList<T>IList<T> plus AddRangeAdd, insert, set, remove, clear, snapshot
IDurableQueue<T>Queue operationsEnqueue, dequeue, clear, snapshot
IDurableSet<T>ISet<T>Add, remove, clear, snapshot
IDurableTaskCompletionSource<T>Durable task completionComplete, fault, or cancel
IPersistentState<T>Record-style stateSet or clear a versioned state value

All named states in one grain share the grain’s journal and participate in the same write. This makes a single WriteStateAsync the atomic storage boundary for their pending changes. Coordination with another grain or an external service requires an application protocol such as idempotency, an inbox, or an outbox.

An IDurableTaskCompletionSource<T> changes status in memory when TrySetResult, TrySetException, or TrySetCanceled succeeds. Its Task completes after a write acknowledges that status or recovery replays it, allowing waiters to observe a durable completion.

The keyed service name identifies a durable state across activations and deployments. Apply these rules:

  • Keep each name unique within the grain.
  • Preserve names when changing constructors or refactoring fields.
  • Keep JSON key, value, and record schemas backward readable during rolling upgrades.
  • Register every JSON payload type in the configured source-generated serializer context when trimming or using Native AOT.
  • Retain removed state definitions through the retirement grace period when a rollback can reintroduce them.

Registering two states with the same name fails activation. Registering a state after activation setup also fails because recovery has already assigned journal stream identities.

Journaling registers keyed IPersistentState<T> services. Its familiar State, WriteStateAsync, and ClearStateAsync members write through the same journal manager as the durable collections. ReadStateAsync completes from the already-recovered in-memory state because activation setup replayed the grain journal.

Use a unique keyed service name exactly as you would for another durable state. The ETag is the journal-backed state’s recovered version and RecordExists indicates whether a stored value is present.

Advanced integrations can implement IJournaledState and register it with IJournaledStateManager. The implementation owns its operation codec, snapshot representation, replay logic, deep-copy behavior, and volatile bookkeeping.

An implementation runs on one logical grain thread. It applies mutations in memory, writes recoverable operations, and uses OnWriteCompleted for behavior that must follow storage acknowledgement. Its Reset and replay methods must rebuild all state after a failed write or activation recovery.