feat(konserve): back an index with konserve, branch by manifest - #3
Open
whilo wants to merge 1 commit into
Open
Conversation
Konserve becomes the source of truth for a scriptum index and the local
directory becomes a derived cache that can be deleted at any time. This
is proximum's dual-storage model applied to Lucene, and Lucene fits it
better than vectors do: segment files are write-once, so a cached file
is valid forever and never needs invalidating.
The substantive change is where a branch LIVES. The path-based design
encodes a branch as `branches/<name>/` and enumerates branches with
newDirectoryStream, which makes the filesystem the branch registry — the
role konserve is supposed to hold. Here a branch is a manifest
{lucene-filename -> content-address} in the store, and each segment is a
blob keyed by its content hash.
Three properties follow, each with a test:
- forking copies a manifest; no bytes move
- branches sharing a segment share ONE blob, and locally one INODE,
because each branch view is hard links into a content-addressed
pool — so a shared segment costs disk and page cache once
- merging is branch-local: it writes new blobs under new addresses and
leaves the old ones for whoever still references them
That last property is why BranchAwareMergePolicy existed; the second is
what BranchedDirectory's base/overlay composition was for; and
reachability from the live manifests replaces BranchDeletionPolicy's
ref-counting. All three can go — a follow-up, since removing them is a
breaking API change that deserves its own review.
Concurrency needs no lock of our own. Lucene's write.lock lives in the
per-branch view, which IS scriptum's contract: a second writer on one
branch fails loudly, writers on different branches proceed in parallel.
Two regressions are pinned because both were live in earlier drafts.
A cache keyed only by filename let Lucene see another branch's files and
CONTINUE its index — branch B's durable manifest ended up containing
branch A's segments. And caching the manifest at construction left
openIfChanged permanently blind, which is precisely what a remote reader
polling a shared store depends on; listAll re-reads it now, so a reader
polls a small pointer and never re-reads immutable segment data.
sync is wrapped in konserve's gc-guard: it is exactly a
values-then-pointer sequence, and a collection landing between the blobs
and the manifest would sweep what the manifest is about to reference.
Needs konserve with gc-guard — unreleased, hence the :local alias.
gc! blocks on the sweep rather than returning its channel; returning it
unconsumed let a caller observe the store before the sweep had run.
Collection is eventual: stamps are millisecond-granular and the sweep
spares ties, and an explicit cutoff can only ever hold a collection back,
never hurry it, because the sweep clamps to min(cutoff, safe-point).
Member
Author
|
CI
With konserve carrying the guard (replikativ/konserve#159), the full suite is green locally: I deliberately did not paper over this with a Merge order: konserve#159 → konserve release → bump the |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Konserve becomes the source of truth for a scriptum index; the local directory becomes a derived cache that can be deleted at any time. This is proximum's dual-storage model (
proximum.vectors) applied to Lucene — and Lucene fits it better than vectors do, because segment files are write-once, so a cached file is valid forever and never needs invalidating.The substantive change: where a branch lives
The path-based design encodes a branch as
branches/<name>/and enumerates branches withnewDirectoryStream— which makes the filesystem the branch registry, the role konserve is supposed to hold. Here:Three properties follow, each with a test:
What this makes unnecessary
BranchAwareMergePolicyexistsBranchedDirectory's base/overlay composition was forBranchDeletionPolicy's ref-countingAll three can go. Not in this PR — removing them is a breaking API change that deserves its own review, and I would rather land the replacement with tests first and delete against a green baseline.
Concurrency: no lock of our own
Lucene's
write.locklives in the per-branch view directory, which is scriptum's contract. Tested: a second writer on one branch fails loudly withLockObtainFailedException, writers on different branches proceed in parallel.Two regressions pinned
Both were live in earlier drafts of this work and are worth knowing about:
listAllis manifest-driven now; the local directory is never authoritative.openIfChangedpermanently blind, which is exactly what a remote reader polling a shared store depends on.listAllre-reads it, so a reader polls a small mutable pointer and never re-reads immutable segment data.GC
syncis wrapped in konserve'sgc-guard— it is precisely a values-then-pointer sequence, and a collection landing between the blobs and the manifest would sweep what the manifest is about to reference.gc!blocks on the sweep rather than returning its channel; returning it unconsumed let a caller observe the store before the sweep had run (that cost me a debugging cycle). Collection is eventual: stamps are millisecond-granular and the sweep spares ties, and an explicit cutoff can only ever hold a collection back, never hurry it, since the sweep clamps tomin(cutoff, safe-point).Dependency
Needs konserve carrying
konserve.gc-guard(replikativ/konserve#159, unreleased) — hence the:localalias. Full suite green with it: 48 tests, 305 assertions.Known limitation
Whole-file materialization: a large merged segment must download completely before any read. Fine locally, and the S3 pain point to address next by porting proximum's chunking.