Skip to main content
← Back to list
01Issue
FeatureShippedSwamp CLIPublic
Assigneesstack72

Relationships

#3258 Datastore rework Phase 3: history compaction so superseded chunks can be reclaimed, dormant (no behaviour change)

Opened by stack72 · 10/11/2026· Shipped 10/12/2026

Background (read this first)

swamp is reworking its datastore layer (tracking: swamp-club#2865). In the target design, v3, every change is a commit in a single-writer SQLite commits.db, replicated by Litestream, and content over 8 KiB is stored as content-addressed chunks through storage/1. Phase 3 builds this dormant: nothing reaches it until a user opts a new, empty datastore into v3.

What's on main (897b64a0), documented in design/enablers/datastores.md:

  • The commit store (swamp-club#3236): commits.db keeps every commit of every partition, plus partition snapshots every 1,000 commits (Partition.snapshot()/restore(), aggregate state only: no content), and a store-wide change cursor.
  • The read model (swamp-club#3239):
    • each checkout catches up from its own cursor and applies each commit in turn, reading every put's content as it goes;
    • a fresh checkout replays the whole history from cursor 0;
    • it keeps a live-record index of present records and their BlobRefs (liveRecords, chunksInUse). That index keeps puts skipped for a path conflict, so their chunks count as live.
  • Chunk GC (swamp-club#3249). It deletes only chunks that no stored commit names, through the chunk_refs index written with each append, and keeps everything else, because a fresh catch-up needs every chunk any commit ever named. That includes content later overwritten, and versions removed by delete, data gc or prune. So storage grows with history: superseded content is never reclaimed.
    • Its race is closed by the sweep's re-check after it deletes, plus the writer's post-commit check.
    • One residual crash case is left to the Phase 4 outbox.

Goal

Let superseded content be reclaimed safely, by making catch-up stop depending on the full history. It stays dormant.

Design questions to settle first, recorded in datastores.md

  1. How a fresh or far-behind checkout catches up without old puts. For example:
    • a compaction point per partition (or store-wide): a materialisable state at seq N, meaning the present records with their BlobRefs or inline content, plus catalog state;
    • a checkout whose cursor is below the point loads that state, reconciling its files and _catalog_v3.db with it (removing what's no longer present), then replays only the commits after N;
    • a checkout already past N is unaffected.
  2. What happens to commits at or before a compaction point. Delete them from commits.db, or keep them for audit with their content no longer guaranteed? Either way:
    • chunk_refs must stop counting their superseded puts;
    • the partition's txnId duplicate rule must keep working for retries of old commits. See how #3236's snapshot priorTxn lookup handles txnIds before a snapshot;
    • Litestream replicates whatever you delete, so measure replica growth or shrinkage.
  3. The reference rule for GC after compaction: chunks named by compaction-point state, or by any commit after a point. Keep #3249's guarantees: the sweep's re-check and restore, and the writer's post-commit check. Adjust the referenced-chunk index so it tracks exactly this.
  4. Ordering and races: a checkout catching up while a compaction runs, and a GC sweep running around a compaction. No checkout may ever need a chunk that GC can delete.
  5. When compaction runs. Is it triggered by size or by commit count, and who runs it: the single writer (solo or the serve leader)? Wiring it into a command is the opt-in's job. Here it's a callable service.
  6. Run-record and audit needs. A run record naming name@version whose content was compacted away (#3250 already allows version reuse) has to resolve to "content no longer available", never to the wrong content. Say how.

Tests

  • Property test: for random histories, a checkout that catches up through compaction plus later commits ends with files, catalog and live index identical to one that replayed the full history before compaction.
  • A checkout behind a compaction point: it reconciles, removing records that are no longer present.
  • GC after compaction: superseded chunks are reclaimed, and present content is never deleted. #3249's race tests still pass unedited.
  • Retries: of commits at or before a compaction point, they still land once.
  • Crash safety: a crash mid-compaction leaves either the old state or the new one, never a mix.
  • Dormancy: extend integration/datastore_v3_rules_test.ts.

Out of scope

  • Wiring a command (the opt-in).
  • The Phase 4 outbox.
  • Litestream itself.

Done when

  • The decisions are recorded.
  • Compaction, compaction-aware catch-up and the adjusted GC reference rule are on main, dormant, with these tests.
  • All existing suites pass unchanged.

Blocked by: nothing (#3249 has merged). It can run in parallel with the two repository staging issues. Expect small merge conflicts in datastores.md and the rules test.

02Bog Flow
✓OPEN✓TRIAGED✓IN PROGRESS✓SHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 9 MOREPR_LINKED+ 2 MORESESSION_SUMMARIZED

Shipped

10/12/2026, 2:15:37 AM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack7210/12/2026, 12:54:30 AM

Sign in to post a ripple.