Add 'knn' section to search endpoint#88002
Conversation
|
NOTE: this PR targets a feature branch |
303726f to
fa8697b
Compare
fa8697b to
4d78eb4
Compare
|
Pinging @elastic/es-search (Team:Search) |
|
Pinging @elastic/clients-team (Team:Clients) |
| static void adjustSearchType(SearchRequest searchRequest) { | ||
| // if there's a kNN search, always use DFS_QUERY_THEN_FETCH | ||
| if (searchRequest.hasKnnSearch()) { | ||
| searchRequest.searchType(DFS_QUERY_THEN_FETCH); |
There was a problem hiding this comment.
drive-by comment: do we want to return an error in case search type is explicitly set and differs from dfs query then fetch? Maybe this is something to think about for later, does not look high priority in this PR. Or maybe you already though about it.
There was a problem hiding this comment.
Thanks @javanna, I meant to add a comment about this. Yes in a follow-up PR I plan to disallow setting the search type explicitly when knn is provided too.
| // Emulate TransportSearchAction logic: first adjust search type, then check minimize roundtrips | ||
| TransportSearchAction.adjustSearchType(searchRequest); | ||
|
|
||
| // Minimize roundtrips should always be false because kNN uses DFS_QUERY_THEN_FETCH |
There was a problem hiding this comment.
too bad that we can't minimize roundtrips! maybe that's a good reminder to rethink whether we can do something about this, I will try to think about this.
There was a problem hiding this comment.
In a follow-up, I hope to optimize the case where there is only kNN, with no query or aggregations. Then we can run kNN during the main query phase (using QUERY_THEN_FETCH), and it will be possible to minimize roundtrips.
Also, I found this test to be a little fragile/ hacky -- I'm happy to remove it if people don't think it's valuable. I checked that the new CCS REST tests already cover the important cases.
There was a problem hiding this comment.
I see, agreed that it's a bit hacky because it requires adjustSearchType to be called. I would rather add a very simple unit test around adjustSearchType then. this specific test method already tests the scenario with dfs enabled, so we should just verify that the search type is adjusted when knn is used.
There was a problem hiding this comment.
Also, Nhat is running benchmarks to asses how bad it is that we don't minimize roundtrips in some cases, in the context of #73971 . I would imagine that the results of the benchmark will also inform whether we need to put more effort into minimizing roundtrips when knn is used.
There was a problem hiding this comment.
👍 I'll rework this test. Looking forward to seeing Nhat's results.
sethmlarson
left a comment
There was a problem hiding this comment.
API looks good, one thought:
rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/40_knn_search.yml
Show resolved
Hide resolved
server/src/main/java/org/elasticsearch/search/vectors/KnnSearchBuilder.java
Show resolved
Hide resolved
server/src/main/java/org/elasticsearch/search/vectors/KnnSearchBuilder.java
Show resolved
Hide resolved
149017a to
6e0e039
Compare
server/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java
Outdated
Show resolved
Hide resolved
...tegTest/groovy/org/elasticsearch/gradle/fixtures/AbstractGradleInternalPluginFuncTest.groovy
Show resolved
Hide resolved
|
@elasticmachine run elasticsearch-ci/packaging-tests-unix-sample |
|
Thanks @mayya-sharipova for the helpful review! @jpountz do you have time to review just to check the overall strategy looks okay? As a reminder, the PR targets a feature branch knn-search. There are still several TODOs (tracked in #87625) like support for filters. |
| searchRequest.searchType(QUERY_THEN_FETCH); | ||
| } | ||
|
|
||
| // if there's a kNN search, always use DFS_QUERY_THEN_FETCH |
There was a problem hiding this comment.
In a follow-up I'm thinking of handling this in SearchSourceBuilder#rewrite instead.
jpountz
left a comment
There was a problem hiding this comment.
I only skimmed through the PR. One thing that looks a bit odd to me if I read the code correctly is that we compute global nearest vectors on the coordinating node and then send them all to all shards, where many of them might get ignored as part of QueryBuilder#toQuery. Would it be possible to only send the relevant vectors to each shard?
| final SearchPhaseController.ReducedQueryPhase reducedQueryPhase = resultConsumer.reduce(); | ||
| final boolean queryAndFetchOptimization = queryResults.length() == 1; | ||
| final boolean queryAndFetchOptimization = queryResults.length() == 1 | ||
| && context.getRequest().searchType() == SearchType.QUERY_THEN_FETCH; |
There was a problem hiding this comment.
Why would we not run the fetch phase and the query phase in the same roundtrip when the search type has a DFS phase?
There was a problem hiding this comment.
Previously, when there was a single shard we would always set the search type to QUERY_THEN_FETCH:
This makes sense, since you don't need DFS with one shard. Now, we might still execute DFS_QUERY_THEN_FETCH with a single shard, when kNN is used. From failing tests, I saw that we don't apply the "query and fetch" optimization in the query phase when DFS is enabled. (Surprisingly, we use different codepath to execute the query phase when DFS is enabled vs. not. There are two different actions QUERY_ID_ACTION_NAME and QUERY_ACTION_NAME).
Here was my thinking for next steps:
- In a follow-up, I plan to use QUERY_THEN_FETCH for kNN with a single shard. This will remove the special handling, since we will always know QUERY_THEN_FETCH is used when there's one shard.
- For now I'll change this check to
queryResults.length() == 1 && context.getRequest().hasKnnSearch() == falseand add a comment explaining.
There was a problem hiding this comment.
Thanks for explaining, the proposed follow-up sounds good to me.
server/src/main/java/org/elasticsearch/search/vectors/ScoreDocQuery.java
Outdated
Show resolved
Hide resolved
server/src/main/java/org/elasticsearch/common/lucene/Lucene.java
Outdated
Show resolved
Hide resolved
That's correct! I did this because I thought it was a nice invariant that we always send the same |
|
The closest thing that I know of is how searches sorted by descending I agree that it shouldn't be a big deal in practice. One could argue that it is |
|
@jpountz thanks for your comments! It sounds like things look okay overall, no major flags. In the interest of time, I'm going to continue with these PRs (merging into a feature branch) and tag both you and @mayya-sharipova for a final review before merging to |
|
I pushed some updates:
|
sethmlarson
left a comment
There was a problem hiding this comment.
Looks good from an API perspective.
I'm guessing that the filter property of knn is being implemented in a future PR since there are no YAML tests cases with the property? If so that's fine.
mayya-sharipova
left a comment
There was a problem hiding this comment.
@jtibshirani Thanks, new changes LGTM@
This PR adds a new `knn` section to the search request. As explained in #87625, the search returns a global top k documents across all shards: * In the DFS phase, collect the top k vector matches per shard, combine them and keep the global top k. * Then move to the query phase. Convert the top k vector matches into a new `KnnScoreDocQueryBuilder` that matches only those top documents. For the final query, take a boolean disjunction between this `KnnScoreDocQueryBuilder` query and the search request `query`. Commits: * Rename KnnSearchRequestBuilder -> KnnSearchRequestParser to avoid confusion with new KnnSearchBuilder * Add `KnnScoreDocQueryBuilder` * Implement kNN search using DFS phase Addresses #87625.
This PR adds a new
knnsection to the search request:As explained in #87625, the search returns a global top k documents across all
shards:
and keep the global top k.
KnnScoreDocQueryBuilderthat matches only those top documents. For the finalquery, take a boolean disjunction between this
KnnScoreDocQueryBuilderquery andthe search request
query.Commits:
KnnScoreDocQueryBuilderAddresses #87625.