Repository navigation
branch-4.2: [fix](inverted index) Count only visible rows in BM25 statistics of merge-on-write tables #69438 - #69752
Merged
yiguolei merged 1 commit intoOct 29, 2026
Conversation
…tistics of merge-on-write tables apache#69438 Backport of apache#69438 to branch-4.2. On a merge-on-write table, BM25 score() gave the same row a different score on each replica of its tablet: the statistics counted rows a later load overwrote or deleted (marked only in the delete bitmap) and rows written with the delete sign until compaction removed them, and each replica compacts on its own schedule. When the scan reads a delete bitmap, CollectionStatistics now describes only the rows the scan returns: per segment, the rows the delete bitmap marks at the read version and the rows with the delete sign (read only for segments whose zone map shows a non-zero sign, with the query's segment and page cache settings) are left out of the document count and of each term's document frequency, and the token count is the sum of the lengths the norms of the returned non-NULL rows stand for. A tablet whose rows are all hidden reports zero documents instead of failing. The statistics' I/O context follows the query's file cache options, and a CLucene searcher the statistics open drops the query's I/O context once its meta is read; a CLucene error while opening it fails the query instead of escaping the scanner thread. Adapted to branch-4.2: segments are identified by their id instead of RowsetSegmentView, and the delete-sign column reader comes from Segment::get_column_reader(), a segment without the column reading as no deletes.
Contributor
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
Contributor
|
run buildall |
Contributor
Author
|
run cloud_p0 |
Contributor
BE Regression && UT Coverage ReportIncrement line coverage Increment coverage report
|
yiguolei
approved these changes
Oct 29, 2026
yiguolei
pushed a commit
that referenced
this pull request
Oct 30, 2026
…blet idf of the terms the prefix expands to #69698 (#69801) ### What problem does this PR solve? Issue Number: None Related PR: #69698, #69752 Problem Summary: Backport of #69698 to branch-4.2. score() over MATCH_PHRASE_PREFIX ignored the prefix and differed between inverted index storage formats: 1. The idf of a phrase prefix now counts every term the prefix expands to, as Lucene's multi-phrase query and Elasticsearch do, and all idfs come from the tablet statistics. The statistics expand the prefix in every segment and keep the `inverted_index_max_expansions` smallest terms of the tablet that a returned row holds (a session limit of 0 keeps 50), so a term's document frequency is counted in every segment that holds it and merge-on-write hidden rows do not change the scores after a compaction. A term whose bytes are not valid UTF-8 is not scored. 2. A phrase prefix of one token scores 1 on every storage format; on V2/V3 it read 0, so `score() > 0` dropped every row it matched. See #69698 for the full description and the review rounds. This change builds on #69438 (BM25 statistics count only visible rows of merge-on-write tables), whose branch-4.2 backport #69752 is merged. The PR is rebased onto the current branch-4.2 and contains only the backport commit; its patch is identical to the one validated below. ### Branch adaptations - No production code adaptation. The automatic pick conflicted only because #69438 was not on branch-4.2 yet; on top of #69752 the change applies with one conflict, in the context of `collection_statistics_test.cpp`: branch-4.2 already has `CollectConvertsAnalyzerFailureToStatus` elsewhere in the file, so the copy that master shows next to the renamed test is not added again. ### Release note score() over MATCH_PHRASE_PREFIX counts the terms the prefix expands to, and a phrase prefix of one token scores 1 on every inverted index storage format. ### Check List (For Author) - Test: Regression test, Unit Test - Validated locally on branch-4.2 at 711db43, the commit before #69625, with the same two commits on top: the newest branch-4.2 calls a lance-c API that no thirdparty build on the machine used here provides. The PR branch itself is on the current branch-4.2 and is built by CI. - test_match_phrase_prefix_score passes with the `.out` of master unchanged (V2, V3, SNII and the capped two-segment case). - test_bm25_score, test_bm25_score_parallel_scan_statistics, test_bm25_score_range_filter, test_bm25_score_replica_statistics, test_bm25_score_variant, test_score_filter_matrix, test_score_filter_usage and test_search_score_topn_predicates pass. - 1644 related BE unit tests pass (PhrasePrefixQueryV2Test, CollectionStatistics*, PrefixExpansionSetTest, SNII, InvertedIndexReaderTest, MultiSegmentCollectorTest, BM25SimilarityTest). - Behavior changed: Yes (scores of MATCH_PHRASE_PREFIX change) - Does this need documentation: No 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What problem does this PR solve?
Issue Number: None
Related PR: #69438 (this is its backport to branch-4.2), #69352 and its branch-4.2 pick #69440 (BM25 statistics shared across the parallel scanners of a tablet)
Problem Summary:
Backport of #69438 to branch-4.2.
On a merge-on-write table, BM25
score()gives the same row a different score on each replica of its tablet. A query picks one replica per tablet, so withreplication_num = 3a row has up to three scores, and pagination overORDER BY score()repeats and skips rows from one page to the next.CollectionStatistics::process_segment()takes the document count, the token count and each term's document frequency straight from every segment's inverted index. Rows a later load overwrote or deleted (marked only in the delete bitmap) and rows written with the delete sign (CDC deletes,DELETE) stay in the index and are counted until compaction removes them, and each replica compacts on its own schedule.On branch-4.2 (3 BEs,
replication_num = 3, 8 buckets, CDC-style upserts and delete-sign deletes, the replicas on one BE fully compacted), all 1,731 matching rows of aSEARCH(... OR ...)query got more than one score over 15 runs and differed betweenuse_fix_replica = 0/1/2.Fix: the same change as #69438. When the scan reads a delete bitmap (merge-on-write),
CollectionStatisticsdescribes only the rows the scan returns:OlapScannerpasses the scan's delete bitmap and read version (MowHiddenRows) tocollect(), or toTabletCollectionStatistics::copy_to()for a split tablet (branch-4.2: [fix](inverted index) Share BM25 statistics across parallel scanners of a tablet #69352 #69440).__DORIS_DELETE_SIGN__is set. The delete-sign column is read only for segments whose zone map shows a non-zero sign (or cannot tell), following the query'senable_segment_cacheandenable_page_cache.IndexSource::scoring_doc_freq_among()subtracts the hidden rows a term's postings list, leaping between them when they are much rarer than the postings, otherwise walking them alongside the decoded postings.CluceneSearcherCacheValue).Tables without a delete bitmap collect the same statistics as before. For every
score()query, the statistics' I/O context followsdisable_file_cacheandinverted_index_snii_read_no_write_file_cache, and a CLucene searcher the statistics open drops the query's I/O context once its meta is read; a CLucene error while opening it fails the query.Differences from #69438, for branch-4.2: segments are identified by their id instead of
RowsetSegmentView, and the delete-sign column reader comes fromSegment::get_column_reader()(branch-4.2 has noget_column_reader_for_pruning), a segment without the column reading as no deletes. The tests are adapted the same way.Not covered (as in #69438): merge-on-read unique tables and duplicate tables with
DELETEpredicates; rows deleted by a delete predicate (enable_mow_light_delete); rows written with the delete sign that the query returns (skip_delete_sign,show_hidden_columns) are still left out; an analyzed empty string, and on SNII any text that yields no token, counts as one token on a merge-on-write table; SNII norm length sums are not cached. Statistics stay per tablet.Release note
Fix BM25
score()on merge-on-write tables differing between the replicas of a tablet, and before and after compaction. Statistics now count only the rows a query can return, and the average document length comes from the indexed norms.Check List (For Author)
Test
Regression test:
inverted_index_p0/test_bm25_score_replica_statisticsandfault_injection_p0/test_bm25_score_statistics_searcher_io_context(same suites as [fix](inverted index) Count only visible rows in BM25 statistics of merge-on-write tables #69438). Updated.out:test_bm25_score.out,test_score_filter_matrix.out(V2, V3 and SNII merge-on-write cases), same values as [fix](inverted index) Count only visible rows in BM25 statistics of merge-on-write tables #69438.score()(inverted_index_p0,search,ann_index_p0,variant_p0,nereids_rules_p0,fault_injection_p0) pass with this change on a 3-BE cluster. Without it, the replica suite, the fault-injection suite (the query fails with[E-6002] The 'is_index_data' flag should be false for non-meta inverted index files.) and the two updated suites fail.Unit Test: the tests of [fix](inverted index) Count only visible rows in BM25 statistics of merge-on-write tables #69438 (
CollectionStatisticsTest.HiddenRowsAreCollectedLikeCompactedAway,AllRowsHiddenCollectsEmptyStatistics,NullRowsCountAsDocumentsWithoutTokens,CollectionStatisticsHiddenRowsTest.*forcount_listed_docs,exclude_hidden_docsandexclude_hidden_term_docs,CluceneSearcherCacheValueTest.*,MultiSegmentCollectorTest.NormLengthSumsReadEachSegment,OlapScannerTest.BuildScoreRuntimeCollectionIoContextFollowsQueryFileCacheOptions), with the existing collection statistics, BM25, searcher cache, multi-segment, query engine, SNII andSEARCHfunction tests: 427 tests in 35 suites pass in an ASAN build on macOS arm64.Manual test (3 BEs, macOS arm64,
replication_num = 3, auto compaction off,enable_sql_cache = false), base = branch-4.2 without this change, same data and compaction state for both builds:merge_type: DELETE, group commit, keyword and lowercase-normalizer indexes, partitions, variant, SNII: rows differing between replicas after one replica compactedCost, query
SEARCH('content:jobs OR content:try') ORDER BY score() DESC LIMIT 100, one bucket, median of 15 warm runs;StatisticsCollectTimewith one scanner, query total ofSEARCH('content:jobs')with the default parallel scan:The cost grows with the rows compaction has not removed yet. With NULL rows, most of it is leaving the NULL rows out of the hidden rows on every query (
exclude_hidden_docs), as in [fix](inverted index) Count only visible rows in BM25 statistics of merge-on-write tables #69438.Memory (BE allocated bytes, sum of 3 BEs, a 4-field merge-on-write table of 41 segments): no growth beyond the noise of the measurement (after 4 queries +26 MB → +25 MB with key pruning; +142 MB → below the starting value scoring all segments).
Behavior changed:
Does this need documentation?
Check List (For Reviewer who merge this PR)