Repository navigation
Conversation
There was a problem hiding this comment.
Thanks for the fix!
The numFoundExact fix is right, and removing the numFound-- on a failed push is a good cleanup. My concern is the new numFound value.
In Solr, numFound is the number of matching documents, independent of rows/start/shards.rows, never the size of the page or candidate pool. Even when approximate (minExactCount, timeAllowed), it estimates the match count, as do grouping's per-group counts.
responseDocs.setNumFound(combinedShardDocs.size()) makes numFound depend on the size of the fusion pool. Each sub-query's queue holds at most perQueryQueueSize = max(start + rows, shards.rows), so the count follows how many docs were requested rather than how many matched.
- rows=0 returns numFound: 0. The queue size is 0 and the shards return no docs, so the fused list is empty. This breaks count-only and facet-only requests, which previously got a reasonable lower bound.
- It can drop below a known match count. With two largely overlapping sub-queries of 6 hits each and rows=2, the fused list has 2–4 docs, so the response reports 2–4 even though we know at least 6 docs match.
- It changes while paging. As start + rows grows the pool, numFound grows too, so page counts built on numFound shift from page to page.
numFoundExact: false doesn't fully cover this. Callers read an inexact count as "at least N", and in these cases N is too low.
Suggestion: use the larger of the two counts we have:
maxQueryNumFound = max(maxQueryNumFound, queryNumFound); // per query, summed across shards
....
responseDocs.setNumFound(max(maxQueryNumFound, combinedShardDocs.size()));
responseDocs.setNumFoundExact(hitCountIsExact && !someQueryListTruncated);
| + ":[\"lexical1\",\"lexical2\"],\"debug\":[\"results\"]}}")); | ||
| assertEquals(10, rsp.getResults().getNumFound()); | ||
| // the test combiner fuses to an empty list, so numFound reflects the fused result | ||
| assertEquals(0, rsp.getResults().getNumFound()); |
There was a problem hiding this comment.
10 docs match here, so numFound: 0 documents the pool-size behaviour rather than a count we want.
There was a problem hiding this comment.
Addressed, the empty fused pool now falls back to the largest sub-query's match count.
| "params": {"combiner": true, "combiner.query": ["lexical1", "lexical2"]} | ||
| }""")); | ||
| assertEquals(4, rsp.getResults().size()); | ||
| assertEquals(8, rsp.getResults().getNumFound()); |
There was a problem hiding this comment.
The true union is 10 and the largest sub-query has 6. Asserting 8 locks the test to the pool size, which changes with limit. With the suggested change this would assert the max, at least 6, and stay numFoundExact == false.
There was a problem hiding this comment.
Addressed; now asserts the max: limit 2 -> numFound=6, numFoundExact stays false. Also added a limit-0 case for the rows=0 concern.
Thanks @ercsonusharma for the careful review. I have addressed your comments in my latest commit. |
ercsonusharma
left a comment
There was a problem hiding this comment.
Please drop "knn topK" from the ref guide and changelog. topK defines how many docs a KNN sub-query matches (its numFound), so it never truncates the list. A KNN list is truncated only by the fusion pool (shards.rows / start + rows), the same as any other sub-query.
|
cc: @dsmiley for help taking it forward! |
https://issues.apache.org/jira/browse/SOLR-18550
Description
For a combined query, the coordinator reported numFound as the largest sub-query's match count, not the distinct document count of the fused result, so the response could return more documents than the header claimed. The exactness check in mergeIds was also inverted, making an exact shard count report numFoundExact: false.
Reported on dev@solr.apache.org by Justin Michael Coyne, verified on 10.1.0-rc2 with 3 docs matching subject_s:cat and 2 matching subject_s:dog (no overlap): 5 documents returned, numFound: 3, numFoundExact: false.
Solution
Tests
Note: independent of SOLR-18542 / #5119 (weighted RRF) - no shared production code. Both add blocks to DistributedCombinedQueryComponentTest, so whichever merges second trivially rebases.
AI disclosure: parts of this change were developed with the help of an AI
coding assistant. I reviewed and tested everything and take full
responsibility for it.
Checklist
Please review the following and check all that apply:
mainbranch../gradlew check.