Skip to content

Implement BAAT for long queries#3378

Open
yingfeng wants to merge 6 commits into
infiniflow:mainfrom
yingfeng:baat
Open

Implement BAAT for long queries#3378
yingfeng wants to merge 6 commits into
infiniflow:mainfrom
yingfeng:baat

Conversation

@yingfeng

@yingfeng yingfeng commented Jun 4, 2026

Copy link
Copy Markdown
Member

What problem does this PR solve?

Algorithm Overview

  • IDF Ordering: Process rare terms (high IDF) first to quickly establish a top-k threshold.
  • Block-level Pruning: Use NextShallow() + BlockMaxBM25Score() to skip entire blocks when the block-level upper bound falls below the threshold.
  • Document-level Accumulation: For blocks that pass pruning, iterate through Next() + Score() to compute BM25 per document, accumulating results into an unordered_map<RowID, AccumEntry>.
  • Top-K Extraction: After all terms are processed, extract the top-k from the accumulator using a min-heap.
  • Adaptive Selection: In kAuto mode, BAAT is automatically triggered when the query contains ≥ 8 terms and the average DF exceeds 10% of the total document count.

Type of change

  • New Feature (non-breaking change which adds functionality)

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f00c2938-658a-45e6-9f58-05bc4486b4d8

📥 Commits

Reviewing files that changed from the base of the PR and between 34fef79 and d8186e0.

📒 Files selected for processing (4)
  • src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp
  • src/storage/invertedindex/search/blockmax_leaf_iterator.cppm
  • src/storage/invertedindex/search/phrase_doc_iterator.cppm
  • src/storage/invertedindex/search/term_doc_iterator.cppm
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/storage/invertedindex/search/phrase_doc_iterator.cppm
  • src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp

📝 Walkthrough

Walkthrough

Adds a BlockAtATimeIterator (BAAT) implementation and enum values, integrates BAAT selection into OR query creation with an eligibility heuristic, refactors iterator-selection switches to group BMW and BAAT, and exposes child overrides to force score thresholds.

Changes

Block-At-A-Time Iterator for Long OR Queries

Layer / File(s) Summary
Type Contracts and Enum Definitions
src/storage/invertedindex/search/doc_iterator.cppm
EarlyTermAlgo::kBlockAtATime and DocIteratorType::kBlockAtATimeIterator enum values enable BAAT algorithm selection and iterator identification.
BlockAtATimeIterator Class Declaration
src/storage/invertedindex/search/block_at_a_time_iterator.cppm
Declares BlockAtATimeIterator as a MultiDocIterator with constructor, destructor, override hooks for iteration/scoring, and internal state for BM25 parameters, per-document accumulators, top-k buffering, and helper methods.
Constructor, Allocation and Destructor
src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp
Constructor validates children, initializes BM25 upper bounds and iterate-cost estimates, optionally extracts SIMD params, conditionally allocates aligned SIMD buffers, sorts term order, and reserves heap; destructor frees buffers and logs counters.
Threshold Management and Top-k Heap
src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp
Implements monotonic UpdateScoreThreshold() that propagates to children, UpdateThresholdFromAccumulator() and AccumulateDoc() which maintain a running bounded heap to tighten pruning thresholds, and ExtractTopK() to materialize final top‑k.
Core BAAT Pipeline and Block Processing
src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp
Next() lazily runs the BAAT pipeline, ProcessTerm() traverses posting blocks with block-level pruning and dispatches to ProcessBlockSIMD() (SIMD batch decode) or scalar fallback, accumulating per-doc BM25 scores.
Accessors and Debug
src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp
BM25Score() and MatchCount() return cached/current-doc values; PrintTree() renders iterator children and threshold/topn debug info.
Query Execution Integration and Algorithm Selection
src/storage/invertedindex/search/query_node_impl.cpp
Adds block_at_a_time_iterator import, introduces term_children_need_baat eligibility check (topn>0, >=8 terms, avg term DF >10% of corpus), updates kAuto to select kBlockAtATime when eligible, and implements kBlockAtATime branch constructing BlockAtATimeIterator.
Iterator Selection Switch Statement Refactoring
src/executor/operator/physical_match_impl.cpp
Groups EarlyTermAlgo::kBMW with EarlyTermAlgo::kBlockAtATime in switch statements so both algorithms execute the same iterator-initialization branch with consistent scoping.
Child Iterator Threshold Control
src/storage/invertedindex/search/blockmax_leaf_iterator.cppm, src/storage/invertedindex/search/term_doc_iterator.cppm, src/storage/invertedindex/search/phrase_doc_iterator.cppm
Adds ForceSetScoreThreshold(float) pure virtual to BlockMaxLeafIterator and overrides in TermDocIterator and PhraseDocIterator to unconditionally set internal thresholds for BAAT traversal.

Sequence Diagram

sequenceDiagram
  participant OrQueryNode
  participant BlockAtATimeIterator
  participant BlockMaxLeafIterator
  participant AccumulatorTopK
  OrQueryNode->>BlockAtATimeIterator: construct(sub_doc_iters, topn)
  BlockAtATimeIterator->>BlockMaxLeafIterator: Reset / ForceSetScoreThreshold(0.0)
  BlockAtATimeIterator->>BlockMaxLeafIterator: Process posting blocks (SIMD or scalar)
  BlockMaxLeafIterator->>AccumulatorTopK: accumulate(doc_id, score, match_cnt)
  AccumulatorTopK->>BlockAtATimeIterator: update running top-k threshold
  BlockAtATimeIterator->>AccumulatorTopK: ExtractTopK() -> topk_results
  BlockAtATimeIterator->>OrQueryNode: return next doc >= requested with score >= threshold
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🐰
I sift through blocks with nimble paws,
counting scores and clever laws.
Top-k carrots neatly found,
BAAT hops lightly, safe and sound.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description provides algorithm details and marks this as a new feature, but omits the 'Issue link' field and appears incomplete compared to the template requirements. Add the missing 'Issue link' field and ensure all sections of the template are addressed, even if marked as not applicable.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Implement BAAT for long queries' clearly and concisely summarizes the main feature addition, which is implementing the BAAT algorithm for handling long queries.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Infer (1.2.0)
src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp

src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp:15:1: error: a type specifier is required for all declarations
15 | module;
| ^
src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp:21:1: error: unknown type name 'module'
21 | module infinity_core:block_at_a_time_iterator.impl;
| ^
src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp:21:21: error: expected ';' after top level declarator
21 | module infinity_core:block_at_a_time_iterator.impl;
| ^
| ;
src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp:21:22: error: unknown type name 'block_at_a_time_iterator'
21 | module infinity_core:block_at_a_time_iterator.impl;
| ^
src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp:21:46: error: cannot use dot operator on a type
21 | module infinity_core:block_at_a_time_iterator.impl;
|

... [truncated 2200 characters] ...

stopping now [-ferror-limit=]
20 errors generated.
Error: the following clang command did not run successfully:
/opt/infer-linux-x86_64-v1.2.0/lib/infer/facebook-clang-plugins/clang/install/bin/clang-18
@/tmp/coderabbit-infer/d8186e08e4882395c065b4c739e7bb742b83b29d-927bb7c887c61de6/tmp/clang_command_.tmp.aa7033.txt
++Contents of '/tmp/coderabbit-infer/d8186e08e4882395c065b4c739e7bb742b83b29d-927bb7c887c61de6/tmp/clang_command_.tmp.aa7033.txt':
"-cc1" "-load"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../../facebook-clang-plugins/libtooling/build/FacebookClangPlugin.dylib"
"-add-plugin" "BiniouASTExporter" "-plugin-arg-BiniouASTExporter" "-"
"-plugin-arg-BiniouASTExporter" "PREPEND_CURRENT_DIR=1"
"-plugin-arg-BiniouASTExporter" "MAX_STRING_SIZE=65535


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp`:
- Around line 172-176: The current heuristic pruning (if (threshold_ > 0.0f &&
block_max_score <= threshold_ * 0.01f) { ... }) can drop documents that would
reach the threshold after other term accumulations; remove or replace it with a
provably-admissible upper bound check: compute a conservative block upper bound
by summing per-term maximum contributions for that block (use available
per-term/per-block max structures or add them), and only skip the block when
block_upper_bound <= threshold_. Update the code around variables
block_max_score, threshold_, target, block_last and the BlockAtATimeIteratorImpl
logic to use the summed per-term block maxima (or disable this heuristic) and
adjust total_blocks_skipped_ accounting accordingly.
- Around line 129-133: Next(RowID) currently advances using topk_cursor_ over
topk_results_ which are sorted by score, so entries with smaller doc IDs can be
skipped; change the logic in the Next(RowID) method to scan topk_results_ (not
just from topk_cursor_) and pick the smallest candidate_doc >= requested doc_id
with candidate_score > threshold_, update doc_id_ to that value, and mark that
entry as consumed (or remove it) so it won't be returned again; use the symbols
topk_results_, topk_cursor_, threshold_, doc_id_ and the Next(RowID) function to
locate and replace the linear consumption loop with a
full-scan-and-select-min-doc strategy (or alternatively sort a secondary index
by doc_id before seeking) so no valid hits are dropped.

In `@src/storage/invertedindex/search/query_node_impl.cpp`:
- Around line 686-690: Replace the release-only assert with a runtime
validation: in the EarlyTermAlgo::kBlockAtATime branch, check params.topn at
runtime and if it is 0 either throw a clear std::invalid_argument (mentioning
EarlyTermAlgo::kBlockAtATime and params.topn) or return a safe empty/no-op
iterator instead of constructing BlockAtATimeIterator; this prevents later
crashes in BlockAtATimeIterator::ExtractTopK when heap top is accessed with
topn==0.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1458e610-c9cc-441e-9e30-28667eeb3a0f

📥 Commits

Reviewing files that changed from the base of the PR and between 95fb008 and 32943ee.

📒 Files selected for processing (5)
  • src/executor/operator/physical_match_impl.cpp
  • src/storage/invertedindex/search/block_at_a_time_iterator.cppm
  • src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp
  • src/storage/invertedindex/search/doc_iterator.cppm
  • src/storage/invertedindex/search/query_node_impl.cpp

Comment thread src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp Outdated
Comment thread src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp Outdated
Comment thread src/storage/invertedindex/search/query_node_impl.cpp
@yingfeng

yingfeng commented Jun 4, 2026

Copy link
Copy Markdown
Member Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (2)
src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp (2)

172-179: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Return the selected top-k in doc-id order before exposing them via Next(RowID).

ExtractTopK() materializes topk_results_ in score order, but Next(RowID) consumes that vector with a monotonic cd >= doc_id scan. Once a higher doc ID is returned first, any remaining lower doc IDs are skipped permanently, so valid hits can disappear from the iterator.

Also applies to: 336-340

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp` around
lines 172 - 179, ExtractTopK() currently fills topk_results_ in score order, but
Next(RowID) iterates with a monotonic doc-id scan using topk_cursor_, causing
lower docIDs to be permanently skipped; fix by sorting topk_results_ by document
id (the cd field) in ascending order after materialization (and before
resetting/using topk_cursor_), and apply the same sort in the other
ExtractTopK-like code path referenced around the later block (lines 336-340);
ensure topk_cursor_ is reset to 0 after sorting so Next(RowID) returns hits in
doc-id order.

212-220: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The 10% block-pruning rule is not a safe upper bound.

block_max_score * 10.0f <= threshold_ can still prune documents that already accumulated most of threshold_ from earlier terms and only need a small contribution from this block. That makes the pruning lossy for exact top-k.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp` around
lines 212 - 220, The 10% heuristic (block_max_score * 10.0f <= threshold_) can
prune valid top-k candidates; remove or disable this unsafe rule and stop
skipping blocks based on that multiplication. Replace the condition in the
block-level pruning (the code using threshold_, block_max_score computed from
leaf->BlockMaxBM25Score(), and incrementing total_blocks_skipped_) with a
conservative no-op until a correct per-block accumulator upper-bound is
available — either remove the if-branch entirely or only prune when you can
provably guarantee no document in the block can reach threshold (i.e., use a
correct accumulated-prefix upper bound), and update total_blocks_skipped_ only
when pruning is safe.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp`:
- Around line 201-218: The loop currently calls leaf->Next(target) before
checking block-level pruning, which forces per-doc decode/seek even when a whole
block should be skipped; change the logic to advance the block cursor with
BlockMaxLeafIterator::NextShallow() (or call NextShallow() on the same leaf
type) to move to the next block and examine BlockMinPossibleDocID(),
BlockLastDocID(), and BlockMaxBM25Score() before decoding docs with
leaf->Next(target); only call leaf->Next(target) if the block passes the
block-level score/threshold check (use the existing threshold_ logic and
BlockMaxBM25Score() to decide), and preserve the same uses of target,
BlockLastDocID(), and BlockMinPossibleDocID() when entering a block.
- Around line 110-115: ProcessTerm currently attempts to disable child-side
thresholding by calling leaf->UpdateScoreThreshold(0.0f) but
TermDocIterator::UpdateScoreThreshold is monotonic so that won't lower an
already-increased threshold; change the fix by adding a non-monotonic API on the
child iterator (e.g., TermDocIterator::ForceSetScoreThreshold(float) or
TermDocIterator::ResetScoreThreshold()) that unconditionally sets or clears the
child threshold, then call that new API from BlockAtATimeIterator::ProcessTerm
(and the other spots noted around lines 193-195) instead of calling
UpdateScoreThreshold(0.0f); update callers to use the new Force/Reset method so
child-side thresholding can be disabled reliably.
- Around line 129-140: The code currently excludes documents whose score equals
threshold_ by using strict comparisons; change all comparisons that drop ties so
equal scores are kept: replace "entry.score <= threshold_" with "entry.score <
threshold_" and replace "entry.score > running_topk_heap_.top().second" with
"entry.score >= running_topk_heap_.top().second" (apply the same edits in the
other occurrences around the ExtractTopK() / heap rebuild logic where
accumulator_, running_topk_heap_, threshold_, and topn_ are used) so documents
that tie the kth score are not filtered out.

---

Duplicate comments:
In `@src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp`:
- Around line 172-179: ExtractTopK() currently fills topk_results_ in score
order, but Next(RowID) iterates with a monotonic doc-id scan using topk_cursor_,
causing lower docIDs to be permanently skipped; fix by sorting topk_results_ by
document id (the cd field) in ascending order after materialization (and before
resetting/using topk_cursor_), and apply the same sort in the other
ExtractTopK-like code path referenced around the later block (lines 336-340);
ensure topk_cursor_ is reset to 0 after sorting so Next(RowID) returns hits in
doc-id order.
- Around line 212-220: The 10% heuristic (block_max_score * 10.0f <= threshold_)
can prune valid top-k candidates; remove or disable this unsafe rule and stop
skipping blocks based on that multiplication. Replace the condition in the
block-level pruning (the code using threshold_, block_max_score computed from
leaf->BlockMaxBM25Score(), and incrementing total_blocks_skipped_) with a
conservative no-op until a correct per-block accumulator upper-bound is
available — either remove the if-branch entirely or only prune when you can
provably guarantee no document in the block can reach threshold (i.e., use a
correct accumulated-prefix upper bound), and update total_blocks_skipped_ only
when pruning is safe.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9e545eb5-0fa7-48f4-9dc0-c2c4eb0a0411

📥 Commits

Reviewing files that changed from the base of the PR and between 32943ee and 7f4e705.

📒 Files selected for processing (2)
  • src/storage/invertedindex/search/block_at_a_time_iterator.cppm
  • src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/storage/invertedindex/search/block_at_a_time_iterator.cppm

Comment thread src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp
Comment thread src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp
Comment thread src/storage/invertedindex/search/block_at_a_time_iterator_impl.cpp Outdated
@yingfeng

yingfeng commented Jun 4, 2026

Copy link
Copy Markdown
Member Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@yingfeng

yingfeng commented Jun 4, 2026

Copy link
Copy Markdown
Member Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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