Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f627d45
ESQL CCS metadata in responses is opt-in, off by default
quux00 Oct 9, 2024
30b65b4
Cleanup and updated end user API docs
quux00 Oct 9, 2024
9d619f4
Added includeCCSMetadata to EsqlExecutionInfo
quux00 Oct 9, 2024
587500e
Restored jdk-deprecated.txt
quux00 Oct 9, 2024
3afa679
PR feedback: fixed code comment
quux00 Oct 9, 2024
2cc8c38
Merge remote-tracking branch 'elastic/main' into esql-ccs/ccs-metadat…
quux00 Oct 9, 2024
03d37e5
Request.includeCCSMetadata passed into EsqlExecutionInfo ctor
quux00 Oct 9, 2024
ae46e37
Fixed failing MultiClustersIT. Adding randomization for include_ccs_m…
quux00 Oct 9, 2024
f487781
Merge remote-tracking branch 'elastic/main' into esql-ccs/ccs-metadat…
quux00 Oct 9, 2024
3e6849d
Updated the CrossCluster IT tests to set random values for includeCCS…
quux00 Oct 10, 2024
4cd2513
Merge remote-tracking branch 'elastic/main' into esql-ccs/ccs-metadat…
quux00 Oct 10, 2024
6703610
Added restriction that include_ccs_metadata can only be used with for…
quux00 Oct 10, 2024
b0807b8
Merge remote-tracking branch 'elastic/main' into esql-ccs/ccs-metadat…
quux00 Oct 10, 2024
6f7bb2a
PR feedback: include_ccs_metadata works with all XContent types, but …
quux00 Oct 10, 2024
ee53b6b
Merge remote-tracking branch 'elastic/main' into esql-ccs/ccs-metadat…
quux00 Oct 10, 2024
6da4841
PR feedback: Added include_ccs_metadata into RestEsqlIT
quux00 Oct 11, 2024
1191432
Merge remote-tracking branch 'elastic/main' into esql-ccs/ccs-metadat…
quux00 Oct 11, 2024
2d2dd09
Merge remote-tracking branch 'elastic/main' into esql-ccs/ccs-metadat…
quux00 Oct 11, 2024
f8ea2bf
Merge remote-tracking branch 'elastic/main' into esql-ccs/ccs-metadat…
quux00 Oct 11, 2024
e6d95bb
Merge remote-tracking branch 'elastic/main' into esql-ccs/ccs-metadat…
quux00 Oct 11, 2024
c35af29
Merge remote-tracking branch 'elastic/main' into esql-ccs/ccs-metadat…
quux00 Oct 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions docs/reference/esql/esql-across-clusters.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,10 @@ FROM *:my-index-000001
[[ccq-cluster-details]]
==== Cross-cluster metadata

ES|QL {ccs} responses include metadata about the search on each cluster when the response format is JSON.
Using the `"include_ccs_metadata": true` option, users can request that
ES|QL {ccs} responses include metadata about the search on each cluster (when the response format is JSON).
Here we show an example using the async search endpoint. {ccs-cap} metadata is also present in the synchronous
search endpoint.
search endpoint response when requested.

[source,console]
----
Expand All @@ -200,7 +201,8 @@ POST /_query/async?format=json
FROM my-index-000001,cluster_one:my-index-000001,cluster_two:my-index*
| STATS COUNT(http.response.status_code) BY user.id
| LIMIT 2
"""
""",
"include_ccs_metadata": true
}
----
// TEST[setup:my_index]
Expand Down Expand Up @@ -238,7 +240,7 @@ Which returns:
"(local)": { <4>
"status": "successful",
"indices": "blogs",
"took": 36, <5>
"took": 41, <5>
"_shards": { <6>
"total": 13,
"successful": 13,
Expand All @@ -260,7 +262,7 @@ Which returns:
"cluster_two": {
"status": "successful",
"indices": "cluster_two:my-index*",
"took": 41,
"took": 40,
"_shards": {
"total": 18,
"successful": 18,
Expand All @@ -286,17 +288,14 @@ it is identified as "(local)".
<5> How long (in milliseconds) the search took on each cluster. This can be useful to determine
which clusters have slower response times than others.
<6> The shard details for the search on that cluster, including a count of shards that were
skipped due to the can-match phase. Shards are skipped when they cannot have any matching data
skipped due to the can-match phase results. Shards are skipped when they cannot have any matching data
and therefore are not included in the full ES|QL query.


The cross-cluster metadata can be used to determine whether any data came back from a cluster.
For instance, in the query below, the wildcard expression for `cluster-two` did not resolve
to a concrete index (or indices). The cluster is, therefore, marked as 'skipped' and the total
number of shards searched is set to zero.
Since the other cluster did have a matching index, the search did not return an error, but
instead returned all the matching data it could find.


[source,console]
----
Expand All @@ -306,7 +305,8 @@ POST /_query/async?format=json
FROM cluster_one:my-index*,cluster_two:logs*
| STATS COUNT(http.response.status_code) BY user.id
| LIMIT 2
"""
""",
"include_ccs_metadata": true
}
----
// TEST[continued]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ static TransportVersion def(int id) {
public static final TransportVersion FAST_REFRESH_RCO = def(8_762_00_0);
public static final TransportVersion TEXT_SIMILARITY_RERANKER_QUERY_REWRITE = def(8_763_00_0);
public static final TransportVersion SIMULATE_INDEX_TEMPLATES_SUBSTITUTIONS = def(8_764_00_0);
public static final TransportVersion OPT_IN_ESQL_CCS_EXECUTION_INFO = def(8_765_00_0);

/*
* STOP! READ THIS FIRST! No, really,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,10 @@ void indexDocs(RestClient client, String index, List<Doc> docs) throws IOExcepti
refresh(client, index);
}

private Map<String, Object> run(String query) throws IOException {
Map<String, Object> resp = runEsql(new RestEsqlTestCase.RequestObjectBuilder().query(query).build());
private Map<String, Object> run(String query, boolean includeCCSMetadata) throws IOException {
Map<String, Object> resp = runEsql(
new RestEsqlTestCase.RequestObjectBuilder().query(query).includeCCSMetadata(includeCCSMetadata).build()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using include_ccs_metadata only on this IT test suite is not enough I think.
Users can use include_ccs_metadata on any kind of REST request (multi cluster, single cluster single node, multi-node, mixed version, json/txt/tsv formats). I would, also, double check that "columnar": true (see this one in docs) still works with this new addition to the JSON response.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would, also, double check that "columnar": true still works with this new addition to the JSON response.

It does. I tested it manually and I have added a test to MultiClustersIT that uses columnar format with include_ccs_metadata=true and it is passing.

I think include_ccs_metadata parameter is of the same type: it can only be used for json format and I think we need a similar check

OK, I can add this restriction. If later we want to support it with SMILE, YAML and CBOR we can relax it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added a restriction that include_ccs_metadata can only be used with format=JSON. Tests have been added / updated to match this restriction.

);
logger.info("--> query {} response {}", query, resp);
return resp;
}
Expand All @@ -147,71 +149,71 @@ private Map<String, Object> runEsql(RestEsqlTestCase.RequestObjectBuilder reques

public void testCount() throws Exception {
{
Map<String, Object> result = run("FROM test-local-index,*:test-remote-index | STATS c = COUNT(*)");
boolean includeCCSMetadata = randomBoolean();
Map<String, Object> result = run("FROM test-local-index,*:test-remote-index | STATS c = COUNT(*)", includeCCSMetadata);
var columns = List.of(Map.of("name", "c", "type", "long"));
var values = List.of(List.of(localDocs.size() + remoteDocs.size()));

MapMatcher mapMatcher = matchesMap();
assertMap(
result,
mapMatcher.entry("columns", columns)
.entry("values", values)
.entry("took", greaterThanOrEqualTo(0))
.entry("_clusters", any(Map.class))
);
assertClusterDetailsMap(result, false);
if (includeCCSMetadata) {
mapMatcher = mapMatcher.entry("_clusters", any(Map.class));
}
assertMap(result, mapMatcher.entry("columns", columns).entry("values", values).entry("took", greaterThanOrEqualTo(0)));
if (includeCCSMetadata) {
assertClusterDetailsMap(result, false);
}
}
{
Map<String, Object> result = run("FROM *:test-remote-index | STATS c = COUNT(*)");
boolean includeCCSMetadata = randomBoolean();
Map<String, Object> result = run("FROM *:test-remote-index | STATS c = COUNT(*)", includeCCSMetadata);
var columns = List.of(Map.of("name", "c", "type", "long"));
var values = List.of(List.of(remoteDocs.size()));

MapMatcher mapMatcher = matchesMap();
assertMap(
result,
mapMatcher.entry("columns", columns)
.entry("values", values)
.entry("took", greaterThanOrEqualTo(0))
.entry("_clusters", any(Map.class))
);
assertClusterDetailsMap(result, true);
if (includeCCSMetadata) {
mapMatcher = mapMatcher.entry("_clusters", any(Map.class));
}
assertMap(result, mapMatcher.entry("columns", columns).entry("values", values).entry("took", greaterThanOrEqualTo(0)));
if (includeCCSMetadata) {
assertClusterDetailsMap(result, true);
}
}
}

public void testUngroupedAggs() throws Exception {
{
Map<String, Object> result = run("FROM test-local-index,*:test-remote-index | STATS total = SUM(data)");
boolean includeCCSMetadata = randomBoolean();
Map<String, Object> result = run("FROM test-local-index,*:test-remote-index | STATS total = SUM(data)", includeCCSMetadata);
var columns = List.of(Map.of("name", "total", "type", "long"));
long sum = Stream.concat(localDocs.stream(), remoteDocs.stream()).mapToLong(d -> d.data).sum();
var values = List.of(List.of(Math.toIntExact(sum)));

// check all sections of map except _cluster/details
MapMatcher mapMatcher = matchesMap();
assertMap(
result,
mapMatcher.entry("columns", columns)
.entry("values", values)
.entry("took", greaterThanOrEqualTo(0))
.entry("_clusters", any(Map.class))
);
assertClusterDetailsMap(result, false);
if (includeCCSMetadata) {
mapMatcher = mapMatcher.entry("_clusters", any(Map.class));
}
assertMap(result, mapMatcher.entry("columns", columns).entry("values", values).entry("took", greaterThanOrEqualTo(0)));
if (includeCCSMetadata) {
assertClusterDetailsMap(result, false);
}
}
{
Map<String, Object> result = run("FROM *:test-remote-index | STATS total = SUM(data)");
boolean includeCCSMetadata = randomBoolean();
Map<String, Object> result = run("FROM *:test-remote-index | STATS total = SUM(data)", includeCCSMetadata);
var columns = List.of(Map.of("name", "total", "type", "long"));
long sum = remoteDocs.stream().mapToLong(d -> d.data).sum();
var values = List.of(List.of(Math.toIntExact(sum)));

// check all sections of map except _cluster/details
MapMatcher mapMatcher = matchesMap();
assertMap(
result,
mapMatcher.entry("columns", columns)
.entry("values", values)
.entry("took", greaterThanOrEqualTo(0))
.entry("_clusters", any(Map.class))
);
assertClusterDetailsMap(result, true);
if (includeCCSMetadata) {
mapMatcher = mapMatcher.entry("_clusters", any(Map.class));
}
assertMap(result, mapMatcher.entry("columns", columns).entry("values", values).entry("took", greaterThanOrEqualTo(0)));
if (includeCCSMetadata) {
assertClusterDetailsMap(result, true);
}
}
}

Expand Down Expand Up @@ -269,7 +271,11 @@ private void assertClusterDetailsMap(Map<String, Object> result, boolean remoteO

public void testGroupedAggs() throws Exception {
{
Map<String, Object> result = run("FROM test-local-index,*:test-remote-index | STATS total = SUM(data) BY color | SORT color");
boolean includeCCSMetadata = randomBoolean();
Map<String, Object> result = run(
"FROM test-local-index,*:test-remote-index | STATS total = SUM(data) BY color | SORT color",
includeCCSMetadata
);
var columns = List.of(Map.of("name", "total", "type", "long"), Map.of("name", "color", "type", "keyword"));
var values = Stream.concat(localDocs.stream(), remoteDocs.stream())
.collect(Collectors.toMap(d -> d.color, Doc::data, Long::sum))
Expand All @@ -280,17 +286,20 @@ public void testGroupedAggs() throws Exception {
.toList();

MapMatcher mapMatcher = matchesMap();
assertMap(
result,
mapMatcher.entry("columns", columns)
.entry("values", values)
.entry("took", greaterThanOrEqualTo(0))
.entry("_clusters", any(Map.class))
);
assertClusterDetailsMap(result, false);
if (includeCCSMetadata) {
mapMatcher = mapMatcher.entry("_clusters", any(Map.class));
}
assertMap(result, mapMatcher.entry("columns", columns).entry("values", values).entry("took", greaterThanOrEqualTo(0)));
if (includeCCSMetadata) {
assertClusterDetailsMap(result, false);
}
}
{
Map<String, Object> result = run("FROM *:test-remote-index | STATS total = SUM(data) by color | SORT color");
boolean includeCCSMetadata = randomBoolean();
Map<String, Object> result = run(
"FROM *:test-remote-index | STATS total = SUM(data) by color | SORT color",
includeCCSMetadata
);
var columns = List.of(Map.of("name", "total", "type", "long"), Map.of("name", "color", "type", "keyword"));
var values = remoteDocs.stream()
.collect(Collectors.toMap(d -> d.color, Doc::data, Long::sum))
Expand All @@ -300,16 +309,15 @@ public void testGroupedAggs() throws Exception {
.map(e -> List.of(Math.toIntExact(e.getValue()), e.getKey()))
.toList();

// check all sections of map except _cluster/details
// check all sections of map except _clusters/details
MapMatcher mapMatcher = matchesMap();
assertMap(
result,
mapMatcher.entry("columns", columns)
.entry("values", values)
.entry("took", greaterThanOrEqualTo(0))
.entry("_clusters", any(Map.class))
);
assertClusterDetailsMap(result, true);
if (includeCCSMetadata) {
mapMatcher = mapMatcher.entry("_clusters", any(Map.class));
}
assertMap(result, mapMatcher.entry("columns", columns).entry("values", values).entry("took", greaterThanOrEqualTo(0)));
if (includeCCSMetadata) {
assertClusterDetailsMap(result, true);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ public static class RequestObjectBuilder {
private Boolean keepOnCompletion = null;

private Boolean profile = null;
private Boolean includeCCSMetadata = null;

private CheckedConsumer<XContentBuilder, IOException> filter;

Expand Down Expand Up @@ -196,6 +197,11 @@ public RequestObjectBuilder profile(boolean profile) {
return this;
}

public RequestObjectBuilder includeCCSMetadata(boolean includeCCSMetadata) {
this.includeCCSMetadata = includeCCSMetadata;
return this;
}

public RequestObjectBuilder filter(CheckedConsumer<XContentBuilder, IOException> filter) {
this.filter = filter;
return this;
Expand All @@ -219,6 +225,9 @@ public RequestObjectBuilder build() throws IOException {
if (profile != null) {
builder.field("profile", profile);
}
if (includeCCSMetadata != null) {
builder.field("include_ccs_metadata", includeCCSMetadata);
}
if (filter != null) {
builder.startObject("filter");
filter.accept(builder);
Expand Down
Loading