Snapshots vs Stories
A photograph freezes a moment. A diary records the journey. Both capture reality, but in different ways. The photograph shows where things are; the diary shows how they got there.
Systems face the same choice. Do we store snapshots (current state) or stories (event history)? Each approach has its wisdom.
The Snapshot Approach
Store the current state. Update it when things change.
storage:
current_game_state: { board: [...], turn: white, ... }
on event:
current_game_state = apply(current_game_state, event)
save(current_game_state) // overwrite the oldAdvantages:
- Fast reads: current state is immediately available
- Simple queries: "where is the white king?" is trivial
- Predictable storage: size doesn't grow with history
Disadvantages:
- History is lost: we can't see past states
- No undo: previous state is overwritten
- No audit trail: we can't answer "what happened?"
The Story Approach
Store events. Derive current state when needed.
storage:
events: [event_1, event_2, ..., event_n]
on event:
events.append(new_event) // never delete, only append
to get current state:
replay(events)Advantages:
- Full history: every past state is recoverable
- Natural undo: just replay fewer events
- Audit trail: complete record of what happened and when
- Debugging: can reproduce any historical scenario
Disadvantages:
- Slow reads: must replay to get current state (unless cached)
- Complex queries: "where is the white king?" requires replay
- Growing storage: events accumulate forever
The Trade-Off Space
Snapshot Story (Events)
-----------------------------------------------------
Read speed Fast Slow (replay)
Write speed Medium (update) Fast (append)
Storage Fixed Growing
History Lost Complete
Undo Hard Trivial
Audit None Built-in
Queries Easy ComplexNeither is universally better. The choice depends on:
- How important is history to your domain?
- How often do you read current state vs historical state?
- What kinds of queries do you need?
- How much storage can you afford?
The Hybrid: Events + Snapshots
Most production systems use both:
storage:
events: [all events, forever]
snapshots:
at_event_1000: state_at_1000
at_event_2000: state_at_2000
...
current_snapshot: latest_state // for fast readsWrite path (fast):
on event:
events.append(event)
current_snapshot = apply(current_snapshot, event)
if event_count divisible by 1000:
save_snapshot(current_snapshot, at: event_count)Read path (fast):
current state: return current_snapshot
historical state: find nearest snapshot before target
replay from thereThis gives us:
- Fast current-state access (from snapshot)
- Full history (from events)
- Reasonable historical access (from nearest snapshot + replay)
When to Choose What
Prefer snapshots when:
- History genuinely doesn't matter
- Queries on current state are complex and frequent
- Storage is severely constrained
- The domain is simple CRUD (create, read, update, delete)
Prefer events when:
Use both when:
- History is valuable (audit, compliance, debugging)
- Undo/redo is important
- You need to answer "what happened?" questions
- Multiple views of the same data exist (different read models)
- The domain is complex with many business rules
- You want event benefits but need fast current-state reads
- History matters but so does query performance
- You're building a serious production system
A Philosophical Note
The snapshot says: "This is what is."
The story says: "This is what happened."
The snapshot is a noun---a state of being. The story is a verb---a sequence of becoming. Both are valid ways to understand reality.
In our chess game:
A chess master studies both: the position to decide the next move, the history to understand how they got there.
- The snapshot shows the position. A frozen moment.
- The story shows the game. A living history.
So far, our history has been linear: one event after another, a single timeline. But what happens when we want to explore alternatives? When we ask "what if?" In the next chapter, we discover forking paths.