examples/live-notes Public
id: 0003-snapshots-in-r2
type: decision
owner: justin
reviewed: 2026-10-16
status: accepted
0003 Snapshots of every note in R2, every 100 versions
Context
A room keeps one row with the current text and a log of the last 200 ops, which is all the sequencer needs ([[0001-single-sequencer-per-note]]). Anything older is gone. When someone deletes half a note by accident and notices an hour later, there is nothing to go back to.
Keeping more in the Durable Object would work, but every note's history would then live inside its object: reading it means waking the room, and deleting the object deletes its history.
Decision
Every 100 versions, the room writes the note's text to an R2 bucket, live-notes-snapshots,
under snapshots/<object id>/<version>.txt. The write happens after the replies, and a failed
write is retried on the next edit. The Worker reads the bucket directly for
GET /notes/:id/snapshots. See [[snapshots]].
Consequences
- A note's history is readable without waking its room, and it is ordinary R2 objects that can be listed, copied or given a lifecycle rule.
- History has 100 version steps. What happened between two snapshots is not kept once it leaves the op log. A version is roughly a word of typing, so a step is a sentence or two.
- One R2 write per 100 edits per note. A note is at most 100,000 characters, so a thousand edits cost at most about a megabyte of storage, and a typical note far less.
- Nothing deletes snapshots yet. When that matters, an R2 lifecycle rule on the
snapshots/prefix is the place to do it, not the room. - Restoring from a snapshot is by hand for now: read the text, paste it into the editor. The paste is an ordinary edit, so everyone sees it and it is in the next snapshot.