Skip to content

Time travel (snapshots)

timeline
  title One seed, many instants
  2026-01-01 : rows born so far
  2026-04-01 : some updated : some deleted
  2026-07-01 : full state

One seed already fixes a whole dataset. Snapshots make time another axis of that determinism: ask for the table as it stood at any instant, or for what changed between two.

synth snapshot -s orders.yaml --at 2026-01-01 -o jan.csv
synth snapshot -s orders.yaml --at 2026-07-01 -o jul.csv
synth snapshot -s orders.yaml --from 2026-01-01 --to 2026-07-01 -o changes.jsonl
tl, _ := synth.Snapshot[Order](synth.SnapshotConfig{Rows: 100_000, Churn: 2, DeleteFrac: 0.1})
jan := tl.At(jan1)
jul := tl.At(jul1)
events := tl.Between(jan1, jul1)

tl.Apply(jan, events)   // == jul, exactly

That last line is the contract, and it is the point: replaying the log over the earlier snapshot reproduces the later one. Migration and incremental-ETL tests need a source of truth on both ends and the diff between them, and here all three come from one seed.

Why it holds

The equivalence holds by construction rather than by luck. Each row's whole life — when it was born, when it changed, whether it was deleted — is derived from its index, and At and Between read that same life. Nothing is simulated forward, so a snapshot a century out costs no more than one an hour out. Consecutive ranges tile exactly, so you can walk a timeline in steps and land on the same state as one jump.

Instants

--at, --from and --to take 2026-01-01 or full RFC 3339. --churn is the mean number of updates per row over the window.