Skip to content

Fix the GC/sync lock key; record why stratum's GC differs - #40

Open
whilo wants to merge 3 commits into
mainfrom
feat/konserve-gc-guard
Open

Fix the GC/sync lock key; record why stratum's GC differs#40
whilo wants to merge 3 commits into
mainfrom
feat/konserve-gc-guard

Conversation

@whilo

@whilo whilo commented Aug 17, 2026

Copy link
Copy Markdown
Member

Two changes: a real bug in the GC/sync coordination lock, and a design note
recording why stratum's garbage collection differs from the rest of the stack —
written while landing konserve.gc-guard across konserve, scriptum, proximum
and stratum.

The lock did not lock

with-storage-lock is what keeps stratum's standalone gc! from sweeping
chunks a concurrent sync! is about to reference. It allocated its monitor in
a ConcurrentHashMap keyed by the store REFERENCE, on the stated reasoning
that

"File-store / S3-store records with content-based equality: two instances
configured against the same backing storage share a lock — which is what we
want (they protect the same physical data)."

They do not. Two connect-fs-store calls on one path are neither identical?
nor =, and hash differently:

identical? false    = false    same hash? false

So a process that connected twice to one store got two independent monitors and
no exclusion at all — measured at 0.16 ms of contention where the lock should
have blocked for 300 ms.

The companion claim, that "under-locking is impossible: distinct keys always
get distinct locks"
, had it exactly backwards. Two distinct keys for ONE
physical store is under-locking. The safe direction is the opposite, and it
is the same rule the GC safe point follows: the key may be COARSER than the
physical store — two stores collapsing onto one monitor merely over-locks — but
never FINER.

The lock now keys on konserve's :id, which is coarser (a logical identity,
shared by replicas across machines) and within one JVM names one store. Stores
built through a backend constructor carry no id and fall back to the object,
which is the old behaviour.

The regression test observes the monitor through the behaviour it exists for
rather than reaching into private state: a lock held on one connection must
exclude the other. It fails on the old key.

Why stratum's GC is not being changed

doc/gc-and-store-ownership.md records what the vertical established, because
the obvious next step — porting stratum to konserve.gc/sweep! for consistency
— would be a mistake as things stand.

Stratum is already correct on both its paths. Embedded, datahike opens
guard/writing! before the secondary flush (its comment names stratum) and
absorbs stratum's marks via sec/mark-from-key-map. Standalone, the monitor
serializes gc! against sync!.

And its GC design follows from a choice one level up. konserve.gc/sweep! is
allow-list — it deletes everything not whitelisted — which is only safe for a
store you own outright. Stratum SHARES datahike's store, so its explicit
three-family deletion is the adaptation that sharing forces, not an oversight.
Porting would trade a safe-by-construction design for one that depends on an
exhaustive whitelist, in the exact configuration where being wrong destroys
datahike's data.

The note lays out the two axes (collection mechanism vs store ownership), the
Model 1 / Model 2 trade-off that actually needs deciding, and a tripwire: if
scriptum's konserve backing ever moves into datahike's store, its
mark-from-key-map returning #{} becomes a data-loss bug.

Verification

1430 tests / 5866 assertions against the published konserve 0.9.375.

whilo added 3 commits August 16, 2026 23:24
`with-storage-lock` is what keeps stratum's standalone `gc!` from
sweeping chunks a concurrent `sync!` is about to reference. It allocated
its monitor in a ConcurrentHashMap keyed by the store REFERENCE, on the
stated reasoning that

  "File-store / S3-store records with content-based equality: two
   instances configured against the same backing storage share a lock —
   which is what we want (they protect the same physical data)."

They do not. Two `connect-fs-store` calls on one path are neither
`identical?` nor `=`, and hash differently:

  identical? false    = false    same hash? false

So a process that connected twice to one store got two independent
monitors and no exclusion at all — measured at 0.16ms of contention
where the lock should have blocked for 300ms.

The companion claim, that "under-locking is impossible: distinct keys
always get distinct locks", had it exactly backwards. Two distinct keys
for ONE physical store IS under-locking. The safe direction is the
opposite, and it is the same rule `konserve.gc-guard` follows for the
safe point: the key may be COARSER than the physical store — two stores
collapsing onto one monitor merely over-locks — but never FINER.

konserve's `:id` is coarser: a logical identity shared by replicas
across machines, and within one JVM (all a monitor can reach) one id
names one store. Stores built through a backend constructor carry no id
and fall back to the object, which is the old behaviour; such callers
should still avoid connecting twice.

The regression test observes the monitor through the behaviour it exists
for rather than reaching into private state: a lock held on one
connection must exclude the other. It fails on the old key.

Signed-off-by: Christian Weilbach <christian@weilbach.name>
…unify it

Companion to the lock fix. Records that stratum's GC design follows from
sharing datahike's store rather than owning its own, that both patterns
already exist in the stack, and that the open question is store ownership
rather than collection mechanism.

Signed-off-by: Christian Weilbach <christian@weilbach.name>
Carries `konserve.protocols/store-id`, which the GC/sync lock now keys on
(replikativ/konserve#159). Both pins bumped — the main dependency and the
`:release` override. The `:local` alias stays for co-development but is no
longer needed to build.

1430 tests / 5866 assertions against the published artifact.

Signed-off-by: Christian Weilbach <christian@weilbach.name>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant