Skip to content

perf(fts): prune low-scoring conjunction candidates#7386

Open
BubbleCal wants to merge 7 commits into
mainfrom
yang/fts-and-candidate-prune
Open

perf(fts): prune low-scoring conjunction candidates#7386
BubbleCal wants to merge 7 commits into
mainfrom
yang/fts-and-candidate-prune

Conversation

@BubbleCal

@BubbleCal BubbleCal commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Performance Improvement

What is the performance issue or bottleneck?

For FTS conjunction searches, once the top-k threshold is established, the AND path can still fully validate and score aligned candidate documents even when a cheap upper bound proves they cannot enter the heap. That pays for full BM25 scoring, phrase checks, and frequency collection for candidates that are already below the competitive threshold.

How does this PR improve performance?

This adds an AND-only score-first candidate prune in Wand::search. After all conjunction postings are aligned, the scorer first computes the exact contribution of one lead posting, then adds the remaining postings' current block-max scores as a safe upper bound. If that upper bound cannot beat the threshold, the candidate is skipped before phrase validation, full scoring, and term-frequency collection.

The change is intentionally narrow:

  • OR and flat-search paths are unchanged.
  • Missing-term and fuzzy AND semantics are unchanged.
  • The bound uses existing block-max scores, so exact top-k behavior is preserved for wand_factor == 1.0.
  • Phrase queries still use the prune only when the BM25 upper bound is already non-competitive.

Benchmark or measurement results

No end-to-end benchmark was run for this draft. The new regression coverage includes a counting scorer case that verifies low-scoring AND candidates avoid full scoring, plus a top-k correctness case that keeps a later high-scoring candidate.

Validation

  • cargo fmt --all --check
  • git diff --check
  • CARGO_TARGET_DIR=/tmp/lance-target-fts-and-prune-main cargo test -p lance-index scalar::inverted::wand::tests -- --nocapture
  • CARGO_TARGET_DIR=/tmp/lance-target-fts-and-prune-clippy cargo clippy --all --tests --benches -- -D warnings

@github-actions github-actions Bot added A-index Vector index, linalg, tokenizer performance labels Jun 22, 2026
@codecov

codecov Bot commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.63033% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rust/lance-index/src/scalar/inverted/wand.rs 97.36% 4 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@github-actions github-actions Bot added the A-python Python bindings label Jun 22, 2026
@BubbleCal BubbleCal marked this pull request as ready for review June 22, 2026 13:57

@Xuanwo Xuanwo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I did not find a blocking correctness, security, or concurrency issue in the AND prune itself.

The remaining test gap is that the new prune branch is only directly covered for the compressed posting path. Plain postings, phrase AND, fuzzy AND, mask/tombstone filtering, and exec/analyze-plan metric exposure are still not directly covered, so the unchanged-semantics claim is mostly carried by existing tests rather than targeted coverage for this branch.

.saturating_sub(pruned_before_return_start);
metrics.record_and_candidates_seen(and_candidates_seen);
metrics.record_and_candidates_pruned_before_return(and_candidates_pruned_before_return);
metrics.record_and_candidates_pruned_before_score(and_candidates_pruned_before_return);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

These two counters are recorded from the same value, while and_candidates_seen only counts candidates that returned from next(). Once exposed as plan metrics, these names can lead users to compute prune rates with the wrong denominator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-index Vector index, linalg, tokenizer A-python Python bindings performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants