8000 refactor(btree): cursor-based scans with seek (v8 PR 1, refs #374) by adityamukho · Pull Request #442 · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content

refactor(btree): cursor-based scans with seek (v8 PR 1, refs #374) - #442

Merged
adityamukho merged 12 commits into
v3from
feat/v8-pr1-cursor
Oct 5, 2026
Merged

adityamukho merged 12 commits into
v3from
feat/v8-pr1-cursor

Conversation

@adityamukho
Copy link
Copy Markdown
Collaborator

First of five PRs for the v8 storage format: copy-on-write checkpoints, per-page checksums and covering indexes (#374, #434, #388, #433).

Contents

  • Spec: docs/superpowers/specs/2026-10-05-v8-storage-format-design.md, one spec for all five PRs.
  • Plan: docs/superpowers/plans/2026-10-05-v8-storage-format.md. PR 1 is planned in detail; PRs 2–5 are outlined.
  • Code (PR 1): every B+tree read goes through a new LeafCursor, which descends from the root and keeps the path as a stack. seek moves forward from the current position and climbs only to the lowest ancestor that covers the key. No read path follows leaf next_leaf links any more, because copy-on-write pages can't keep them. Writers still set them, and the file format and query results are unchanged.
    • range_scan and stream_all_entries use the cursor. collect_leaf_pages walks child pointers depth-first without decoding entries.
    • Separators are decoded on demand by binary search: O(log n) decodes per node instead of all of them.
    • A corrupt child pointer into a non-B+tree page is STG-013. A cycle in child pointers is INT-049 and never hangs.

Review focus

  1. A seek that crosses subtrees must land where a fresh descent from the root would: cursor_seek_matches_fresh_descent_random and cursor_seek_sequence_without_reset_visits_ascending_entries.
  2. A key in the gap between one leaf's last entry and the next leaf's first: cursor_seek_into_gap_after_leaf_end.
  3. A backward seek is a no-op: cursor_seek_backwards_is_noop.
  4. Corrupt child pointers: cursor_child_pointing_at_non_btree_page_returns_stg_013 and cursor_cycle_in_child_pointers_is_bounded.
  5. Seek cost: a seek to the sibling leaf reads exactly one page (cursor_seek_to_next_leaf_reads_one_page). Reads are unchanged with every next_leaf zeroed (range_scan_ignores_next_leaf).

Benchmarks (criterion, against v3)

bench change
concurrent_btree_scan/100k_{2,4,8}t −25 %, −37 %, −29 %
point_query_chain_depth/sibling_attr/{500,2000} −16 %, −29 %
checkpoint/after_1_fact/10k −9 %
checkpoint/after_1_fact/100k −2 % to 0 % over two re-runs (a first run read +17 %; that was noise)
btree_lookup/entity_point/{1k,100k} +2 %, +6 % (~0.4 µs)
btree_lookup/attribute_scan/* +2 % to +4 %

The query/* benchmarks use in-memory databases and don't touch this code.

Tests

Full suite: 1,217 passing, 8 ignored (+10). Lib clippy is clean. The clippy --all-targets lints are all in tests/*.rs files this PR doesn't touch; they were already on v3.

Refs #374 #434 #388 #433

🤖 Generated with Claude Code

https://claude.ai/code/session_015GgLEs8YozJjYhwPEYgTUn

adityamukho and others added 12 commits October 3, 2026 10:05
…ormat spec

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015GgLEs8YozJjYhwPEYgTUn
All B+tree reads go through LeafCursor, which descends with a stack of
decoded internal nodes instead of following leaf next_leaf pointers. A
copy-on-write tree (#374) cannot keep sibling pointers, so no read path
depends on them any more; writers still set them and the format is
unchanged.

- LeafCursor::new/next/advance_leaf/seek. seek moves forward from the
  current position: inside the current leaf it binary-searches; otherwise
  it climbs only to the lowest ancestor whose range covers the key.
- range_scan and stream_all_entries use the cursor. collect_leaf_pages
  walks child pointers depth-first without decoding entries.
- Corrupt child pointers: a non-B+tree page is STG-013; a cycle hits
  MAX_TREE_DEPTH (or the page-count visit bound) as INT-049, never a hang.
- find_leaf_for_key removed; find_leftmost_leaf is test-only.

Tests: cursor full scan and start positioning, seek against fresh descent
(random), monotone seek/next sequences, seek within leaf / to sibling /
across subtrees / past end / into the gap after a leaf / backwards,
one page read for a seek to the sibling leaf, reads ignore next_leaf,
cycle and wrong-page-type handling.

Refs #374 #434

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015GgLEs8YozJjYhwPEYgTUn
Descending through an internal node decoded every separator key; the
cursor now binary-searches and decodes O(log n) of them, and range_scan
borrows entries instead of cloning keys. Upper bounds are read from the
path instead of being cloned into each frame.

Against v3 (criterion): concurrent_btree_scan/100k -25..-37 %,
point_query_chain_depth/sibling_attr/2000 -29 %, checkpoint/after_1_fact/10k
-9 %; btree_lookup/entity_point and attribute_scan +2..6 % (~0.4 us).

Docs: CHANGELOG (Internal), CLAUDE.md and TEST_COVERAGE.md test counts
(1,217 passing, 8 ignored).

Refs #374

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015GgLEs8YozJjYhwPEYgTUn
@codecov
codecov Bot commented Oct 5, 2026
Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.76923% with 12 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/storage/btree_v6.rs 90.76% 12 Missing ⚠️

📢 Thoughts on this report? Let us know!

@adityamukho
adityamukho merged commit a99b915 into v3 Oct 5, 2026
18 checks passed
@adityamukho
adityamukho deleted the feat/v8-pr1-cursor branch October 5, 2026 17:48
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

0