Skip to content

Commit 4af408d

Browse files
EQL: add support for partial search results (#116388)
Allow queries to succeed if some shards are failing
1 parent 8dc8854 commit 4af408d

File tree

65 files changed

+3068
-130
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+3068
-130
lines changed

docs/changelog/116388.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 116388
2+
summary: Add support for partial shard results
3+
area: EQL
4+
type: enhancement
5+
issues: []

docs/reference/eql/eql-search-api.asciidoc

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,53 @@ request that targets only `bar*` still returns an error.
8888
+
8989
Defaults to `true`.
9090

91+
`allow_partial_search_results`::
92+
(Optional, Boolean)
93+
94+
If `false`, the request returns an error if one or more shards involved in the query are unavailable.
95+
+
96+
If `true`, the query is executed only on the available shards, ignoring shard request timeouts and
97+
<<shard-failures,shard failures>>.
98+
+
99+
Defaults to `false`.
100+
+
101+
To override the default for this field, set the
102+
`xpack.eql.default_allow_partial_results` cluster setting to `true`.
103+
104+
105+
[IMPORTANT]
106+
====
107+
You can also specify this value using the `allow_partial_search_results` request body parameter.
108+
If both parameters are specified, only the query parameter is used.
109+
====
110+
111+
112+
`allow_partial_sequence_results`::
113+
(Optional, Boolean)
114+
115+
116+
Used together with `allow_partial_search_results=true`, controls the behavior of sequence queries specifically
117+
(if `allow_partial_search_results=false`, this setting has no effect).
118+
If `true` and if some shards are unavailable, the sequences are calculated on available shards only.
119+
+
120+
If `false` and if some shards are unavailable, the query only returns information about the shard failures,
121+
but no further results.
122+
+
123+
Defaults to `false`.
124+
+
125+
Consider that sequences calculated with `allow_partial_search_results=true` can return incorrect results
126+
(eg. if a <<eql-missing-events, missing event>> clause matches records in unavailable shards)
127+
+
128+
To override the default for this field, set the
129+
`xpack.eql.default_allow_partial_sequence_results` cluster setting to `true`.
130+
131+
132+
[IMPORTANT]
133+
====
134+
You can also specify this value using the `allow_partial_sequence_results` request body parameter.
135+
If both parameters are specified, only the query parameter is used.
136+
====
137+
91138
`ccs_minimize_roundtrips`::
92139
(Optional, Boolean) If `true`, network round-trips between the local and the
93140
remote cluster are minimized when running cross-cluster search (CCS) requests.

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@
4141
"type": "time",
4242
"description": "Update the time interval in which the results (partial or final) for this search will be available",
4343
"default": "5d"
44+
},
45+
"allow_partial_search_results": {
46+
"type":"boolean",
47+
"description":"Control whether the query should keep running in case of shard failures, and return partial results",
48+
"default":false
49+
},
50+
"allow_partial_sequence_results": {
51+
"type":"boolean",
52+
"description":"Control whether a sequence query should return partial results or no results at all in case of shard failures. This option has effect only if [allow_partial_search_results] is true.",
53+
"default":false
4454
}
4555
},
4656
"body":{

server/src/main/java/org/elasticsearch/TransportVersions.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ static TransportVersion def(int id) {
146146
public static final TransportVersion KNN_QUERY_RESCORE_OVERSAMPLE = def(8_806_00_0);
147147
public static final TransportVersion SEMANTIC_QUERY_LENIENT = def(8_807_00_0);
148148
public static final TransportVersion ESQL_QUERY_BUILDER_IN_SEARCH_FUNCTIONS = def(8_808_00_0);
149+
public static final TransportVersion EQL_ALLOW_PARTIAL_SEARCH_RESULTS = def(8_809_00_0);
149150

150151
/*
151152
* STOP! READ THIS FIRST! No, really,

x-pack/plugin/eql/qa/common/src/main/java/org/elasticsearch/test/eql/BaseEqlSpecTestCase.java

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
import java.util.function.Function;
3434
import java.util.stream.Collectors;
3535

36+
import static org.hamcrest.Matchers.greaterThan;
37+
import static org.hamcrest.Matchers.is;
38+
3639
public abstract class BaseEqlSpecTestCase extends RemoteClusterAwareEqlRestTestCase {
3740

3841
protected static final String PARAM_FORMATTING = "%2$s";
@@ -52,6 +55,9 @@ public abstract class BaseEqlSpecTestCase extends RemoteClusterAwareEqlRestTestC
5255
*/
5356
private final int size;
5457
private final int maxSamplesPerKey;
58+
private final Boolean allowPartialSearchResults;
59+
private final Boolean allowPartialSequenceResults;
60+
private final Boolean expectShardFailures;
5561

5662
@Before
5763
public void setup() throws Exception {
@@ -104,7 +110,16 @@ protected static List<Object[]> asArray(List<EqlSpec> specs) {
104110
}
105111

106112
results.add(
107-
new Object[] { spec.query(), name, spec.expectedEventIds(), spec.joinKeys(), spec.size(), spec.maxSamplesPerKey() }
113+
new Object[] {
114+
spec.query(),
115+
name,
116+
spec.expectedEventIds(),
117+
spec.joinKeys(),
118+
spec.size(),
119+
spec.maxSamplesPerKey(),
120+
spec.allowPartialSearchResults(),
121+
spec.allowPartialSequenceResults(),
122+
spec.expectShardFailures() }
108123
);
109124
}
110125

@@ -118,7 +133,10 @@ protected static List<Object[]> asArray(List<EqlSpec> specs) {
118133
List<long[]> eventIds,
119134
String[] joinKeys,
120135
Integer size,
121-
Integer maxSamplesPerKey
136+
Integer maxSamplesPerKey,
137+
Boolean allowPartialSearchResults,
138+
Boolean allowPartialSequenceResults,
139+
Boolean expectShardFailures
122140
) {
123141
this.index = index;
124142

@@ -128,6 +146,9 @@ protected static List<Object[]> asArray(List<EqlSpec> specs) {
128146
this.joinKeys = joinKeys;
129147
this.size = size == null ? -1 : size;
130148
this.maxSamplesPerKey = maxSamplesPerKey == null ? -1 : maxSamplesPerKey;
149+
this.allowPartialSearchResults = allowPartialSearchResults;
150+
this.allowPartialSequenceResults = allowPartialSequenceResults;
151+
this.expectShardFailures = expectShardFailures;
131152
}
132153

133154
public void test() throws Exception {
@@ -137,6 +158,7 @@ public void test() throws Exception {
137158
private void assertResponse(ObjectPath response) throws Exception {
138159
List<Map<String, Object>> events = response.evaluate("hits.events");
139160
List<Map<String, Object>> sequences = response.evaluate("hits.sequences");
161+
Object shardFailures = response.evaluate("shard_failures");
140162

141163
if (events != null) {
142164
assertEvents(events);
@@ -145,6 +167,7 @@ private void assertResponse(ObjectPath response) throws Exception {
145167
} else {
146168
fail("No events or sequences found");
147169
}
170+
assertShardFailures(shardFailures);
148171
}
149172

150173
protected ObjectPath runQuery(String index, String query) throws Exception {
@@ -163,13 +186,56 @@ protected ObjectPath runQuery(String index, String query) throws Exception {
163186
if (maxSamplesPerKey > 0) {
164187
builder.field("max_samples_per_key", maxSamplesPerKey);
165188
}
189+
boolean allowPartialResultsInBody = randomBoolean();
190+
if (allowPartialSearchResults != null) {
191+
if (allowPartialResultsInBody) {
192+
builder.field("allow_partial_search_results", String.valueOf(allowPartialSearchResults));
193+
if (allowPartialSequenceResults != null) {
194+
builder.field("allow_partial_sequence_results", String.valueOf(allowPartialSequenceResults));
195+
}
196+
} else {
197+
// these will be overwritten by the path params, that have higher priority than the query (JSON body) params
198+
if (allowPartialSearchResults != null) {
199+
builder.field("allow_partial_search_results", randomBoolean());
200+
}
201+
if (allowPartialSequenceResults != null) {
202+
builder.field("allow_partial_sequence_results", randomBoolean());
203+
}
204+
}
205+
} else {
206+
// Tests that don't specify a setting for these parameters should always pass.
207+
// These params should be irrelevant.
208+
if (randomBoolean()) {
209+
builder.field("allow_partial_search_results", randomBoolean());
210+
}
211+
if (randomBoolean()) {
212+
builder.field("allow_partial_sequence_results", randomBoolean());
213+
}
214+
}
166215
builder.endObject();
167216

168217
Request request = new Request("POST", "/" + index + "/_eql/search");
169218
Boolean ccsMinimizeRoundtrips = ccsMinimizeRoundtrips();
170219
if (ccsMinimizeRoundtrips != null) {
171220
request.addParameter("ccs_minimize_roundtrips", ccsMinimizeRoundtrips.toString());
172221
}
222+
if (allowPartialSearchResults != null) {
223+
if (allowPartialResultsInBody == false) {
224+
request.addParameter("allow_partial_search_results", String.valueOf(allowPartialSearchResults));
225+
if (allowPartialSequenceResults != null) {
226+
request.addParameter("allow_partial_sequence_results", String.valueOf(allowPartialSequenceResults));
227+
}
228+
}
229+
} else {
230+
// Tests that don't specify a setting for these parameters should always pass.
231+
// These params should be irrelevant.
232+
if (randomBoolean()) {
233+
request.addParameter("allow_partial_search_results", String.valueOf(randomBoolean()));
234+
}
235+
if (randomBoolean()) {
236+
request.addParameter("allow_partial_sequence_results", String.valueOf(randomBoolean()));
237+
}
238+
}
173239
int timeout = Math.toIntExact(timeout().millis());
174240
RequestConfig config = RequestConfig.copy(RequestConfig.DEFAULT)
175241
.setConnectionRequestTimeout(timeout)
@@ -182,6 +248,20 @@ protected ObjectPath runQuery(String index, String query) throws Exception {
182248
return ObjectPath.createFromResponse(client().performRequest(request));
183249
}
184250

251+
private void assertShardFailures(Object shardFailures) {
252+
if (expectShardFailures != null) {
253+
if (expectShardFailures) {
254+
assertNotNull(shardFailures);
255+
List<?> list = (List<?>) shardFailures;
256+
assertThat(list.size(), is(greaterThan(0)));
257+
} else {
258+
assertNull(shardFailures);
259+
}
260+
} else {
261+
assertNull(shardFailures);
262+
}
263+
}
264+
185265
private void assertEvents(List<Map<String, Object>> events) {
186266
assertNotNull(events);
187267
logger.debug("Events {}", new Object() {

x-pack/plugin/eql/qa/common/src/main/java/org/elasticsearch/test/eql/DataLoader.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
*/
5353
public class DataLoader {
5454
public static final String TEST_INDEX = "endgame-140";
55+
public static final String TEST_SHARD_FAILURES_INDEX = "endgame-shard-failures";
5556
public static final String TEST_EXTRA_INDEX = "extra";
5657
public static final String TEST_NANOS_INDEX = "endgame-140-nanos";
5758
public static final String TEST_SAMPLE = "sample1,sample2,sample3";
@@ -103,6 +104,11 @@ public static void loadDatasetIntoEs(RestClient client, CheckedBiFunction<XConte
103104
//
104105
load(client, TEST_MISSING_EVENTS_INDEX, null, null, p);
105106
load(client, TEST_SAMPLE_MULTI, null, null, p);
107+
//
108+
// index with a runtime field ("broken", type long) that causes shard failures.
109+
// the rest of the mapping is the same as TEST_INDEX
110+
//
111+
load(client, TEST_SHARD_FAILURES_INDEX, null, DataLoader::timestampToUnixMillis, p);
106112
}
107113

108114
private static void load(

x-pack/plugin/eql/qa/common/src/main/java/org/elasticsearch/test/eql/EqlDateNanosSpecTestCase.java

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,23 @@ public EqlDateNanosSpecTestCase(
2727
List<long[]> eventIds,
2828
String[] joinKeys,
2929
Integer size,
30-
Integer maxSamplesPerKey
30+
Integer maxSamplesPerKey,
31+
Boolean allowPartialSearchResults,
32+
Boolean allowPartialSequenceResults,
33+
Boolean expectShardFailures
3134
) {
32-
this(TEST_NANOS_INDEX, query, name, eventIds, joinKeys, size, maxSamplesPerKey);
35+
this(
36+
TEST_NANOS_INDEX,
37+
query,
38+
name,
39+
eventIds,
40+
joinKeys,
41+
size,
42+
maxSamplesPerKey,
43+
allowPartialSearchResults,
44+
allowPartialSequenceResults,
45+
expectShardFailures
46+
);
3347
}
3448

3549
// constructor for multi-cluster tests
@@ -40,9 +54,23 @@ public EqlDateNanosSpecTestCase(
4054
List<long[]> eventIds,
4155
String[] joinKeys,
4256
Integer size,
43-
Integer maxSamplesPerKey
57+
Integer maxSamplesPerKey,
58+
Boolean allowPartialSearchResults,
59+
Boolean allowPartialSequenceResults,
60+
Boolean expectShardFailures
4461
) {
45-
super(index, query, name, eventIds, joinKeys, size, maxSamplesPerKey);
62+
super(
63+
index,
64+
query,
65+
name,
66+
eventIds,
67+
joinKeys,
68+
size,
69+
maxSamplesPerKey,
70+
allowPartialSearchResults,
71+
allowPartialSequenceResults,
72+
expectShardFailures
73+
);
4674
}
4775

4876
@Override

x-pack/plugin/eql/qa/common/src/main/java/org/elasticsearch/test/eql/EqlExtraSpecTestCase.java

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,23 @@ public EqlExtraSpecTestCase(
2727
List<long[]> eventIds,
2828
String[] joinKeys,
2929
Integer size,
30-
Integer maxSamplesPerKey
30+
Integer maxSamplesPerKey,
31+
Boolean allowPartialSearchResults,
32+
Boolean allowPartialSequenceResults,
33+
Boolean expectShardFailures
3134
) {
32-
this(TEST_EXTRA_INDEX, query, name, eventIds, joinKeys, size, maxSamplesPerKey);
35+
this(
36+
TEST_EXTRA_INDEX,
37+
query,
38+
name,
39+
eventIds,
40+
joinKeys,
41+
size,
42+
maxSamplesPerKey,
43+
allowPartialSearchResults,
44+
allowPartialSequenceResults,
45+
expectShardFailures
46+
);
3347
}
3448

3549
// constructor for multi-cluster tests
@@ -40,9 +54,23 @@ public EqlExtraSpecTestCase(
4054
List<long[]> eventIds,
4155
String[] joinKeys,
4256
Integer size,
43-
Integer maxSamplesPerKey
57+
Integer maxSamplesPerKey,
58+
Boolean allowPartialSearchResults,
59+
Boolean allowPartialSequenceResults,
60+
Boolean expectShardFailures
4461
) {
45-
super(index, query, name, eventIds, joinKeys, size, maxSamplesPerKey);
62+
super(
63+
index,
64+
query,
65+
name,
66+
eventIds,
67+
joinKeys,
68+
size,
69+
maxSamplesPerKey,
70+
allowPartialSearchResults,
71+
allowPartialSequenceResults,
72+
expectShardFailures
73+
);
4674
}
4775

4876
@Override

0 commit comments

Comments
 (0)