Skip to content

fix: IndexCursor::seek does O(log n) tree descent instead of linear scan - #667

Merged
iheitlager merged 2 commits into
mainfrom
fix/661-index-seek-tree-descent
Aug 30, 2026
Merged

fix: IndexCursor::seek does O(log n) tree descent instead of linear scan#667
iheitlager merged 2 commits into
mainfrom
fix/661-index-seek-tree-descent

Conversation

@iheitlager

Copy link
Copy Markdown
Member

Summary

  • IndexCursor::seek() (src/btree/index.rs) previously called first() then walked forward via next(), fully decode_record-ing every candidate until finding one >= target: O(n) cells scanned and eagerly payload/overflow-reassembled per seek. TableCursor::seek already binary-searches cell pointers per tree level; IndexCursor never got the equivalent treatment.
  • seek() now descends the tree: at each level it binary-searches that page's cell array for the leftmost cell >= target, descends into that cell's left child (recording the cell as a fallback via the frame's step, exactly mirroring how ordinary first()/next() traversal would leave it), and falls back to rightmost when no cell qualifies. A final advance() call resolves the right entry using this same stack state — the leaf match if there is one, or the nearest ancestor's fallback cell, or None. This means only the O(log n) cells actually probed per level get decoded, and continued next() calls behave identically to full-scan traversal.

Test plan

  • Added secondary_index_seek_between_keys_matches_full_scan_and_next_continues — seeks a value between two stored keys (exercising the interior-cell fallback path), cross-checks against a full scan, and confirms next() continues correctly from the seek position.
  • Added secondary_index_seek_past_every_key_returns_none and secondary_index_seek_before_every_key_returns_first_row for the boundary cases.
  • Existing secondary_index_seek_matches_oracle, secondary_index_walk_matches_oracle_binary_order, secondary_index_last_prev_matches_reversed_forward_walk all still pass unchanged.
  • cargo test --release — full suite green (979+ tests, 0 failed).
  • cargo clippy --all-targets — clean.

Closes #661

spend: ~1x estimate

@iheitlager
iheitlager force-pushed the fix/661-index-seek-tree-descent branch from 849d4d7 to cf37525 Compare August 30, 2026 10:29
…ear scan (#661)

IndexCursor::seek() previously called first() then walked forward via
next(), fully decode_record-ing every candidate until finding one
>= target: O(n) per seek and O(n) eager payload/overflow reassembly.
TableCursor::seek already binary-searches cell pointers per level;
IndexCursor never got the equivalent treatment.

seek() now descends the tree, binary-searching each level's cell array
for the leftmost cell >= target and descending into that cell's left
child, recording the cell as a fallback via the frame's step (mirroring
how ordinary first()/next() traversal would leave it) in case the
child's subtree has nothing qualifying. A final advance() call resolves
to the right entry using this same stack state, so continued next()
calls behave identically to full-scan traversal. Only the O(log n)
cells actually probed per level get decoded/reassembled, not every
cell in the tree.

spend: ~1x estimate
@iheitlager
iheitlager force-pushed the fix/661-index-seek-tree-descent branch from 6b46b4b to 7d975c3 Compare August 30, 2026 10:33
@iheitlager
iheitlager merged commit 37c0d63 into main Aug 30, 2026
6 checks passed
@iheitlager
iheitlager deleted the fix/661-index-seek-tree-descent branch August 30, 2026 10:36
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.

perf: IndexCursor::seek does linear scan instead of binary-search tree descent

1 participant