[Hybrid Query] Gate collapse distinct-groups collection behind an opt-in index setting - #1956
Conversation
PR Reviewer Guide 🔍(Review updated until commit bc1a0ac)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to bc1a0ac Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit 44d5bfb
Suggestions up to commit 0cbec23
Suggestions up to commit 3a3e174
Suggestions up to commit 06aa2f9
|
|
Persistent review updated to latest commit 3a3e174 |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1956 +/- ##
============================================
+ Coverage 83.56% 83.66% +0.10%
- Complexity 3956 3970 +14
============================================
Files 298 299 +1
Lines 14039 14251 +212
Branches 2323 2367 +44
============================================
+ Hits 11731 11923 +192
- Misses 1475 1486 +11
- Partials 833 842 +9 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
3a3e174 to
0cbec23
Compare
|
Persistent review updated to latest commit 0cbec23 |
0cbec23 to
44d5bfb
Compare
|
Persistent review updated to latest commit 44d5bfb |
| } | ||
| } | ||
|
|
||
| private void collectExistingGroup(int doc, float score, CollectedGroup<T> group) throws IOException { |
There was a problem hiding this comment.
Seems per-leg representative election still splits a group's score. CollectedGroup holds int topDoc, each SubQueryGroupCollector is per sub-query and its score comparator is wrapped to read that leg's individual score (HybridLeafFieldComparator fed by setCurrentSubQueryScore(score)). Election happens per leg in collectExistingGroup , same for collectNewGroup.
This scenario will be relatively common, it needs only 2+ legs and one group containing two documents that legs rank differently. Legs ranking documents differently is the point of hybrid search, so any multi-doc group will potential have this problem.
One way to fix this, and it's easier with the new opt-in mode - leg-independent election can go straight in new collector collect(int doc) method:
// in the outer collect(), before the per-sub-query loop
float fusionProxy = 0f;
for (float s : subScoresByQuery) fusionProxy += s; // leg-independent ranking keyPass that down so every SubQueryGroupCollector elects the same doc id for a group while group.score stays the leg's own score. Later join adds legs for one doc id instead of splitting them.
|
|
||
| import static org.mockito.Mockito.mock; | ||
|
|
||
| public class HybridCollapsingTopGroupsCollectorTests extends HybridCollectorTestCase { |
There was a problem hiding this comment.
please increase coverage, currently there is no test that can catch the missing functionality from my other code level comment, existing tests are only for per-leg representative election.
- shared helper
collectWithGroupDerivedScoreswrites onlygetSubQueryScores()[0], that's one leg, so there is nothing to disagree on testCollapse_whenMultipleSubQueries_thenEachSubQueryHasResultsdoes build disagreeing legs ("high scores for even docs" / "high scores for odd docs"), so actual condition is present in its data. But its assertions are for existence-only, so it cannot fail on a wrong representative
Ideally would be great to have a two-leg IT asserting group order, not just group count
There was a problem hiding this comment.
Added in 5ff02f0:
testCollapse_whenLegsDisagreeWithinGroup_thenSameRepresentativeElectedAcrossLegs, two legs ranking a group's documents differently, asserting the same representative in every leg with each leg's own score, and leg independent group order. This one fails on the previous code.testCollapse_whenGroupStrongInOneLegOnly_thenEvictionUsesSummedScore, eviction is also leg independent.testCollapse_whenSortByFieldAndLegsMatchDifferentDocs_thenSameRepresentativeElected, the same guarantee under field sort.- The two leg IT you suggested,
testCollapse_whenLegsDisagreeAndDistinctGroupsEnabled_thenGroupsOrderedByFusedScore, legs scoring different fields and the full group order asserted. testCollapse_whenMultipleSubQueries_thenEachSubQueryHasResultsis updated to the new contract, a sub-query that matched none of the elected representatives emits an empty list while its total hits still count its own matches.
…e top docs Collect top-numHits groups instead of documents in HybridCollapsingTopDocsCollector, mirroring Lucene's FirstPassGroupingCollector bookkeeping per sub-query. Resolves opensearch-project#1947 Signed-off-by: Yasutaka Hisano <yasutennis713@gmail.com>
Signed-off-by: Yasutaka Hisano <yasutennis713@gmail.com>
…x setting Add index.neural_search.hybrid_collapse_distinct_groups_enabled (dynamic, default false). The default keeps the existing behavior of collecting the top-size documents per sub-query, preserving score parity with the same hybrid query without collapse. When enabled, HybridCollapsingTopGroupsCollector collects the top-size distinct groups per sub-query instead, so the response contains size groups whenever that many exist. Resolves opensearch-project#1947 Signed-off-by: Yasutaka Hisano <yasutennis713@gmail.com>
Per-sub-query election could elect different documents for one group, splitting the group's score across documents in the downstream per-document fusion. Run a single election instead: when sorting by score the comparators read HybridSubQueryScorer#score(), the sum over sub-queries, and every sub-query reports its own score for the one elected representative. A sub-query that did not match the representative leaves it out of its list, and group evictions no longer propagate per-sub-query competitive score thresholds, since a low score in one sub-query does not disqualify a document whose other sub-query scores make it the representative. Addresses review feedback on opensearch-project#1956 Signed-off-by: Yasutaka Hisano <yasutennis713@gmail.com>
Signed-off-by: Yasutaka Hisano <yasutennis713@gmail.com>
44d5bfb to
bc1a0ac
Compare
|
Persistent review updated to latest commit bc1a0ac |
Description
Implements the decision from #1947: both collapse behaviors make sense but are mutually exclusive, so the existing behavior stays the default and the distinct-groups collection is opt-in. This adds
By default nothing changes — the collector keeps the top-
sizedocuments per sub-query, preserving score parity with the same hybrid query without collapse. Over the issue's dataset (6 groups on one shard,groupAowning the three top-scoring documents),size: 5returns 4 hits:Turning the setting on (dynamic, takes effect on the next request):
makes the same search return 5 hits:
The two modes cannot be combined: they hand different document sets to per-sub-query normalization, so the same document can get different normalized scores under each mode — which is why this is a switch rather than a fix of the default.
Implementation notes
HybridCollapsingTopGroupsCollector(new) — collects the top-sizedistinct groups per sub-query, mirroring the bookkeeping of Lucene'sFirstPassGroupingCollector: a group map plus an ordered set oncesizegroups exist, evicting the weakest group when a new competitive one arrives. When sorting by score, comparators are wrapped withHybridLeafFieldComparatorso they read the sub-query's individual score rather than the sum. Emits oneFieldDocper surviving group, in the sameCollapseTopFieldDocsencoding the existing collector uses — the coordinator-side normalization/deduplication pipeline is unchanged and no version gating is needed.HybridCollectorFactory— reads the setting and picks the collector; this is the only decision point, next to where the deprecatedhybrid_collapse_docs_per_group_per_subquerysetting is already read.HybridCollectorManager— registers the new collector type.HybridCollapsingTopDocsCollector,HybridLeafFieldComparatorand the existing collector tests are byte-identical tomain— the default path is untouched.Testing
HybridCollapsingTopDocsCollectorTests(default behavior, identical tomain),HybridCollapsingTopGroupsCollectorTests(opt-in behavior), factory tests for the setting switchHybridCollapseITcovers both modes over the same skewed-groups dataset, including a test pinning the by-design default from the issue discussionRelated Issues
Resolves #1947
Check List
--signoff.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.