3.7 KiB
3.7 KiB
T2.5 — Checkpoints
| Field | Value |
|---|---|
| Phase | P2 — Durability hard parts |
| Size | M — 1 to 3 days |
| Status | Not started |
| Flags | — |
| Spec | inlined below |
| Blocks | — |
Goal
Materialized state snapshots tagged with their LSN, so restart folds forward from the newest one instead of from zero.
Facts (inlined — no spec read needed)
- A checkpoint is a materialized state snapshot tagged with its LSN. Restart folds forward from the newest one at or below the target LSN.
- An optimization only. Deleting every checkpoint costs startup time and nothing else. That property is the acceptance test, and it is what keeps checkpoints from quietly becoming a second source of truth.
- Port surface already exists from T0.5:
put_checkpoint(key, upto, state)andlatest_checkpoint(key, upto) -> Option<Checkpoint>.Nonemeans fold from LSN 0. - Checkpoints are per
BranchKey, like everything else in the log.
Steps
- Serialize the materialized state (T0.8) deterministically — the same ordered collections, so a checkpoint written twice from the same state is byte-equal.
- Write checkpoints on a policy: every N records or every M seconds per branch. Keep the policy in one place and make it configurable rather than scattered.
rebuild(key, upto): calllatest_checkpoint, deserialize if present, then fold records fromcheckpoint.lsn + 1. OnNone, fold from 0.- Never let the checkpoint be the only holder of a fact. Validate this by making
the
Nonepath the default in tests. - Add a prune path — old checkpoints for a branch are deletable at any time without coordination.
- Test both directions: with checkpoints and after deleting them all, comparing final state and reporting both startup times.
Acceptance
- Deleting all checkpoints changes startup time and nothing else — final state identical.
Verify
Harness: a run long enough to trigger the checkpoint policy several times — at least 3 checkpoints, so "newest at or below" is a real choice.
Integration test — tests/it_checkpoints.rs:
- Run to completion with checkpointing on. Record final state bytes and startup time for a rebuild.
- Delete every checkpoint row. Rebuild from LSN 0.
- Assert the final state bytes are identical; assert startup time differs (log both, do not assert a threshold — that is flaky).
latest_checkpoint(key, upto)withuptobetween two checkpoints: assert it returns the lower one, not the newest overall.- Determinism: write a checkpoint twice from the same state; assert byte equality of the two serializations.
- Crash between checkpoint write and subsequent commits; assert rebuild still lands on the same state.
- Prune: delete an older checkpoint while a newer exists; assert rebuild is unaffected.
Command: cargo test -p durability checkpoints
False pass:
- A run short enough to produce zero or one checkpoint. Then step 2 deletes nothing and the test is vacuous — assert the checkpoint count is ≥ 3 before deleting.
- Step 4 omitted: an implementation returning the globally newest checkpoint passes everything else and breaks the moment rewind or point-in-time rebuild needs an earlier LSN.
- Comparing state with
PartialEqrather than bytes.
Traps
- Writing a checkpoint inside the same transaction as the commit path and then depending on it for correctness. It is a cache; keep it separable.
- A checkpoint that serializes a
HashMap. Byte-identity fails intermittently and reads as flakiness.
Background (not required to do this task): rust-agentic-sys.md §7, §8.6 · rust-agentic-task.md