| title | Snapshot |
|---|---|
| description | Snapshot is an important optimization mechanism in event sourcing architecture that improves performance by saving checkpoints of aggregate root state. |
Snapshot is an important optimization mechanism in event sourcing architecture that improves performance by saving checkpoints of aggregate root state to reduce the number of event replays.
In event sourcing, the state of an aggregate root is reconstructed by replaying all historical events. As the number of events increases, replaying all events becomes slower and slower. The snapshot mechanism solves this problem by periodically saving the current state of the aggregate root.
interface Snapshot<S : Any> : ReadOnlyStateAggregate<S>, SnapshotTimeCapable
data class SimpleSnapshot<S : Any>(
override val delegate: ReadOnlyStateAggregate<S>,
override val snapshotTime: Long = System.currentTimeMillis()
) : Snapshot<S>When loading an aggregate, the snapshot store is consulted first. If a snapshot exists, only events after the snapshot version need to be replayed.
sequenceDiagram
autonumber
participant CB as Command Bus
participant AG as Aggregate
participant SS as Snapshot Store
participant ES as Event Store
CB->>AG: Load Aggregate(id)
AG->>SS: Get Latest Snapshot(id)
alt Snapshot Found
SS-->>AG: Snapshot(v=50)
AG->>ES: Get Events After(v=50)
ES-->>AG: Events [51..55]
else No Snapshot
SS-->>AG: null
AG->>ES: Get All Events(id)
ES-->>AG: Events [1..55]
end
AG->>AG: Replay Events -> State
AG-->>CB: Aggregate Ready
Snapshot strategies determine when to create snapshots. The Wow framework provides multiple built-in strategies:
Creates a snapshot when the difference between the aggregate root version and the last snapshot version reaches a specified threshold.
class VersionOffsetSnapshotStrategy(
private val snapshotStore: SnapshotStore,
private val versionOffset: Int = 5
) : SnapshotStrategyCreates a snapshot for every state event.
class SimpleSnapshotStrategy(
private val snapshotStore: SnapshotStore
) : SnapshotStrategyDoes not create any snapshots.
object NoOp : SnapshotStrategy {
override fun <S : Any> shouldSnapshot(stateEvent: StateEvent<S>): Boolean = false
}stateDiagram-v2
[*] --> Create: Every N Events
Create --> Store: Serialize State
Store --> Active: Available for Loading
Active --> Stale: New Events Added
Stale --> Create: Interval Reached
Active --> Delete: Aggregate Deleted
Delete --> [*]
The snapshot store is responsible for storing and retrieving snapshots. Batch aggregate ID scanning belongs to EventStore.scanAggregateId(...), not to the snapshot store.
interface SnapshotStore : Named {
fun <S : Any> load(aggregateId: AggregateId): Mono<Snapshot<S>>
fun <S : Any> save(snapshot: Snapshot<S>): Mono<Void>
fun getVersion(aggregateId: AggregateId): Mono<Int>
}class InMemorySnapshotStore : SnapshotStore {
private val aggregateIdMapSnapshot = ConcurrentHashMap<AggregateId, String>()
override fun <S : Any> load(aggregateId: AggregateId): Mono<Snapshot<S>> =
Mono.fromCallable {
aggregateIdMapSnapshot[aggregateId]?.toObject<Snapshot<S>>()
}
override fun <S : Any> save(snapshot: Snapshot<S>): Mono<Void> =
Mono.fromRunnable {
aggregateIdMapSnapshot[snapshot.aggregateId] = snapshot.toJsonString()
}
}| Backend | Module | Status |
|---|---|---|
| MongoDB | wow-mongo |
Production-ready |
| Redis | wow-redis |
Production-ready |
- State Event Publishing: When aggregate root state changes, publish state events
- Strategy Evaluation: Snapshot strategy evaluates whether a snapshot needs to be created
- Snapshot Creation: If needed, create a snapshot of the current state
- Snapshot Storage: Save the snapshot to the snapshot store
wow:
eventsourcing:
snapshot:
enabled: true # Whether to enable snapshots
strategy: all # Snapshot strategy (all, version_offset)
version-offset: 5 # Version offset (only valid for version_offset strategy)| Property | Default | Description |
|---|---|---|
wow.snapshot.enabled |
false |
Enable snapshot store |
wow.snapshot.interval |
100 |
Events before new snapshot |
wow.snapshot.store.type |
Event store backend | Snapshot storage backend |
Snapshots greatly optimize aggregate root loading performance:
class EventSourcingOrderRepository(
private val eventStore: EventStore,
private val snapshotStore: SnapshotStore
) : OrderRepository {
override fun load(orderId: String): Mono<OrderState> {
val aggregateId = AggregateId("order", orderId)
return snapshotStore.load<OrderState>(aggregateId)
.flatMap { snapshot ->
// Only replay events after the snapshot version
eventStore.load(aggregateId, snapshot.version + 1)
.collectList()
.map { eventStreams ->
val state = snapshot.state
eventStreams.forEach { stream ->
stream.events.forEach { event ->
state.apply(event)
}
}
state
}
}
.switchIfEmpty(
// No snapshot, load all events
eventStore.load(aggregateId)
.collectList()
.map { eventStreams ->
val state = OrderState(orderId)
eventStreams.forEach { stream ->
stream.events.forEach { event ->
state.apply(event)
}
}
state
}
)
}
}- Snapshots Enabled: Aggregate loading time is proportional to snapshot interval, not total event count
- Snapshots Disabled: Every load requires replaying all historical events
- Storage Cost: Requires additional storage space to save snapshot data
With a snapshot interval of 50, an aggregate with 1000 events replays at most 49 events instead of all 1000 -- a ~95% reduction.
- Choose Appropriate Snapshot Strategy: Select appropriate snapshot frequency based on business scenarios
- Monitor Snapshot Effectiveness: Regularly check if snapshots significantly improve loading performance
- Snapshot Cleanup: Regularly clean up expired snapshots to save storage space
- Snapshot Consistency: Ensure snapshot version consistency with event streams