Skip to content

Commit 6165676

Browse files
authored
Search - allow_partial_search_results flag to search requests with default setting = true. (#27906)
When false will error if search either timeouts, has partial errors or has missing shards rather than returning partial search results. Backport of 28440. Closes #27435
1 parent f575ac9 commit 6165676

File tree

32 files changed

+392
-36
lines changed

32 files changed

+392
-36
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/Request.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,9 @@ static Request search(SearchRequest searchRequest) throws IOException {
444444
if (searchRequest.requestCache() != null) {
445445
params.putParam("request_cache", Boolean.toString(searchRequest.requestCache()));
446446
}
447+
if (searchRequest.allowPartialSearchResults() != null) {
448+
params.putParam("allow_partial_search_results", Boolean.toString(searchRequest.allowPartialSearchResults()));
449+
}
447450
params.putParam("batched_reduce_size", Integer.toString(searchRequest.getBatchedReduceSize()));
448451
if (searchRequest.scroll() != null) {
449452
params.putParam("scroll", searchRequest.scroll().keepAlive());

client/rest-high-level/src/test/java/org/elasticsearch/client/RequestTests.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,10 @@ public void testSearch() throws Exception {
846846
searchRequest.requestCache(randomBoolean());
847847
expectedParams.put("request_cache", Boolean.toString(searchRequest.requestCache()));
848848
}
849+
if (randomBoolean()) {
850+
searchRequest.allowPartialSearchResults(randomBoolean());
851+
expectedParams.put("allow_partial_search_results", Boolean.toString(searchRequest.allowPartialSearchResults()));
852+
}
849853
if (randomBoolean()) {
850854
searchRequest.setBatchedReduceSize(randomIntBetween(2, Integer.MAX_VALUE));
851855
}

docs/reference/search/request-body.asciidoc

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ And here is a sample response:
8686
aggregations and suggestions (no top hits returned).
8787
See <<shard-request-cache>>.
8888

89+
`allow_partial_search_results`::
90+
91+
Set to `false` to return an overall failure if the request would produce partial
92+
results. Defaults to true, which will allow partial results in the case of timeouts
93+
or partial failures.
94+
8995
`terminate_after`::
9096

9197
The maximum number of documents to collect for each shard,
@@ -103,9 +109,9 @@ And here is a sample response:
103109

104110

105111

106-
Out of the above, the `search_type` and the `request_cache` must be passed as
107-
query-string parameters. The rest of the search request should be passed
108-
within the body itself. The body content can also be passed as a REST
112+
Out of the above, the `search_type`, `request_cache` and the `allow_partial_search_results`
113+
settings must be passed as query-string parameters. The rest of the search request should
114+
be passed within the body itself. The body content can also be passed as a REST
109115
parameter named `source`.
110116

111117
Both HTTP GET and HTTP POST can be used to execute search with body. Since not

docs/reference/search/uri-request.asciidoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,8 @@ Defaults to no terminate_after.
122122
Defaults to `query_then_fetch`. See
123123
<<search-request-search-type,_Search Type_>> for
124124
more details on the different types of search that can be performed.
125+
126+
|`allow_partial_search_results` |Set to `false` to return an overall failure if the request would produce
127+
partial results. Defaults to true, which will allow partial results in the case of timeouts
128+
or partial failures..
125129
|=======================================================================

modules/reindex/src/main/java/org/elasticsearch/index/reindex/BulkByScrollParallelizationHelper.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ static SearchRequest[] sliceIntoSubRequests(SearchRequest request, String field,
164164
.requestCache(request.requestCache())
165165
.scroll(request.scroll())
166166
.indicesOptions(request.indicesOptions());
167+
if (request.allowPartialSearchResults() != null) {
168+
slices[slice].allowPartialSearchResults(request.allowPartialSearchResults());
169+
}
167170
}
168171
return slices;
169172
}

rest-api-spec/src/main/resources/rest-api-spec/api/search.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@
147147
"type" : "boolean",
148148
"description": "Indicate if the number of documents that match the query should be tracked"
149149
},
150+
"allow_partial_search_results": {
151+
"type" : "boolean",
152+
"default" : true,
153+
"description": "Indicate if an error should be returned if there is a partial search failure or timeout"
154+
},
150155
"typed_keys": {
151156
"type" : "boolean",
152157
"description" : "Specify whether aggregation and suggester names should be prefixed by their respective types in the response"

server/src/main/java/org/elasticsearch/action/search/AbstractSearchAsyncAction.java

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,27 @@ public final void executeNextPhase(SearchPhase currentPhase, SearchPhase nextPha
131131
}
132132
onPhaseFailure(currentPhase, "all shards failed", cause);
133133
} else {
134-
if (logger.isTraceEnabled()) {
135-
final String resultsFrom = results.getSuccessfulResults()
136-
.map(r -> r.getSearchShardTarget().toString()).collect(Collectors.joining(","));
137-
logger.trace("[{}] Moving to next phase: [{}], based on results from: {} (cluster state version: {})",
138-
currentPhase.getName(), nextPhase.getName(), resultsFrom, clusterStateVersion);
134+
Boolean allowPartialResults = request.allowPartialSearchResults();
135+
assert allowPartialResults != null : "SearchRequest missing setting for allowPartialSearchResults";
136+
if (allowPartialResults == false && shardFailures.get() != null ){
137+
if (logger.isDebugEnabled()) {
138+
final ShardOperationFailedException[] shardSearchFailures = ExceptionsHelper.groupBy(buildShardFailures());
139+
Throwable cause = shardSearchFailures.length == 0 ? null :
140+
ElasticsearchException.guessRootCauses(shardSearchFailures[0].getCause())[0];
141+
logger.debug((Supplier<?>) () -> new ParameterizedMessage("{} shards failed for phase: [{}]",
142+
shardSearchFailures.length, getName()),
143+
cause);
144+
}
145+
onPhaseFailure(currentPhase, "Partial shards failure", null);
146+
} else {
147+
if (logger.isTraceEnabled()) {
148+
final String resultsFrom = results.getSuccessfulResults()
149+
.map(r -> r.getSearchShardTarget().toString()).collect(Collectors.joining(","));
150+
logger.trace("[{}] Moving to next phase: [{}], based on results from: {} (cluster state version: {})",
151+
currentPhase.getName(), nextPhase.getName(), resultsFrom, clusterStateVersion);
152+
}
153+
executePhase(nextPhase);
139154
}
140-
executePhase(nextPhase);
141155
}
142156
}
143157

@@ -265,8 +279,16 @@ public final SearchRequest getRequest() {
265279

266280
@Override
267281
public final SearchResponse buildSearchResponse(InternalSearchResponse internalSearchResponse, String scrollId) {
282+
283+
ShardSearchFailure[] failures = buildShardFailures();
284+
Boolean allowPartialResults = request.allowPartialSearchResults();
285+
assert allowPartialResults != null : "SearchRequest missing setting for allowPartialSearchResults";
286+
if (allowPartialResults == false && failures.length > 0){
287+
raisePhaseFailure(new SearchPhaseExecutionException("", "Shard failures", null, failures));
288+
}
289+
268290
return new SearchResponse(internalSearchResponse, scrollId, getNumShards(), successfulOps.get(),
269-
skippedOps.get(), buildTookInMillis(), buildShardFailures(), clusters);
291+
skippedOps.get(), buildTookInMillis(), failures, clusters);
270292
}
271293

272294
@Override

server/src/main/java/org/elasticsearch/action/search/ExpandSearchPhase.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ private SearchRequest buildExpandSearchRequest(SearchRequest orig, SearchSourceB
128128
.preference(orig.preference())
129129
.routing(orig.routing())
130130
.searchType(orig.searchType());
131+
if (orig.allowPartialSearchResults() != null){
132+
groupRequest.allowPartialSearchResults(orig.allowPartialSearchResults());
133+
}
131134
if (orig.isMaxConcurrentShardRequestsSet()) {
132135
groupRequest.setMaxConcurrentShardRequests(orig.getMaxConcurrentShardRequests());
133136
}

server/src/main/java/org/elasticsearch/action/search/InitialSearchPhase.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,27 @@ public final void run() throws IOException {
146146
if (shardsIts.size() > 0) {
147147
int maxConcurrentShardRequests = Math.min(this.maxConcurrentShardRequests, shardsIts.size());
148148
final boolean success = shardExecutionIndex.compareAndSet(0, maxConcurrentShardRequests);
149-
assert success;
149+
assert success;
150+
assert request.allowPartialSearchResults() != null : "SearchRequest missing setting for allowPartialSearchResults";
151+
if (request.allowPartialSearchResults() == false) {
152+
final StringBuilder missingShards = new StringBuilder();
153+
// Fail-fast verification of all shards being available
154+
for (int index = 0; index < shardsIts.size(); index++) {
155+
final SearchShardIterator shardRoutings = shardsIts.get(index);
156+
if (shardRoutings.size() == 0) {
157+
if(missingShards.length() >0 ){
158+
missingShards.append(", ");
159+
}
160+
missingShards.append(shardRoutings.shardId());
161+
}
162+
}
163+
if (missingShards.length() >0) {
164+
//Status red - shard is missing all copies and would produce partial results for an index search
165+
final String msg = "Search rejected due to missing shards ["+ missingShards +
166+
"]. Consider using `allow_partial_search_results` setting to bypass this error.";
167+
throw new SearchPhaseExecutionException(getName(), msg, null, ShardSearchFailure.EMPTY_ARRAY);
168+
}
169+
}
150170
for (int index = 0; index < maxConcurrentShardRequests; index++) {
151171
final SearchShardIterator shardRoutings = shardsIts.get(index);
152172
assert shardRoutings.skip() == false;

server/src/main/java/org/elasticsearch/action/search/MultiSearchRequest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ public static void readMultiLineFormat(BytesReference data,
226226
searchRequest.preference(nodeStringValue(value, null));
227227
} else if ("routing".equals(entry.getKey())) {
228228
searchRequest.routing(nodeStringValue(value, null));
229+
} else if ("allow_partial_search_results".equals(entry.getKey())) {
230+
searchRequest.allowPartialSearchResults(nodeBooleanValue(value, null));
229231
}
230232
}
231233
defaultOptions = IndicesOptions.fromMap(source, defaultOptions);
@@ -297,6 +299,9 @@ public static byte[] writeMultiLineFormat(MultiSearchRequest multiSearchRequest,
297299
if (request.routing() != null) {
298300
xContentBuilder.field("routing", request.routing());
299301
}
302+
if (request.allowPartialSearchResults() != null) {
303+
xContentBuilder.field("allow_partial_search_results", request.allowPartialSearchResults());
304+
}
300305
xContentBuilder.endObject();
301306
xContentBuilder.bytes().writeTo(output);
302307
}

0 commit comments

Comments
 (0)