Skip to content

Commit eb479e5

Browse files
authored
Allow partial results by default in ES|QL - Take 2 (#127351)
* Revert "ESQL: Revert "Allow partial results by default in ES|QL (#125060)" (#126286)" This reverts commit 8f38b13. Restore changes from #125060 now that the breakage should be fixed.
1 parent bdb70c0 commit eb479e5

File tree

17 files changed

+86
-68
lines changed

17 files changed

+86
-68
lines changed

docs/changelog/126286.yaml

Lines changed: 0 additions & 6 deletions
This file was deleted.

docs/changelog/127351.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
pr: 127351
2+
summary: Allow partial results by default in ES|QL
3+
area: ES|QL
4+
type: breaking
5+
issues: [122802]
6+
7+
breaking:
8+
title: Allow partial results by default in ES|QL
9+
area: ES|QL
10+
details: >-
11+
In earlier versions of {es}, ES|QL would fail the entire query if it encountered any error. ES|QL now returns partial results instead of failing when encountering errors.
12+
13+
impact: >-
14+
Callers should check the `is_partial` flag returned in the response to determine if the result is partial or complete. If returning partial results is not desired, this option can be overridden per request via an `allow_partial_results` parameter in the query URL or globally via the cluster setting `esql.query.allow_partial_results`.
15+
16+
notable: true

docs/release-notes/breaking-changes.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ If you are migrating from a version prior to version 9.0, you must first upgrade
1212

1313
% ## Next version [elasticsearch-nextversion-breaking-changes]
1414

15+
## 9.1.0 [elasticsearch-910-breaking-changes]
16+
17+
ES|QL
18+
: * Allow partial results by default in ES|QL [#125060](https://github.com/elastic/elasticsearch/pull/125060)
19+
1520
## 9.0.0 [elasticsearch-900-breaking-changes]
1621

1722
Aggregations:

test/external-modules/esql-heap-attack/src/javaRestTest/java/org/elasticsearch/xpack/esql/heap_attack/Clusters.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ static ElasticsearchCluster buildCluster() {
2121
.module("test-esql-heap-attack")
2222
.setting("xpack.security.enabled", "false")
2323
.setting("xpack.license.self_generated.type", "trial")
24+
.setting("esql.query.allow_partial_results", "false")
2425
.jvmArg("-Xmx512m");
2526
String javaVersion = JvmInfo.jvmInfo().version();
2627
if (javaVersion.equals("20") || javaVersion.equals("21")) {

x-pack/plugin/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ tasks.named("yamlRestCompatTestTransform").configure({ task ->
125125
task.replaceValueInMatch("Size", 49, "Test flamegraph from profiling-events")
126126
task.replaceValueInMatch("Size", 49, "Test flamegraph from test-events")
127127
task.skipTest("esql/90_non_indexed/fetch", "Temporary until backported")
128+
task.skipTest("esql/63_enrich_int_range/Invalid age as double", "TODO: require disable allow_partial_results")
128129
})
129130

130131
tasks.named('yamlRestCompatTest').configure {

x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/EsqlRestValidationIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,6 @@ private RestClient remoteClusterClient() throws IOException {
8383

8484
@Before
8585
public void skipTestOnOldVersions() {
86-
assumeTrue("skip on old versions", Clusters.localClusterVersion().equals(Version.V_8_16_0));
86+
assumeTrue("skip on old versions", Clusters.localClusterVersion().equals(Version.V_8_19_0));
8787
}
8888
}

x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/RequestIndexFilteringIT.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.junit.rules.TestRule;
2929

3030
import java.io.IOException;
31+
import java.util.List;
3132
import java.util.Map;
3233

3334
import static org.elasticsearch.test.MapMatcher.assertMap;
@@ -87,6 +88,12 @@ protected String from(String... indexName) {
8788

8889
@Override
8990
public Map<String, Object> runEsql(RestEsqlTestCase.RequestObjectBuilder requestObject) throws IOException {
91+
if (requestObject.allowPartialResults() != null) {
92+
assumeTrue(
93+
"require allow_partial_results on local cluster",
94+
clusterHasCapability("POST", "/_query", List.of(), List.of("support_partial_results")).orElse(false)
95+
);
96+
}
9097
requestObject.includeCCSMetadata(true);
9198
return super.runEsql(requestObject);
9299
}

x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/RestEsqlIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public void testInvalidPragma() throws IOException {
111111
request.setJsonEntity("{\"f\":" + i + "}");
112112
assertOK(client().performRequest(request));
113113
}
114-
RequestObjectBuilder builder = requestObjectBuilder().query("from test-index | limit 1 | keep f");
114+
RequestObjectBuilder builder = requestObjectBuilder().query("from test-index | limit 1 | keep f").allowPartialResults(false);
115115
builder.pragmas(Settings.builder().put("data_partitioning", "invalid-option").build());
116116
ResponseException re = expectThrows(ResponseException.class, () -> runEsqlSync(builder));
117117
assertThat(EntityUtils.toString(re.getResponse().getEntity()), containsString("No enum constant"));

x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestEsqlTestCase.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public static class RequestObjectBuilder {
131131
private Boolean includeCCSMetadata = null;
132132

133133
private CheckedConsumer<XContentBuilder, IOException> filter;
134-
private Boolean allPartialResults = null;
134+
private Boolean allowPartialResults = null;
135135

136136
public RequestObjectBuilder() throws IOException {
137137
this(randomFrom(XContentType.values()));
@@ -209,11 +209,15 @@ public RequestObjectBuilder filter(CheckedConsumer<XContentBuilder, IOException>
209209
return this;
210210
}
211211

212-
public RequestObjectBuilder allPartialResults(boolean allPartialResults) {
213-
this.allPartialResults = allPartialResults;
212+
public RequestObjectBuilder allowPartialResults(boolean allowPartialResults) {
213+
this.allowPartialResults = allowPartialResults;
214214
return this;
215215
}
216216

217+
public Boolean allowPartialResults() {
218+
return allowPartialResults;
219+
}
220+
217221
public RequestObjectBuilder build() throws IOException {
218222
if (isBuilt == false) {
219223
if (tables != null) {
@@ -1376,8 +1380,8 @@ protected static Request prepareRequestWithOptions(RequestObjectBuilder requestO
13761380
requestObject.build();
13771381
Request request = prepareRequest(mode);
13781382
String mediaType = attachBody(requestObject, request);
1379-
if (requestObject.allPartialResults != null) {
1380-
request.addParameter("allow_partial_results", String.valueOf(requestObject.allPartialResults));
1383+
if (requestObject.allowPartialResults != null) {
1384+
request.addParameter("allow_partial_results", String.valueOf(requestObject.allowPartialResults));
13811385
}
13821386

13831387
RequestOptions.Builder options = request.getOptions().toBuilder();

x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/AbstractCrossClusterTestCase.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.elasticsearch.transport.RemoteClusterAware;
2626
import org.elasticsearch.xcontent.XContentBuilder;
2727
import org.elasticsearch.xcontent.json.JsonXContent;
28+
import org.elasticsearch.xpack.esql.plugin.EsqlPlugin;
2829
import org.junit.After;
2930
import org.junit.Before;
3031

@@ -76,6 +77,11 @@ protected Collection<Class<? extends Plugin>> nodePlugins(String clusterAlias) {
7677
return plugins;
7778
}
7879

80+
@Override
81+
protected Settings nodeSettings() {
82+
return Settings.builder().put(super.nodeSettings()).put(EsqlPlugin.QUERY_ALLOW_PARTIAL_RESULTS.getKey(), false).build();
83+
}
84+
7985
public static class InternalExchangePlugin extends Plugin {
8086
@Override
8187
public List<Setting<?>> getSettings() {

x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/AbstractEsqlIntegTestCase.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,14 @@ protected Collection<Class<? extends Plugin>> nodePlugins() {
139139
return CollectionUtils.appendToCopy(super.nodePlugins(), EsqlPlugin.class);
140140
}
141141

142+
@Override
143+
protected Settings nodeSettings(int nodeOrdinal, Settings otherSettings) {
144+
return Settings.builder()
145+
.put(super.nodeSettings(nodeOrdinal, otherSettings))
146+
.put(EsqlPlugin.QUERY_ALLOW_PARTIAL_RESULTS.getKey(), false)
147+
.build();
148+
}
149+
142150
protected void setRequestCircuitBreakerLimit(ByteSizeValue limit) {
143151
if (limit != null) {
144152
assertAcked(

x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/CrossClusterCancellationIT.java

Lines changed: 7 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,20 @@
1414
import org.elasticsearch.action.index.IndexRequest;
1515
import org.elasticsearch.action.support.PlainActionFuture;
1616
import org.elasticsearch.action.support.WriteRequest;
17-
import org.elasticsearch.common.settings.Setting;
17+
import org.elasticsearch.common.settings.Settings;
1818
import org.elasticsearch.common.transport.TransportAddress;
1919
import org.elasticsearch.compute.operator.DriverTaskRunner;
2020
import org.elasticsearch.compute.operator.exchange.ExchangeService;
2121
import org.elasticsearch.core.TimeValue;
22-
import org.elasticsearch.plugins.Plugin;
2322
import org.elasticsearch.tasks.TaskCancelledException;
2423
import org.elasticsearch.tasks.TaskInfo;
25-
import org.elasticsearch.test.AbstractMultiClustersTestCase;
2624
import org.elasticsearch.transport.TransportService;
2725
import org.elasticsearch.xcontent.XContentBuilder;
2826
import org.elasticsearch.xcontent.json.JsonXContent;
2927
import org.elasticsearch.xpack.esql.EsqlTestUtils;
3028
import org.elasticsearch.xpack.esql.plugin.ComputeService;
31-
import org.junit.After;
32-
import org.junit.Before;
3329

3430
import java.util.ArrayList;
35-
import java.util.Collection;
3631
import java.util.List;
3732
import java.util.concurrent.TimeUnit;
3833

@@ -44,7 +39,7 @@
4439
import static org.hamcrest.Matchers.hasSize;
4540
import static org.hamcrest.Matchers.instanceOf;
4641

47-
public class CrossClusterCancellationIT extends AbstractMultiClustersTestCase {
42+
public class CrossClusterCancellationIT extends AbstractCrossClusterTestCase {
4843
private static final String REMOTE_CLUSTER = "cluster-a";
4944

5045
@Override
@@ -53,35 +48,11 @@ protected List<String> remoteClusterAlias() {
5348
}
5449

5550
@Override
56-
protected Collection<Class<? extends Plugin>> nodePlugins(String clusterAlias) {
57-
List<Class<? extends Plugin>> plugins = new ArrayList<>(super.nodePlugins(clusterAlias));
58-
plugins.add(EsqlPluginWithEnterpriseOrTrialLicense.class);
59-
plugins.add(InternalExchangePlugin.class);
60-
plugins.add(SimplePauseFieldPlugin.class);
61-
return plugins;
62-
}
63-
64-
public static class InternalExchangePlugin extends Plugin {
65-
@Override
66-
public List<Setting<?>> getSettings() {
67-
return List.of(
68-
Setting.timeSetting(
69-
ExchangeService.INACTIVE_SINKS_INTERVAL_SETTING,
70-
TimeValue.timeValueMillis(between(3000, 4000)),
71-
Setting.Property.NodeScope
72-
)
73-
);
74-
}
75-
}
76-
77-
@Before
78-
public void resetPlugin() {
79-
SimplePauseFieldPlugin.resetPlugin();
80-
}
81-
82-
@After
83-
public void releasePlugin() {
84-
SimplePauseFieldPlugin.release();
51+
protected Settings nodeSettings() {
52+
return Settings.builder()
53+
.put(super.nodeSettings())
54+
.put(ExchangeService.INACTIVE_SINKS_INTERVAL_SETTING, TimeValue.timeValueMillis(between(3000, 4000)))
55+
.build();
8556
}
8657

8758
@Override

x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/ManyShardsIT.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ protected Collection<Class<? extends Plugin>> nodePlugins() {
7171
@Override
7272
protected Settings nodeSettings(int nodeOrdinal, Settings otherSettings) {
7373
return Settings.builder()
74+
.put(super.nodeSettings(nodeOrdinal, otherSettings))
7475
.put(ExchangeService.INACTIVE_SINKS_INTERVAL_SETTING, TimeValue.timeValueMillis(between(3000, 5000)))
7576
.build();
7677
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/ClusterComputeHandler.java

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
package org.elasticsearch.xpack.esql.plugin;
99

10-
import org.elasticsearch.ExceptionsHelper;
1110
import org.elasticsearch.action.ActionListener;
1211
import org.elasticsearch.action.ActionListenerResponseHandler;
1312
import org.elasticsearch.action.OriginalIndices;
@@ -17,7 +16,6 @@
1716
import org.elasticsearch.compute.operator.exchange.ExchangeSourceHandler;
1817
import org.elasticsearch.core.Releasable;
1918
import org.elasticsearch.core.TimeValue;
20-
import org.elasticsearch.index.IndexNotFoundException;
2119
import org.elasticsearch.tasks.CancellableTask;
2220
import org.elasticsearch.tasks.Task;
2321
import org.elasticsearch.tasks.TaskCancelledException;
@@ -90,18 +88,12 @@ void startComputeOnRemoteCluster(
9088
if (receivedResults == false && EsqlCCSUtils.shouldIgnoreRuntimeError(executionInfo, clusterAlias, e)) {
9189
EsqlCCSUtils.markClusterWithFinalStateAndNoShards(executionInfo, clusterAlias, EsqlExecutionInfo.Cluster.Status.SKIPPED, e);
9290
l.onResponse(DriverCompletionInfo.EMPTY);
93-
} else if (configuration.allowPartialResults()
94-
&& (ExceptionsHelper.unwrapCause(e) instanceof IndexNotFoundException) == false) {
95-
EsqlCCSUtils.markClusterWithFinalStateAndNoShards(
96-
executionInfo,
97-
clusterAlias,
98-
EsqlExecutionInfo.Cluster.Status.PARTIAL,
99-
e
100-
);
101-
l.onResponse(DriverCompletionInfo.EMPTY);
102-
} else {
103-
l.onFailure(e);
104-
}
91+
} else if (configuration.allowPartialResults() && EsqlCCSUtils.canAllowPartial(e)) {
92+
EsqlCCSUtils.markClusterWithFinalStateAndNoShards(executionInfo, clusterAlias, EsqlExecutionInfo.Cluster.Status.PARTIAL, e);
93+
l.onResponse(DriverCompletionInfo.EMPTY);
94+
} else {
95+
l.onFailure(e);
96+
}
10597
});
10698
ExchangeService.openExchange(
10799
transportService,

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/ComputeService.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
package org.elasticsearch.xpack.esql.plugin;
99

10-
import org.elasticsearch.ExceptionsHelper;
1110
import org.elasticsearch.action.ActionListener;
1211
import org.elasticsearch.action.OriginalIndices;
1312
import org.elasticsearch.action.search.SearchRequest;
@@ -31,7 +30,6 @@
3130
import org.elasticsearch.core.Releasable;
3231
import org.elasticsearch.core.Releasables;
3332
import org.elasticsearch.core.Tuple;
34-
import org.elasticsearch.index.IndexNotFoundException;
3533
import org.elasticsearch.index.query.SearchExecutionContext;
3634
import org.elasticsearch.logging.LogManager;
3735
import org.elasticsearch.logging.Logger;
@@ -63,6 +61,7 @@
6361
import org.elasticsearch.xpack.esql.planner.LocalExecutionPlanner;
6462
import org.elasticsearch.xpack.esql.planner.PlannerUtils;
6563
import org.elasticsearch.xpack.esql.session.Configuration;
64+
import org.elasticsearch.xpack.esql.session.EsqlCCSUtils;
6665
import org.elasticsearch.xpack.esql.session.Result;
6766

6867
import java.util.ArrayList;
@@ -433,8 +432,7 @@ public void executePlan(
433432
);
434433
dataNodesListener.onResponse(r.getCompletionInfo());
435434
}, e -> {
436-
if (configuration.allowPartialResults()
437-
&& (ExceptionsHelper.unwrapCause(e) instanceof IndexNotFoundException) == false) {
435+
if (configuration.allowPartialResults() && EsqlCCSUtils.canAllowPartial(e)) {
438436
execInfo.swapCluster(
439437
LOCAL_CLUSTER,
440438
(k, v) -> new EsqlExecutionInfo.Cluster.Builder(v).setStatus(

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlPlugin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public class EsqlPlugin extends Plugin implements ActionPlugin {
107107

108108
public static final Setting<Boolean> QUERY_ALLOW_PARTIAL_RESULTS = Setting.boolSetting(
109109
"esql.query.allow_partial_results",
110-
false,
110+
true,
111111
Setting.Property.NodeScope,
112112
Setting.Property.Dynamic
113113
);

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlCCSUtils.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package org.elasticsearch.xpack.esql.session;
99

10+
import org.elasticsearch.ElasticsearchSecurityException;
1011
import org.elasticsearch.ExceptionsHelper;
1112
import org.elasticsearch.action.ActionListener;
1213
import org.elasticsearch.action.OriginalIndices;
@@ -17,6 +18,7 @@
1718
import org.elasticsearch.common.util.set.Sets;
1819
import org.elasticsearch.compute.operator.DriverCompletionInfo;
1920
import org.elasticsearch.core.Nullable;
21+
import org.elasticsearch.index.IndexNotFoundException;
2022
import org.elasticsearch.index.query.QueryBuilder;
2123
import org.elasticsearch.indices.IndicesExpressionGrouper;
2224
import org.elasticsearch.license.XPackLicenseState;
@@ -368,4 +370,16 @@ public static boolean shouldIgnoreRuntimeError(EsqlExecutionInfo executionInfo,
368370

369371
return ExceptionsHelper.isRemoteUnavailableException(e);
370372
}
373+
374+
/**
375+
* Check whether this exception can be tolerated when partial results are on, or should be treated as fatal.
376+
* @return true if the exception can be tolerated, false if it should be treated as fatal.
377+
*/
378+
public static boolean canAllowPartial(Exception e) {
379+
Throwable unwrapped = ExceptionsHelper.unwrapCause(e);
380+
if (unwrapped instanceof IndexNotFoundException || unwrapped instanceof ElasticsearchSecurityException) {
381+
return false;
382+
}
383+
return true;
384+
}
371385
}

0 commit comments

Comments
 (0)