fix: IndexCursor::seek does O(log n) tree descent instead of linear scan - #667
Merged
Conversation
iheitlager
force-pushed
the
fix/661-index-seek-tree-descent
branch
from
August 30, 2026 10:29
849d4d7 to
cf37525
Compare
…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
force-pushed
the
fix/661-index-seek-tree-descent
branch
from
August 30, 2026 10:33
6b46b4b to
7d975c3
Compare
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
IndexCursor::seek()(src/btree/index.rs) previously calledfirst()then walked forward vianext(), fullydecode_record-ing every candidate until finding one>= target: O(n) cells scanned and eagerly payload/overflow-reassembled per seek.TableCursor::seekalready binary-searches cell pointers per tree level;IndexCursornever 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'sstep, exactly mirroring how ordinaryfirst()/next()traversal would leave it), and falls back torightmostwhen no cell qualifies. A finaladvance()call resolves the right entry using this same stack state — the leaf match if there is one, or the nearest ancestor's fallback cell, orNone. This means only the O(log n) cells actually probed per level get decoded, and continuednext()calls behave identically to full-scan traversal.Test plan
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 confirmsnext()continues correctly from the seek position.secondary_index_seek_past_every_key_returns_noneandsecondary_index_seek_before_every_key_returns_first_rowfor the boundary cases.secondary_index_seek_matches_oracle,secondary_index_walk_matches_oracle_binary_order,secondary_index_last_prev_matches_reversed_forward_walkall still pass unchanged.cargo test --release— full suite green (979+ tests, 0 failed).cargo clippy --all-targets— clean.Closes #661
spend: ~1x estimate