perf(fts): add MAXSCORE for pure SHOULD queries - #8474
Conversation
There was a problem hiding this comment.
✅ Gate recommendation: approve.
The change closes the compound clause-level thresholding gap with conservative list-wide bounds and a dedicated pure-SHOULD MAXSCORE driver. Keeping it specialized is preferable to changing every sum disjunction: it preserves exact query-order scoring, inclusive ties, phrase confirmation, MUST_NOT ordering, and safe eager fallback for unsupported bounds.
5e4e9c3 to
86d7783
Compare
There was a problem hiding this comment.
✅ Gate recommendation: approve.
The change closes the compound clause-level thresholding gap with conservative list-wide bounds and a dedicated pure-SHOULD MAXSCORE driver. Keeping it specialized is preferable to changing every sum disjunction: it preserves exact query-order scoring, inclusive ties, phrase confirmation, MUST_NOT ordering, and safe eager fallback for unsupported bounds.
## What is the bug? Compound WAND score bounds sum non-negative clause maxima in f64 and round upward once. Exact scoring accumulates f32 values recursively, and a different clause order can produce a score one ULP above that bound. A competitive document can therefore be pruned incorrectly. ## How does this PR fix it? - Widen the f64 total with the existing clause-count upper-bound factor before converting to f32. - Round upward again when the f32 conversion rounds down. - Add deterministic bit-level regressions for query-order and reordered f32 accumulation. ## Stack This is 1/3 for OSS-1706: 1. lance-format#8473 - conservative score-sum bounds 2. lance-format#8474 - pure-SHOULD clause MAXSCORE 3. lance-format#8475 - metrics and end-to-end observability ## Validation - cargo fmt --all -- --check - cargo test -p lance-index scalar::inverted::wand::tests::conservative_score_sum_covers_query_order_f32_rounding --lib -- --exact - cargo clippy --all --tests --benches -- -D warnings
## What changed? This final stack layer exposes the pure-SHOULD MAXSCORE work in FTS execution metrics: - compound_should_skipped_windows - compound_should_bound_recomputations - compound_should_essential_evaluations - compound_should_non_essential_evaluations The collector is threaded recursively so nested eligible Boolean scorers report work. Counters are accumulated on the scorer hot path and flushed independently when the scorer is dropped. The end-to-end test verifies that production routing activates the optimization across four fragments and reports non-zero bound recomputations and essential evaluations. ## Stack This is 3/3 for OSS-1706 and is stacked on #8474, which is stacked on #8473. Review this PR relative to branch yang/oss-1706-pure-should-maxscore. This PR head has the exact source tree used for the 10M MMLB benchmark below. ## Benchmark Environment and protocol: GCP c4-standard-16 VM; 10,000,000 MMLB rows in 10 fragments; one reused 37,785,665,554-byte FTS index; 8 query workers; 64 GiB cache; position streams prewarmed. Results use a two-block ABBA run, four isolated process samples per build, 1,000 queries per case, and geometric means. QPS is higher-is-better; p50 latency is lower-is-better. | Scenario / metric | Baseline | This PR | Benefit | | --- | ---: | ---: | ---: | | SHOULD x3, k=10, QPS | 159.77 queries/s | 465.76 queries/s | 2.92x throughput | | SHOULD x3, k=10, p50 | 31.02 ms | 9.21 ms | 3.37x speedup | | SHOULD x3, k=100, QPS | 144.69 queries/s | 337.98 queries/s | 2.34x throughput | | SHOULD x3, k=100, p50 | 31.64 ms | 14.95 ms | 2.12x speedup | | SHOULD + Phrase, k=10, QPS | 320.42 queries/s | 885.60 queries/s | 2.76x throughput | | SHOULD + Phrase, k=10, p50 | 13.29 ms | 4.88 ms | 2.72x speedup | | SHOULD + Phrase, k=100, QPS | 221.36 queries/s | 462.28 queries/s | 2.09x throughput | | SHOULD + Phrase, k=100, p50 | 14.73 ms | 8.43 ms | 1.75x speedup | MUST controls remained within 0.99x-1.01x. The reused index hash was unchanged before and after the run. Exact-oracle checks passed 24/24 for each build, all 56,000 timed result signatures matched across builds, and there were no timed digest mismatches. The existing index_comparisons metric is a WandCursor leaf-candidate proxy rather than a low-level PostingIterator doc-id comparison count; no stronger claim is made for that metric. ## Validation - cargo fmt --all -- --check - cargo test -p lance-index pure_should_maxscore --lib -- --nocapture - cargo test -p lance io::exec::fts::tests::test_compound_should_metrics_are_counted_independently --lib -- --exact --nocapture - cargo test -p lance dataset::tests::dataset_index::test_pure_should_maxscore_is_exact_across_fragments --lib -- --exact --nocapture - cargo clippy --all --tests --benches -- -D warnings Completes OSS-1706.
What is the performance issue?
Pure Boolean SHOULD queries currently use an eager sum disjunction. A dense low-scoring clause can keep shallow windows small even when it cannot make a document competitive, so the scorer repeatedly probes candidates that a clause-level MAXSCORE split could defer.
How does this PR improve performance?
Stack
This is 2/3 for OSS-1706:
Review this PR relative to branch yang/oss-1706-score-bounds.
Benchmark
Measured on this exact PR head with the deterministic sparse-high-score plus dense-low-score unit canary: 1,025 documents, 10 SHOULD clauses, and k=1. Lower is better.
Candidate probes count successful next/advance operations on instrumented clause scorers. It is an operational posting-candidate proxy, not a count of low-level doc-id comparisons inside PostingIterator. The 10M MMLB ABBA benchmark is reported on the exact full-stack head in #8475.
Validation