Skip to content

branch-4.2: [improvement](be) Speed up dense unscored inverted index conjunctions (#69172) - #69239

Merged
yiguolei merged 1 commit into
apache:branch-4.2from
airborne12:pick-69172-branch-4.2
Oct 19, 2026
Merged

yiguolei merged 1 commit into
apache:branch-4.2from
airborne12:pick-69172-branch-4.2

Conversation

@airborne12

Copy link
Copy Markdown
Member

What problem does this PR solve?

Issue Number: N/A

Related PR: #69172, #69206

Problem Summary: Backport #69172 after #69206 introduced the shared inverted-index query engine to branch-4.2. Dense unscored conjunctions now skip terms held by every row, write a lone term directly to the result bitmap, use branch-free block intersection, and bound streaming-source batches. Query results are unchanged.

Source PR: #69172

Source squash commit: f937d950d45313f20864d287df453e1b90f5c615 (cherry-picked with -x).

Release note

None

Check List (For Author)

  • Test
    • Regression test: test_match_format_parity passed on an isolated branch-4.2 FE/BE cluster after rebasing onto ecd322ad31193cda19cf4f3b08e0f3f9d12dd67c (1 suite, 0 failures).
    • Unit Test: ListedTermsTest.*:CursorChainedPostings.* passed (41 tests in 2 suites); all 9 source-added tests were listed and executed. No sanitizer report.
    • Manual test: ./build.sh --be -j64 and ./build.sh --fe -j8 passed; isolated backend reported Alive=true, and SELECT 1 succeeded.
  • Behavior changed:
    • No. Unscored conjunction execution is faster; query results are unchanged.
  • Does this need documentation?
    • No.

Validation

  • build-support/check-format.sh: PASS.
  • Complete merged-tree format preflight against origin/branch-4.2: PASS.
  • Complete PR diff English description check: PASS (5 source files, no exceptions).
  • Branch-4.2 has no build-support/run-clang-tidy.sh; the branch's BE build and format gates passed.

Hunk audit

The source squash patch ID matches the final source PR head. The cherry-pick was clean, and every changed file has a byte-identical postimage to the source commit.

Source file Disposition
be/src/storage/index/inverted/query/engine/boolean_query/listed_terms.cpp Ported (verbatim)
be/src/storage/index/inverted/query/exec/cursor_chained_postings.cpp Ported (verbatim)
be/src/storage/index/inverted/query/exec/cursor_chained_postings.h Ported (verbatim)
be/test/storage/index/inverted/query/engine/boolean_query/listed_terms_test.cpp Ported (verbatim)
be/test/storage/index/inverted/query/exec/cursor_chained_postings_test.cpp Ported (verbatim)

git range-diff shows only the -x cherry-pick provenance line; the code patch is unchanged.

…apache#69172)

### What problem does this PR solve?

Issue Number: N/A

Related PR: apache#68758

Problem Summary:

apache#68758 runs an unscored AND of terms on one field, such as MATCH_ALL, as
a chain: the rarest term's rows are listed into an array and the other
terms narrow them into another. On dense terms this was slower than the
bitmap intersection it replaced on V2 segments, and its arrays grew with
the segment (review comment M2 on apache#68758):

1. A term held by every row cannot remove any row, but the chain still
read its postings. For `MATCH_ALL 'http status'`, where both terms are
held by every row, it copied every row id twice and rebuilt the result
bitmap from the array: 14.4 ms on a 2M-row V2 segment, about 10 times
what it took before apache#68758.
2. A term left alone, such as `get` in `get http` once `http` is
skipped, was listed into an array of all its rows before the result
bitmap was built from it. For a term in 90% of 2M rows that cost about
1,200 extra page faults per query.
3. Intersecting a block through a bit set tested each candidate with a
branch. For two unrelated terms each in half the rows (`alpha beta`),
the branch mispredicted about half the time, and the intersection took
30% of the scan thread's cycles.
4. The arrays held up to 8 bytes per row of the segment in each scanner.

This PR:

- skips a term whose document frequency equals the source's document
count, so a conjunction whose terms are all held by every row returns
every row, or every candidate, without reading postings;
- lists a lone term's blocks straight into the result bitmap, a dense
block as a range;
- intersects a block through its bit set without a branch per candidate:
every candidate is written and kept only when its bit is set;
- streams a conjunction of several terms on a source that reads on
demand, such as a V2 segment: the cheapest term lists at most 2^16 of
its rows at a time, the others narrow them, and the rows left go
straight into the result, so no array holds more than a batch. The other
terms keep their current block from one batch to the next, so each block
is still decoded once. A batching source, such as SNII, keeps the chain,
which reads each term's blocks in one round.

Scored queries keep their path, and results are unchanged.

Index filter time of MATCH_ALL on a 2M-row V2 segment, as the median
ratio of 8 paired runs on fresh backends; each column is a separate
comparison and lower is faster:

| Query | Rows holding each term | vs apache#68758 | vs before apache#68758 |
|---|---|---|---|
| `http status` | 100%, 100% | 0.01 | 0.10 |
| `rare http` | 0.05%, 100% | 0.31 | 0.10 |
| `sparse http` | 0.5%, 100% | 0.23 | 0.31 |
| `get http` | 90%, 100% | 0.60 | 0.95 |
| `alpha beta` | 50%, 50% | 0.56 | 0.72 |

In the comparison with apache#68758, the `http status` filter drops from 16.9
ms to 0.15 ms and its end-to-end time to 0.63, and the end-to-end time
of `alpha beta` to 0.81. SNII segments take the same paths except the
stream; there these queries take 0.02 to 0.83 of apache#68758 and 0.02 to 0.93
of their time before apache#68758. Seven more shapes, from `rare alpha` and
`sparse alpha` to `alpha beta get` and the empty `get post`, take 0.20
to 0.89 of their time before apache#68758 on V2 and 0.57 to 1.00 on SNII.
Two-term MATCH_ALL queries on short text take 0.86 to 1.01 of apache#68758,
and the 18 queries used to accept apache#68758 (MATCH_PHRASE, MATCH_ANY and
SEARCH phrases on V2 and SNII) stay between 0.97 and 1.03 of it, every
95% interval including 1.

On V2 the stream holds at most two arrays of 2^16 rows per scanner, and
it cuts the page faults per query of `alpha beta` from 458 to 192 and
those of `get alpha` from 484 to 47. Its scan threads run within 1% of
the chain's instructions, and a cheapest term of at most 2^16 rows is
listed in one batch, as the chain lists it.

### Release note

None

### Check List (For Author)

- Test <!-- At least one of them must be included. -->
    - [ ] Regression test
    - [x] Unit Test
    - [x] Manual test (add detailed scripts or steps below)
    - [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
        - [ ] Previous test can cover this change.
        - [ ] No code files have been changed.
        - [ ] Other reason <!-- Add your reason?  -->

Unit test: `ListedTermsTest.AConjunctionSkipsATermHeldByEveryRow` and
`ListedTermsTest.AConjunctionOfTermsHeldByEveryRowHoldsEveryRow` fail
before the first commit, because the chain read the term held by every
row, and pass after it.
`ListedTermsTest.AConjunctionOnAStreamingSourceStreamsItsTerms` replaces
`AConjunctionOnAStreamingSourceChainsItsTermsToo`, which recorded that
such a source chained its terms, and fails before the last commit.
`ListedTermsTest.ALoneTermListsItsRowsOrThoseAmongTheCandidates`,
`CursorChainedPostings.IntersectionHandlesRandomDensities` and the
`CursorChainedPostings.AStream*` tests cover the lone-term listing, the
intersection and the stream; each fails when the code it covers is
broken on purpose.

Manual test: a 2M-row corpus with terms held by 100%, 90%, 50%, 10%,
0.5% and 0.05% of the rows was loaded into a V2 table and an SNII table.
Each of 8 pairs started two fresh backends, one per build, swapping
which backend ran which build on alternate pairs; each query ran 4
warm-up and 16 measured times on each build, alternating between the
builds. Every result matched aggregates computed independently from the
corpus. Scan-thread CPU time, cycles, instructions and page faults were
sampled with `perf stat` over 200 runs of each query on each build.

- Behavior changed:
    - [x] No.
    - [ ] Yes. <!-- Explain the behavior change -->

- Does this need documentation?
    - [x] No.
- [ ] Yes. <!-- Add document PR link here. eg:
apache/doris-website#1214 -->

### Check List (For Reviewer who merge this PR)

- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label <!-- Add branch pick label that this PR
should merge into -->

(cherry picked from commit f937d95)
@airborne12
airborne12 requested a review from yiguolei as a code owner October 18, 2026 15:41
@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@airborne12

Copy link
Copy Markdown
Member Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 100.00% (145/145) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 64.00% (29651/46330)
Line Coverage 48.81% (312064/639302)
Region Coverage 44.62% (250891/562318)
Branch Coverage 46.26% (118201/255505)

@yiguolei
yiguolei merged commit 9c11717 into apache:branch-4.2 Oct 19, 2026
27 of 30 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

PR approved by anyone and no changes requested.

@github-actions github-actions Bot added the approved Indicates a PR has been approved by one committer. label Oct 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

PR approved by at least one committer and no changes requested.

@airborne12
airborne12 deleted the pick-69172-branch-4.2 branch October 19, 2026 04:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by one committer. reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants