Repository navigation
feat(storage): v8 covering keys, DICT and value pages (PR 3 of 5) - #449
Merged
Merged
Conversation
Records two deviations from the spec, to be amended in the same PR: ids, value refs and keys are assigned at checkpoint rather than at transact, and tx↓ is the complemented tx_count encoding rather than u64::MAX − tx_count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GgLEs8YozJjYhwPEYgTUn
FoundationDB-style integers, value tags in cross-type order, escaped short strings, long strings as prefix + hash + value ref, the FOREVER byte, and tx↓ as the complemented tx_count encoding. EAVT/AEVT/AVET/VAET key builders and decoders, and DICT keys for tags 0x01-0x06. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GgLEs8YozJjYhwPEYgTUn
Leaves store prefix-compressed entries with a restart point every 16 entries; internal nodes store shortest separators with a slot array for binary search. Both reject a page overflow with INT-049. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GgLEs8YozJjYhwPEYgTUn
build_btree, rebuild_btree_incremental, collect_leaf_pages, LeafCursor (with seek), prefix_scan and point get over (key, value) byte entries in the new node format. Leaves split by balanced bytes at cumulative-size boundaries; untouched leaves are copied and restamped. btree_v6.rs stays until its callers move over. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GgLEs8YozJjYhwPEYgTUn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GgLEs8YozJjYhwPEYgTUn
DictReader translates index entries back into facts through memoised DICT lookups. Encoder assigns eids and iids in pending order, records one timestamp per transaction (STG-037 on a conflict), deduplicates long values against committed and new ones, and builds sorted entries for all five trees. A missing DICT entry is STG-036. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GgLEs8YozJjYhwPEYgTUn
…pages (#433) save() and the v7 migration now encode facts into byte-comparable keys at checkpoint time (Encoder), write new long strings to value pages, and rebuild all five trees (EAVT, AEVT, AVET, VAET, DICT). The meta records the DICT root and the next eid/iid. Fact pages (0x41, now retired), FactRef, CommittedFactReader/CommittedIndexReader and btree_v6.rs are removed. Committed reads go through one CommittedReader (all facts, by entity, by entity and attribute, by attribute) that decodes index entries and translates ids through DICT; nothing but index leaves, DICT pages and long values is read. Pending indexes map to usize positions. The matcher's and optimizer's unused index plumbing is removed. WAL-003 now limits string values to 4068 bytes (one value page) and attributes/keywords to 1024 bytes, instead of the serialised fact size. Also carries the #447 fix (the tx counter never rewinds on WAL replay), which the new one-timestamp-per-transaction check (STG-037) exposed. Refs #433 #374 #434 #388 #447 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GgLEs8YozJjYhwPEYgTUn
#433) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GgLEs8YozJjYhwPEYgTUn
collect_leaf_pages validates a leaf's structure without copying keys, and the incremental rebuild reads a copied leaf's last key from its final restart point. A checkpoint after one fact at 100k facts drops from 516 ms to 43 ms (PR 2: 61 ms). Also: pre-covering-key v8 files are refused with STG-032, spec amendments, CHANGELOG, CLAUDE.md, ERROR_REFERENCE and TEST_COVERAGE for PR 3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GgLEs8YozJjYhwPEYgTUn
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This was referenced Oct 6, 2026
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.
Summary
PR 3 of 5 of the v8 storage format (spec
docs/superpowers/specs/2026-10-05-v8-storage-format-design.md, plan section "PR 3"). The four indexes become covering indexes: every entry is a whole fact encoded as a byte-comparable key. Entities and idents (attribute names and keyword values) become sequential ids held in a new DICT tree. Strings longer than 64 bytes are stored once in value pages. Fact pages,FactRefand both committed-reader traits are gone. The on-disk format freezes after this PR.save()is still a full rewrite (O(N)); PR 4 makes it O(change).Size: 100,000 facts (10 attributes per entity) take 88 bytes per fact, down from 314 on PR 2's format.
What changed
keys.rs: byte-comparable keys (spec §6).00 01marker,hash64(FNV-1a) and a value ref.vt = FOREVERis the single byte0xFF.tx↓is the complementedtx_countencoding (see deviations).e a v tx↓ vf vt op, AEVT, AVET, and VAET (refs only).node.rs: node codecs (spec §4.3).btree.rs(replacesbtree_v6.rs): the B+tree over(key, value)byte entries.LeafCursorwithseek,prefix_scanand pointget.value_pages.rs: append-only 0x51 pages. Each checkpoint starts a fresh page.dict.rs:DictReadertranslates entries back into facts, with lookups memoised per read.Encoderassigns eids and iids at checkpoint, in pending (WAL) order. It records onetx_idpertx_countand deduplicates long values against committed and new ones.reader.rs:OnDiskReaderimplements the singleCommittedReadertrait. It provides all facts, by entity, by entity and attribute, and by attribute: the only committed read paths the query layer uses.persistent_facts.rs:save()and the v7 migration share one encode pipeline and rebuild all five trees. The meta recordsdict_root,next_eidandnext_iid.FactStorage:usizepositions; only EAVT and AEVT remain.packed_pages.rskeeps only the v7 reader (plus a test-only v7 writer for fixtures). Page type 0x41 is retired.Deviations from the spec (spec amended in this PR)
tx↓is the byte complement of thetx_countencoding (§6.1). The encoding is prefix-free, so the complement reverses order exactly. It costs 2–4 bytes instead of 9.hash64 ‖ value refwith an empty value, so several long values can share a hash. DICT 0x03 holds raw ident bytes.Behaviour changes (CHANGELOG)
tx_countwith twotx_ids. A checkpoint or v7 upgrade refuses to alter a stored timestamp.Found along the way
The STG-037 check exposed #447, a v2.x data-integrity bug: reopening next to an already-checkpointed WAL rewound the transaction counter, so later transactions reused
tx_counts. This PR carries the same one-line fix and test as the v2.x fix (#448), so the nextmain→v3merge is clean. #445 (pending-slot overflow) was found while planning; its fix is merged intomain(#446). On v3 the code it touched is removed here.Tests
seek, page-read counting.cargo clippy --all-features -- -D warningsis clean.Benchmarks (PR 2 → PR 3, same machine, nothing else running)
checkpoint/after_1_fact/100kcheckpoint/after_1_fact/10kbtree_lookup/entity_point/{1k,10k,100k}open/checkpointed/*concurrent_btree_scan/100k_{2,4}tcollect_leaf_pagesvalidates structure without copying keys.btree_lookup/attribute_scan/100k(an in-memory database) reads 106 ms on PR 2 and 246 ms on PR 3, but the two do not do the same work. PR 2's branch still has Queries return wrong results with more than 65,535 uncheckpointed facts #445: past 65,535 pending facts, its attribute scan resolved most facts to one slot and returned far fewer rows. The flat profiles match, and at 10k (below the bug) the two are equal (23.3 vs 23.0 ms).Docs
Spec amendments (§5.1, §5.2 DICT table, §6.1, §6.2, §6.3, §12), the PR 3 plan, CHANGELOG, CLAUDE.md (module list, File Format, test count), ERROR_REFERENCE (STG-036, STG-037, WAL-003, STG-032) and TEST_COVERAGE.
Refs #433 #374 #434 #388
🤖 Generated with Claude Code
https://claude.ai/code/session_015GgLEs8YozJjYhwPEYgTUn