Skip to content

perf: binary-search index leaf position instead of full-page decode - #651

Merged
iheitlager merged 1 commit into
mainfrom
perf/648-index-leaf-binary-search
Aug 29, 2026
Merged

perf: binary-search index leaf position instead of full-page decode#651
iheitlager merged 1 commit into
mainfrom
perf/648-index-leaf-binary-search

Conversation

@iheitlager

Copy link
Copy Markdown
Member

Summary

  • insert_into_index_leaf and delete_from_leaf each fully decoded every cell
    on an index leaf page (collect_index_leaf_cells) just to linear-scan for
    one insert/delete position, and decode_value_cell re-copied the entire
    page into a fresh Rc<[u8]> once per cell decoded rather than once per page.
  • Flamegraph profiling of tests/performance/crud.rs (triggered by
    benchmark results showing all write ops 5-11x slower than the SQLite
    oracle, with update_filtered_range at 85x) traced the shared bottleneck
    to this exact code path.

Changes

Changed

  • src/btree/index.rs: decode_value_cell now takes a shared Rc<[u8]>
    built once per page by its callers instead of building its own per cell.
    Added search_index_leaf — a binary search over an index leaf's sorted
    cells, decoding only the O(log n) cells actually compared — and
    value_cell_len, a cheap byte-length-only check (no key decode) for
    space bookkeeping.
  • src/btree/index/insert.rs: insert_into_index_leaf uses
    search_index_leaf for position/duplicate-check and a cheap length-only
    loop for the page-space check. Full collect_index_leaf_cells now only
    runs on the already-rare split/fragmented-fallback paths, which genuinely
    need every cell's contents.
  • src/btree/index/delete.rs: delete_from_leaf uses the same
    search_index_leaf swap — splice_delete_cell already recomputes the
    cell span from raw bytes by index, so only the matched cell's bytes (for
    overflow_page_of) are needed.

Not touched (intentionally out of scope, see #648 discussion)

  • IndexCursor::seek's read-path linear scan — a documented Tier 0 design
    tradeoff (index.rs:20-22), a separate concern.
  • Interior-page collection (collect_index_interior_entries, used in
    split-promotion/predecessor-swap) — gets the Rc-hoist fix but not a
    binary-search rewrite, since those call sites already need full page
    contents.

Testing

  • cargo test --lib — 959 passed, 0 failed
  • make test (unit, public-API, proptest, doctests) — all green
  • make lint (clippy + fmt) — clean
  • make -C tests/performance crud (criterion, sqlite-rs vs pinned oracle):
    • update_filtered_range (the reported 85x outlier): 666ms → 123ms (5.4x faster)
    • delete_pk: 8.7% faster; delete_filtered_range: 2.7% faster
    • insert_single, update_indexed_column, delete_equality_bucket: no
      change (within run-to-run noise) — expected, since single-row writes
      are dominated by fixed per-statement fsync/journal cost, not index
      decoding
    • No regressions found across the 15-scenario suite

spend: matched estimate (medium)

Related Issues

Closes #648


🤖 Generated with Claude Code

…648)

insert_into_index_leaf and delete_from_leaf each fully decoded every
cell on an index leaf page (collect_index_leaf_cells) just to linear-
scan for one position, and decode_value_cell re-copied the whole page
into a fresh Rc<[u8]> per cell decoded rather than once per page.

search_index_leaf replaces both call sites with a binary search that
decodes only the O(log n) cells actually compared; the full decode now
only runs on the already-rare split/fragmented-page fallback paths that
genuinely need every cell's contents. value_cell_len adds a cheap,
key-decode-free byte-length check for the insert space calculation.

update_filtered_range (tests/performance/crud.rs, the reported 85x-
slower-than-oracle outlier): 666ms -> 123ms. Other write scenarios:
no regression, most modestly faster.

spend: matched estimate (medium)
@iheitlager
iheitlager force-pushed the perf/648-index-leaf-binary-search branch from a093f63 to 472958e Compare August 29, 2026 20:41
@iheitlager
iheitlager merged commit 38e6970 into main Aug 29, 2026
6 checks passed
@iheitlager
iheitlager deleted the perf/648-index-leaf-binary-search branch August 29, 2026 20:47
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: index leaf/interior writes fully re-decode every cell instead of using existing splice fast path

1 participant