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

Relationships

#3531 Datastore rework Phase 4: a provider capability to remove abandoned Litestream lineage paths on buckets (replicaPaths), so chunk GC isn't deferred forever

Opened by stack72 · 10/15/2026· Shipped 10/15/2026

Background (read this first)

swamp is reworking its datastore layer (tracking: swamp-club#2865). In v3 on a bucket (Phase 4E), each checkout's commits.db is replicated by Litestream to <prefix>/_litestream/<fence>/, one path per lineage (swamp-club#3482, D2). A takeover from another checkout starts a new lineage on a new path, with a full snapshot. Lineages are published as control-plane records under _control/lineage/<fence>/.

What's shipped:

  • swamp-club#3507: the provider's replicaTarget(), which tells core where the replica lives;
  • swamp-club#3491: chunk GC on a replicated copy is deferred while any lineage other than the current one is registered, because a restore's truncation can leave chunks only an older lineage's head names;
  • D2: collects old lineages (paths, then records) only on a file replica. On S3 and GCS it keeps both, and the design record says "the paths stay until 4E collects them through storage/1".

The gap

storage/1 can't do that. It's chunk-keyed (<prefix>/_blobs/<namespace>/sha256/<hh>/<sha256>) and can't list or delete Litestream's objects under <prefix>/_litestream/<fence>/. So on buckets today:

  1. old lineage paths are never removed. Every takeover between checkouts leaves a full snapshot plus LTX files behind;
  2. their records stay too, so #3491's rule keeps chunk collection deferred forever on S3 and GCS. Chunks are never collected, and retention (swamp-club#3473) frees no chunk space.

This has to be in the extension contract before the extensions' replica release (swamp-club#3508), or Phase 4E needs a second extension release.

Design

  1. A small optional capability next to replicaTarget(): replicaPaths(): ReplicaPathStore, scoped to <prefix>/_litestream/:

    • list(signal): the fence directories present (<fence>/), so GC can find what nothing records any more;
    • removePath(fence, signal): deletes every object under <prefix>/_litestream/<fence>/, paging through listings, and succeeds when nothing is there. It must never touch anything outside that path. Validate fence as the 12-digit padded form core writes (replicaPathFor), and refuse anything else.
  2. Core: D2's lineage collection (collectV3Lineages, v3_copy.ts) uses it on S3 and GCS, with the same rules as the file replica:

    • only after the new lineage's first confirmed sync, once the lease check passed;
    • never the current lineage, or one started after it;
    • paths first, then records;
    • best effort, never failing a command.

    Once old lineages are collected, #3491's deferral lifts and chunk GC runs.

  3. Without the capability, behaviour is as today: paths and records are kept, and chunk GC stays deferred. Say so in the method's doc and in datastore-litestream.md, and fix the record's "through storage/1" wording.

  4. Conformance: extend assertReplicaTargetConformance, or add assertReplicaPathConformance, in packages/testing:

    • removePath deletes exactly one fence's objects and leaves a sibling fence and _blobs/ alone;
    • list sees what was written;
    • a malformed fence is refused.

    Run it on the in-memory remote, then publish @swamp-club/swamp-testing.

Tests

  • Unit tests for the core wiring with a fake store: which fences are removed, ordering, and best-effort failure.
  • Integration, dormant path:
    • a file replica keeps working as today;
    • with the in-memory remote's capability, a takeover's old lineage path and records are removed after the new lineage confirms, and chunk collection then runs.
  • The conformance suite.

Blocked by: nothing. swamp-club#3508 (the extensions' side) should implement replicaPaths() in the same release as replicaTarget(), so 4E needs only one extension release.

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

Shipped

10/15/2026, 11:38:53 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack7210/15/2026, 10:46:10 PM

Sign in to post a ripple.