DBRaven
Pattern · state management

Snapshot Pattern

established

Summary

Periodically capture the full materialized state of an event-sourced aggregate into a snapshot record, so that subsequent reads reconstruct the aggregate from the most recent snapshot plus a short tail of events: rather than replaying the entire event history from the beginning.

Problem

In event-sourced systems, aggregate load time grows proportionally to event history length. Aggregates with millions of events take minutes to load, making them operationally unusable in synchronous request paths. Snapshots bound the aggregate load time to O(events_since_last_snapshot) rather than O(total_events).

Description

Event sourcing stores state as an append-only log of events. Reconstructing current state requires replaying all events from the beginning of the log. For aggregates with long histories (thousands or millions of events), this replay becomes progressively slower: eventually taking seconds to load a single aggregate.

The snapshot pattern solves this by periodically writing a complete state checkpoint. After a snapshot is taken, only events after the snapshot's version need to be replayed: reconstruct(state) = apply_events(snapshot.state, events[snapshot.version + 1:]).

Snapshot frequency is tunable: snapshot every N events (e.g., every 1000), every N hours, or when a performance threshold is crossed. More frequent snapshots reduce replay time but increase write amplification. Less frequent snapshots reduce write overhead but allow replay time to grow between snapshots.

Snapshots coexist with the full event log: they are a read optimization, not a replacement for events. The event log remains the authoritative source; snapshots can always be regenerated by replaying. Old snapshots can be deleted when superseded; the events are retained for audit, time travel, and event projection needs.

Snapshots are commonly stored in the same event store (EventStore, DynamoDB, PostgreSQL) as a special event type, recording aggregate_id, version, state_bytes, and created_at. Loading with a snapshot means fetching the latest snapshot for the aggregate, deserializing its state if one exists, then loading and applying only the events after that snapshot's version; without a snapshot, loading falls back to the initial state plus every event from version 0.

Snapshot trigger policies are typically one of: every N events (snapshot when the current version is a multiple of N), time-based (snapshot every hour for active aggregates), or lazy (snapshot after loading, if replay time exceeded a threshold). Snapshot versioning stores a schema_version alongside the state bytes so deserialization can handle aggregate schema evolution; old snapshots must either remain deserializable under their original schema version or be migrated forward.

Tradeoffs

Load time bound
+0.8

Bounded aggregate load time regardless of total event history length

Long-lived aggregate support
+0.7

Enables long-lived aggregates (accounts, documents) without performance degradation over time

Non-destructiveness
+0.5

The event log is preserved; snapshots are an optimization layer, not a replacement

Write overhead
-0.2

Snapshot creation adds a write on top of event appends

Schema evolution burden
-0.3

Snapshot schema must evolve alongside aggregate schema; deserialization must handle all historical snapshot versions

New infrastructure dependency
-0.2

The snapshot store is a new infrastructure dependency with its own operational requirements

Correctness risk
-0.3

Incorrect snapshot logic produces incorrect state reconstruction, and such bugs are harder to detect than in full-replay mode

When to use

Event-sourced aggregates accumulate more than 1,000 events over their lifetime

Below 1,000 events, full replay is fast enough that snapshots add complexity for minimal benefit

Aggregate load time is approaching user-facing latency budgets

If aggregate loading is a bottleneck in request handling, snapshots are the correct structural fix

Aggregates are long-lived (accounts, documents, orders with years of history)

Short-lived aggregates never accumulate enough events to require snapshotting

When not to use

Event history is bounded by design (e.g., order processed and closed within 24 hours)

Short event histories replay instantly; snapshot overhead is not justified

System is write-once, never read for reconstruction (pure audit logs)

If aggregates are never replayed for state reconstruction, snapshots serve no purpose

Operational Requirements

mandatory

Monitor snapshot freshness

An aggregate with many events and no recent snapshot is a ticking performance degradation.

mandatory

Test snapshot round-trips

Serialize, deserialize, and compare against full-replay state.

mandatory

Implement snapshot migration scripts before deploying aggregate schema changes

Old snapshots must remain deserializable or be migrated forward.

recommended

Automate snapshot creation in the aggregate's command handler

Prefer this over an ad-hoc background job for predictable freshness.

Characteristics

Scales on
Implementation complexitymedium
Operational complexitymedium

Relationships

Complements

event sourcingcqrsmaterialized view

Basis

Snapshot pattern is a standard component of event sourcing architectures, documented in Vaughn Vernon's Implementing Domain-Driven Design, EventStore documentation, and Axon Framework documentation

Related Architecture Knowledge

Outbound: this entity affects

ComplementsPattern
event sourcing
Grounded

Snapshots are a performance optimization for event-sourced aggregates, providing bounded aggregate load time without changing the event sourcing model.

Full relationship →
MitigatesFailure Mode
read amplification
Draft · unverified

Snapshots bound the number of events that must be replayed to reconstruct aggregate state, reducing the read I/O required to serve aggregate loads compared to full event log replay.

Full relationship →

Used In Architecture Scenarios

Snapshot Pattern: DBRaven