Skip to content

[ENH] Add maxscore lazy cursor#6829

Open
HammadB wants to merge 3 commits intohammad/maxscore_writer_readerfrom
hammad/maxscore_lazy_cursor
Open

[ENH] Add maxscore lazy cursor#6829
HammadB wants to merge 3 commits intohammad/maxscore_writer_readerfrom
hammad/maxscore_lazy_cursor

Conversation

@HammadB
Copy link
Copy Markdown
Collaborator

@HammadB HammadB commented Apr 5, 2026

Description of changes

This is PR #3 of the BlockMaxMaxScore series, stacked on hammad/maxscore_writer_reader. It replaces the eager-only cursor with a tri-modal cursor and rewrites the query pipeline to use a 3-batch I/O strategy for reduced latency.

  • New functionality
    • Tri-modal PostingCursor (cursor.rs): Replaces the eager-only cursor from PR Test queries in nested parquet coco files #2 with three source modes:
      • Eager — fully deserialized SparsePostingBlocks (unchanged from PR Test queries in nested parquet coco files #2).
      • View — borrows raw &[u8] slices from the Arrow block cache. Offsets are decompressed into a reusable buffer; f16 weights are read directly from raw bytes on the fly (fused reads, no bulk conversion).
      • Lazy — blocks start as None, populated on demand via populate_from_cache / populate_all_from_cache. Same fused f16 read path as View once populated.
    • Blockstore raw access APIs: get_raw_from_cache (synchronous cache-only raw byte lookup), count_blocks_for_prefix, Block::get_raw / Block::get_prefix_raw, and ArrowReadableValue::get_raw_bytes trait method.
    • 3-batch I/O pipeline in query():
      • Batch 1: Load directory blocks for all query dimensions in parallel.
      • Batch 2: Small dimensions (≤2 Arrow blocks) get View cursors; large dimensions get Lazy cursors with essential blocks loaded in bulk.
      • Batch 3: After the threshold stabilizes, load remaining non-essential blocks with pruning — blocks where query_weight × max_weight ≤ threshold are skipped entirely.

Test plan

  • ms_12_cursor_view — View cursor vs Eager equivalence: advance, drain_essential, score_candidates, get_value, window_upper_bound, current.
  • ms_13_cursor_lazy — Lazy cursor via real blockfile I/O: advance, get_value, drain_essential.
  • ms_14_three_batch_pipeline — End-to-end 3-batch query: basic correctness, single dim, k > results, empty query, k=0, missing dim, masks, multi-block per dim.
  • ms_15_multi_window — Documents spanning 4096-entry window boundaries: boundary transitions, accumulator reset, many-docs-across-windows recall.
  • ms_16_lazy_partial_load — Lazy cursor with partially loaded blocks: advance skips unloaded, drain_essential skips unloaded, score_candidates skips unloaded.
  • Tests pass locally with cargo test

Migration plan

No migration needed. This changes the internal query execution strategy but does not alter blockfile formats or segment layouts.

Observability plan

No new instrumentation in this PR. Block-skip counters and batch timing will be added alongside segment integration.

Documentation Changes

No user-facing API changes. Internal design is documented in rust/index/src/sparse/maxscore.md.

Copy link
Copy Markdown
Collaborator Author

HammadB commented Apr 5, 2026

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 5, 2026

Reviewer Checklist

Please leverage this checklist to ensure your code review is thorough before approving

Testing, Bugs, Errors, Logs, Documentation

  • Can you think of any use case in which the code does not behave as intended? Have they been tested?
  • Can you think of any inputs or external events that could break the code? Is user input validated and safe? Have they been tested?
  • If appropriate, are there adequate property based tests?
  • If appropriate, are there adequate unit tests?
  • Should any logging, debugging, tracing information be added or removed?
  • Are error messages user-friendly?
  • Have all documentation changes needed been made?
  • Have all non-obvious changes been commented?

System Compatibility

  • Are there any potential impacts on other parts of the system or backward compatibility?
  • Does this change intersect with any items on our roadmap, and if so, is there a plan for fitting them together?

Quality

  • Is this code of a unexpectedly high quality (Readability, Modularity, Intuitiveness)

@HammadB HammadB mentioned this pull request Apr 5, 2026
2 tasks
propel-code-bot[bot]

This comment was marked as outdated.

@HammadB HammadB changed the base branch from hammad/maxscore_writer_reader to graphite-base/6829 April 6, 2026 21:56
Copy link
Copy Markdown
Contributor

@propel-code-bot propel-code-bot bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes are promising, but one important unsafe lifetime issue and code duplication should be addressed before merge.

Status: Changes Suggested | Risk: Medium

Issues Identified & Suggestions
  • Unsafe lifetime transmute may cause use-after-free; return owned bytes or Arc. rust/blockstore/src/arrow/blockfile.rs
  • Duplicate View/Lazy logic; extract shared raw-block helper to reduce drift. rust/index/src/sparse/cursor.rs
Review Details

📁 13 files reviewed | 💬 2 comments

👍 / 👎 individual comments to help improve reviews for you

Comment on lines +565 to +571
let target_block_id = self.root.sparse_index.get_target_block_id(&search_key);
let guard = self.loaded_blocks.read();
let block: &Block = guard.get(&target_block_id)?;
// Safety: the Block is heap-allocated (Box<Block>) and is never
// removed from loaded_blocks. The returned &[u8] points into
// Arrow buffers owned by the Block. Same lifetime-extension
// argument used in get_block's transmute.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

[Logic] The transmute here extends the lifetime of a &Block borrowed from inside the RwLockReadGuard to &'me Block, where 'me is the lifetime parameter of the reader. This is unsound: once the read guard drops at the end of this function, the underlying Block could theoretically be deallocated if a write guard is acquired elsewhere (e.g., if blocks were evicted from the cache), while the returned &[u8] still points into its Arrow buffers.

The comment says "Block is never removed from loaded_blocks", and the same invariant is relied upon by get_block's existing transmutes (lines 498, 513). However, this is a behavioral invariant, not a type-system invariant. This PR expands the unsafe surface area by adding a new function that relies on the same unverifiable guarantee. If that invariant ever changes (cache eviction, compaction, etc.), this becomes a use-after-free with no compiler warning.

A safer approach is to either:

  1. Return an owned Vec<u8> (a copy of the raw bytes), or
  2. Wrap Block in Arc<Block> so the caller can hold a reference-counted handle that prevents deallocation without needing the transmute.
Context for Agents
The `transmute` here extends the lifetime of a `&Block` borrowed from inside the `RwLockReadGuard` to `&'me Block`, where `'me` is the lifetime parameter of the reader. This is unsound: once the read guard drops at the end of this function, the underlying `Block` could theoretically be deallocated if a write guard is acquired elsewhere (e.g., if blocks were evicted from the cache), while the returned `&[u8]` still points into its Arrow buffers.

The comment says "Block is never removed from loaded_blocks", and the same invariant is relied upon by `get_block`'s existing transmutes (lines 498, 513). However, this is a behavioral invariant, not a type-system invariant. This PR expands the unsafe surface area by adding a new function that relies on the same unverifiable guarantee. If that invariant ever changes (cache eviction, compaction, etc.), this becomes a use-after-free with no compiler warning.

A safer approach is to either:
1. Return an owned `Vec<u8>` (a copy of the raw bytes), or
2. Wrap Block in `Arc<Block>` so the caller can hold a reference-counted handle that prevents deallocation without needing the transmute.

File: rust/blockstore/src/arrow/blockfile.rs
Line: 571

@HammadB HammadB force-pushed the hammad/maxscore_lazy_cursor branch from 8925c1a to 814fd3f Compare April 9, 2026 18:15
@HammadB HammadB force-pushed the graphite-base/6829 branch from 9d41c2f to 2fed177 Compare April 9, 2026 18:15
@HammadB HammadB changed the base branch from graphite-base/6829 to hammad/maxscore_writer_reader April 9, 2026 18:15
Copy link
Copy Markdown
Contributor

@propel-code-bot propel-code-bot bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 15 files for the maxscore lazy cursor enhancement; no issues were found and changes appear ready to merge.

Status: No Issues Found | Risk: Low

Review Details

📁 15 files reviewed | 💬 0 comments

@blacksmith-sh

This comment has been minimized.

@Sicheng-Pan Sicheng-Pan force-pushed the hammad/maxscore_lazy_cursor branch from 814fd3f to d3f0665 Compare April 9, 2026 19:13
@Sicheng-Pan Sicheng-Pan force-pushed the hammad/maxscore_writer_reader branch from 2fed177 to ffb0104 Compare April 9, 2026 19:13
@Sicheng-Pan Sicheng-Pan force-pushed the hammad/maxscore_lazy_cursor branch from d3f0665 to 756d94a Compare April 9, 2026 22:56
@Sicheng-Pan Sicheng-Pan force-pushed the hammad/maxscore_writer_reader branch from e71b3f4 to 6b0e4e6 Compare April 9, 2026 23:09
@Sicheng-Pan Sicheng-Pan force-pushed the hammad/maxscore_lazy_cursor branch 2 times, most recently from 2834ec4 to 30520f9 Compare April 10, 2026 00:08
@Sicheng-Pan Sicheng-Pan force-pushed the hammad/maxscore_writer_reader branch from 6b0e4e6 to d57413d Compare April 10, 2026 00:08
@Sicheng-Pan Sicheng-Pan changed the title lazy cursor perf [ENH] Add maxscore lazy cursor Apr 10, 2026
@Sicheng-Pan Sicheng-Pan force-pushed the hammad/maxscore_lazy_cursor branch from 30520f9 to 609b9b0 Compare April 10, 2026 01:19
@Sicheng-Pan Sicheng-Pan mentioned this pull request Apr 10, 2026
1 task
@Sicheng-Pan Sicheng-Pan marked this pull request as ready for review April 10, 2026 01:44
@propel-code-bot
Copy link
Copy Markdown
Contributor

propel-code-bot bot commented Apr 10, 2026

Add tri-modal PostingCursor and 3-batch MaxScore query pipeline

This PR introduces a new tri-modal cursor architecture for sparse maxscore search by moving PostingCursor into rust/index/src/sparse/cursor.rs and supporting Eager, View, and Lazy source modes. The new modes enable cache-backed raw-byte traversal, selective block population, and fused f16-to-f32 reads in hot paths (drain_essential and score_candidates) to reduce unnecessary deserialization and conversion work.

It also rewrites MaxScoreReader.query() in rust/index/src/sparse/maxscore.rs to a 3-batch I/O strategy: directory preload, essential-term data load, and threshold/pruning-driven non-essential lazy loads. Supporting raw cache access APIs were added in blockstore (get_raw_from_cache, count_blocks_for_prefix, Block::get_raw, Block::get_prefix_raw, ArrowReadableValue::get_raw_bytes), and a substantial new test suite validates cursor equivalence, lazy/partial-load behavior, window-boundary correctness, and end-to-end pipeline parity with brute-force scoring.

This summary was automatically generated by @propel-code-bot

@Sicheng-Pan Sicheng-Pan force-pushed the hammad/maxscore_lazy_cursor branch from b3e6c5a to fcca860 Compare April 10, 2026 03:11
@Sicheng-Pan Sicheng-Pan force-pushed the hammad/maxscore_writer_reader branch from d57413d to 9941a72 Compare April 10, 2026 03:11
@Sicheng-Pan Sicheng-Pan mentioned this pull request Apr 10, 2026
5 tasks
@Sicheng-Pan Sicheng-Pan force-pushed the hammad/maxscore_lazy_cursor branch from fcca860 to da68907 Compare April 10, 2026 03:26
@Sicheng-Pan Sicheng-Pan force-pushed the hammad/maxscore_writer_reader branch from 9941a72 to 0dec95c Compare April 10, 2026 03:26
@Sicheng-Pan Sicheng-Pan force-pushed the hammad/maxscore_lazy_cursor branch from da68907 to 59904b4 Compare April 10, 2026 17:18
@Sicheng-Pan Sicheng-Pan force-pushed the hammad/maxscore_lazy_cursor branch from 59904b4 to c76ce23 Compare April 10, 2026 20:11
@Sicheng-Pan Sicheng-Pan force-pushed the hammad/maxscore_writer_reader branch from 6ad7e5c to be9d78b Compare April 10, 2026 20:11
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.

2 participants