apache / apache/lucene

Skip scorer construction for zero-cost clause in BooleanScorerSupplier?

Open
#15,887 3 comments 0 reactions 0 assignees View on GitHub
type:enhancement
Dominant language
Java
Stars
3.6k
Forks
1.4k
Avg merge
2d 11h
Merged PRs (30d)
88

Description

### Description

While debugging a latency spike issue in OpenSearch, I saw that one user was firing a complex boolean queries which had hundreds(900 to be exact) of SHOULD clauses where most of the time was being spent in scorer construction rather than actual query execution.

This is how their query looked like:
```
{
"bool": {
"must": [{"term": {"field1": "value1"}}],
"filter": [{
"bool": {
"should": [
{"bool": {"filter": [
{"terms": {"field2": ["val_a", "val_b"]}},
{"term": {"field3": "val_001"}},
{"term": {"field4": "val_x"}}
]}},
{"bool": {"filter": [
{"terms": {"field2": ["val_c"]}},
{"term": {"field3": "val_002"}},
{"term": {"field4": "val_x"}}
]}},
... // ~900 more such clauses
]
}
}]
}
}
```

In one of the hot threads dump, I saw this:
```
100.5% cpu usage by thread 'search[T#10]'
BooleanScorerSupplier.req(BooleanScorerSupplier.java:496)
BooleanScorerSupplier.getInternal(BooleanScorerSupplier.java:137)
BooleanScorerSupplier.get(BooleanScorerSupplier.java:117)
BooleanScorerSupplier.opt(BooleanScorerSupplier.java:537) ← building scorers for 900+ clauses
BooleanScorerSupplier.getInternal(BooleanScorerSupplier.java:145)
BooleanScorerSupplier.get(BooleanScorerSupplier.java:117)
BooleanScorerSupplier.requiredBulkScorer(BooleanScorerSupplier.java:377)
BooleanScorerSupplier.booleanScorer(BooleanScorerSupplier.java:219)
BooleanScorerSupplier.bulkScorer(BooleanScorerSupplier.java:177)
```

Note that the above query had zero hits, so most of the should clauses must have had zero cost.

I see that BooleanScorerSupplier already computes and caches `cost()` for every child ScorerSupplier during `computeShouldCost() / computeCost()`. So maybe we can use this cached cost to skip the expensive `scorer.get(leadCost)`?

We can do that for should clauses where if `minShouldMatch <= 1`, and a clause has `cost() == 0`, we simply skip it and return an empty scorer?

Contributor guide

Open the contributing guide

Research direction

Start in BooleanScorerSupplier.java, especially req(), getInternal(), opt(), and the cached costs computed by computeShouldCost() and computeCost(). Trace how zero-cost SHOULD clauses are handled when minShouldMatch <= 1, then verify that skipping scorer construction preserves matching behavior and avoids unnecessary work.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
search
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.