Deferring, snapshots, and passivation
An active aggregate has state in memory, but memory is not the source of truth. FCQRS can stop the actor, move its identity to another node, or restart the process and still rebuild the same state.
Deferring, snapshotting, and passivation make sense once their different jobs are separated:
- deferring returns an outcome without adding it to history;
- snapshotting creates a recovery checkpoint from durable history;
- passivation stops an actor and releases its in-memory state.
Only persisted events define recoverable aggregate state.
Three forms of state
It helps to distinguish three layers:
Layer |
Durable? |
Purpose |
|---|---|---|
Active actor state |
no |
decide the next command quickly |
Snapshot |
yes |
begin recovery from a later checkpoint |
Journal events |
yes |
record the facts from which state is derived |
The actor's state is a working copy. A snapshot is an optimization. The journal is the history that explains how the aggregate reached its state.
Motivation: Treating these layers as interchangeable creates recovery bugs. Memory may disappear, and snapshots may be missing or old; persisted events remain the evidence from which correct state must be reproducible.
Deferring returns an outcome without recording it
A command does not always create a new fact. Cancelling an already shipped order may need to return
OrderAlreadyShipped, but that verdict does not change the order.
DeferEvent follows this path:
|
FCQRS folds the deferred reply in the active actor, so its fold must preserve recoverable state. If a deferred reply changes state, that change exists only in memory and disappears after passivation or a restart.
Use a deferred reply for a rejection or idempotent verdict whose meaning is “nothing new happened.”
Persist the outcome instead when it must become part of history, change future decisions, or reach
journal projections. The CancelOrder decide/fold pair in
Aggregates and the write side shows the pattern: OrderAlreadyShipped and
AlreadyCancelled are deferred replies whose folds preserve state.
Snapshots shorten recovery
Without a snapshot, activating an aggregate replays every stored event from version 1. That produces the correct state, but a long history can make activation slow.
A snapshot stores the already-folded state and the event version it represents:
|
Both paths must produce identical state. A snapshot does not replace journal events, change business rules, or repair a non-deterministic fold. It changes recovery cost, not truth.
FCQRS provides three cadence policies on aggregate and saga definitions:
Defaultuses the configured interval, falling back to every 30 persisted versions;Every ncreates a checkpoint everynpersisted versions;NoSnapshotsstops creating new snapshots, although recovery can still use an older saved snapshot.
PersistAndSnapshot persists one event and requests an immediate checkpoint after it becomes durable.
Use it for a meaningful manual checkpoint, not as a substitute for choosing a measured cadence.
Snapshot more frequently when measured replay time makes activation too slow. Snapshot less often when histories are short or snapshot storage and serialization cost outweigh the replay saving. Test recovery both with and without a recent snapshot because later events are always replayed through the fold.
Passivation releases memory, not identity
Passivation is the Akka.NET term for stopping a sharded entity actor when it does not need to stay active. FCQRS aggregate actors may be passivated after the sharding system's configured idle period. Completed sagas also passivate because their workflow is finished.
Passivation does not delete the aggregate, its journal, or its snapshots. Callers still address the same aggregate id. When another command arrives, cluster sharding creates an actor for that identity, and FCQRS recovers its state before deciding the command.
|
This is why application code must not treat actor memory as permanent storage. Process restarts, node movement, and passivation all exercise the same recovery rules.
See the three mechanisms on one timeline
Consider this order history:
|
The deferred reply does not reappear during recovery, which is safe because its fold left the order unchanged. The snapshot avoids replaying v1 and v2. Passivation removes only the working state that can be reconstructed from the snapshot and journal.
What survives?
Thing |
Restart or passivation |
Reaches journal projections |
Changes persisted version |
|---|---|---|---|
Persisted event |
yes |
yes |
yes |
Deferred reply |
no |
no |
no |
Snapshot |
yes |
no |
no |
Active actor state alone |
no |
no |
no |
If future decisions depend on a value, make that value recoverable from persisted events. If the value only appears in a deferred fold or a mutable object held by the actor, it is not durable domain state.
Put it into practice
Chapter 1 of the tutorial introduces persisted and deferred actions. Chapter 2 demonstrates recovery after running the application again. Use Define an aggregate for the complete action table and Configuration for snapshot cadence and Akka.NET settings. Consistency and recovery places aggregate recovery beside projections, sagas, and external services.
FCQRS