Skip to content

Commit a102788

Browse files
Disallow partial results when shard unavailable (#45739)
Searching with `allowPartialSearchResults=false` could still return partial search results during recovery. If a shard copy fails with a "shard not available" exception, the failure would be ignored and a partial result returned. The one case where this is known to happen is when a shard copy is recovering when searching, since `IllegalIndexShardStateException` is considered a "shard not available" exception. Relates to #42612
1 parent c80891e commit a102788

File tree

2 files changed

+34
-2
lines changed

2 files changed

+34
-2
lines changed

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ public final void executeNextPhase(SearchPhase currentPhase, SearchPhase nextPha
140140
} else {
141141
Boolean allowPartialResults = request.allowPartialSearchResults();
142142
assert allowPartialResults != null : "SearchRequest missing setting for allowPartialSearchResults";
143-
if (allowPartialResults == false && shardFailures.get() != null) {
143+
if (allowPartialResults == false && successfulOps.get() != getNumShards()) {
144144
// check if there are actual failures in the atomic array since
145145
// successful retries can reset the failures to null
146146
ShardOperationFailedException[] shardSearchFailures = buildShardFailures();
@@ -154,6 +154,15 @@ public final void executeNextPhase(SearchPhase currentPhase, SearchPhase nextPha
154154
}
155155
onPhaseFailure(currentPhase, "Partial shards failure", null);
156156
return;
157+
} else {
158+
int discrepancy = getNumShards() - successfulOps.get();
159+
assert discrepancy > 0 : "discrepancy: " + discrepancy;
160+
if (logger.isDebugEnabled()) {
161+
logger.debug("Partial shards failure (unavailable: {}, successful: {}, skipped: {}, num-shards: {}, phase: {})",
162+
discrepancy, successfulOps.get(), skippedOps.get(), getNumShards(), currentPhase.getName());
163+
}
164+
onPhaseFailure(currentPhase, "Partial shards failure (" + discrepancy + " shards unavailable)", null);
165+
return;
157166
}
158167
}
159168
if (logger.isTraceEnabled()) {

server/src/test/java/org/elasticsearch/action/search/AbstractSearchAsyncActionTests.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ private AbstractSearchAsyncAction<SearchPhaseResult> createAction(SearchRequest
8383
return null;
8484
};
8585

86-
return new AbstractSearchAsyncAction<SearchPhaseResult>("test", null, null, nodeIdToConnection,
86+
return new AbstractSearchAsyncAction<SearchPhaseResult>("test", logger, null, nodeIdToConnection,
8787
Collections.singletonMap("foo", new AliasFilter(new MatchAllQueryBuilder())), Collections.singletonMap("foo", 2.0f),
8888
Collections.singletonMap("name", Sets.newHashSet("bar", "baz")), null, request, listener,
8989
new GroupShardsIterator<>(
@@ -239,6 +239,29 @@ public void run() {
239239
assertEquals(requestIds, releasedContexts);
240240
}
241241

242+
public void testShardNotAvailableWithDisallowPartialFailures() {
243+
SearchRequest searchRequest = new SearchRequest().allowPartialSearchResults(false);
244+
AtomicReference<Exception> exception = new AtomicReference<>();
245+
ActionListener<SearchResponse> listener = ActionListener.wrap(response -> fail("onResponse should not be called"), exception::set);
246+
int numShards = randomIntBetween(2, 10);
247+
InitialSearchPhase.ArraySearchPhaseResults<SearchPhaseResult> phaseResults =
248+
new InitialSearchPhase.ArraySearchPhaseResults<>(numShards);
249+
AbstractSearchAsyncAction<SearchPhaseResult> action = createAction(searchRequest, phaseResults, listener, false, new AtomicLong());
250+
// skip one to avoid the "all shards failed" failure.
251+
SearchShardIterator skipIterator = new SearchShardIterator(null, null, Collections.emptyList(), null);
252+
skipIterator.resetAndSkip();
253+
action.skipShard(skipIterator);
254+
// expect at least 2 shards, so onPhaseDone should report failure.
255+
action.onPhaseDone();
256+
assertThat(exception.get(), instanceOf(SearchPhaseExecutionException.class));
257+
SearchPhaseExecutionException searchPhaseExecutionException = (SearchPhaseExecutionException)exception.get();
258+
assertEquals("Partial shards failure (" + (numShards - 1) + " shards unavailable)",
259+
searchPhaseExecutionException.getMessage());
260+
assertEquals("test", searchPhaseExecutionException.getPhaseName());
261+
assertEquals(0, searchPhaseExecutionException.shardFailures().length);
262+
assertEquals(0, searchPhaseExecutionException.getSuppressed().length);
263+
}
264+
242265
private static InitialSearchPhase.ArraySearchPhaseResults<SearchPhaseResult> phaseResults(Set<Long> requestIds,
243266
List<Tuple<String, String>> nodeLookups,
244267
int numFailures) {

0 commit comments

Comments
 (0)