Skip to content

Commit 0acda3a

Browse files
authored
ESQL: Add documents_found and values_loaded (#125631) (#130029)
This adds `documents_found` and `values_loaded` to the to the ESQL response: ```json { "took" : 194, "is_partial" : false, "documents_found" : 100000, "values_loaded" : 200000, "columns" : [ { "name" : "a", "type" : "long" }, { "name" : "b", "type" : "long" } ], "values" : [[10, 1]] } ``` These are cheap enough to collect that we can do it for every query and return it with every response. It's small, but it still gives you a reasonable sense of how much work Elasticsearch had to go through to perform the query. I've also added these two fields to the driver profile and task status: ```json "drivers" : [ { "description" : "data", "cluster_name" : "runTask", "node_name" : "runTask-0", "start_millis" : 1742923173077, "stop_millis" : 1742923173087, "took_nanos" : 9557014, "cpu_nanos" : 9091340, "documents_found" : 5, <---- THESE "values_loaded" : 15, <---- THESE "iterations" : 6, ... ``` These are at a high level and should be easy to reason about. We'd like to extract this into a "show me how difficult this running query is" API one day. But today, just plumbing it into the debugging output is good. Any `Operator` can claim to "find documents" or "load values" by overriding a method on its `Operator.Status` implementation: ```java /** * The number of documents found by this operator. Most operators * don't find documents and will return {@code 0} here. */ default long documentsFound() { return 0; } /** * The number of values loaded by this operator. Most operators * don't load values and will return {@code 0} here. */ default long valuesLoaded() { return 0; } ``` In this PR all of the `LuceneOperator`s declare that each `position` they emit is a "document found" and the `ValuesSourceValuesSourceReaderOperator` says each value it makes is a "value loaded". That's pretty pretty much true. The `LuceneCountOperator` and `LuceneMinMaxOperator` sort of pretend that the count/min/max that they emit is a "document" - but that's good enough to give you a sense of what's going on. It's *like* document.
1 parent a0579d5 commit 0acda3a

File tree

57 files changed

+1168
-359
lines changed

Some content is hidden

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

57 files changed

+1168
-359
lines changed

docs/changelog/125631.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 125631
2+
summary: Add `documents_found` and `values_loaded`
3+
area: ES|QL
4+
type: enhancement
5+
issues: []

docs/reference/esql/esql-rest.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ Which returns:
194194
{
195195
"took": 28,
196196
"is_partial": false,
197+
"documents_found": 5,
198+
"values_loaded": 20,
197199
"columns": [
198200
{"name": "author", "type": "text"},
199201
{"name": "name", "type": "text"},

docs/reference/esql/functions/description/knn.asciidoc

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/examples/knn.asciidoc

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/functionNamedParams/knn.asciidoc

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/functionNamedParams/to_ip.asciidoc

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/layout/knn.asciidoc

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/parameters/knn.asciidoc

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/functions/signature/knn.svg

Lines changed: 1 addition & 0 deletions
Loading

docs/reference/esql/functions/types/knn.asciidoc

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/esql/multivalued-fields.asciidoc

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ Multivalued fields come back as a JSON array:
2828
{
2929
"took": 28,
3030
"is_partial": false,
31+
"documents_found": 2,
32+
"values_loaded": 5,
3133
"columns": [
3234
{ "name": "a", "type": "long"},
3335
{ "name": "b", "type": "long"}
@@ -80,6 +82,8 @@ And {esql} sees that removal:
8082
{
8183
"took": 28,
8284
"is_partial": false,
85+
"documents_found": 2,
86+
"values_loaded": 5,
8387
"columns": [
8488
{ "name": "a", "type": "long"},
8589
{ "name": "b", "type": "keyword"}
@@ -125,6 +129,8 @@ And {esql} also sees that:
125129
{
126130
"took": 28,
127131
"is_partial": false,
132+
"documents_found": 2,
133+
"values_loaded": 7,
128134
"columns": [
129135
{ "name": "a", "type": "long"},
130136
{ "name": "b", "type": "long"}
@@ -169,6 +175,8 @@ POST /_query
169175
{
170176
"took": 28,
171177
"is_partial": false,
178+
"documents_found": 2,
179+
"values_loaded": 7,
172180
"columns": [
173181
{ "name": "a", "type": "long"},
174182
{ "name": "b", "type": "keyword"}
@@ -203,6 +211,8 @@ POST /_query
203211
{
204212
"took": 28,
205213
"is_partial": false,
214+
"documents_found": 1,
215+
"values_loaded": 2,
206216
"columns": [
207217
{ "name": "a", "type": "long"},
208218
],
@@ -247,6 +257,8 @@ POST /_query
247257
{
248258
"took": 28,
249259
"is_partial": false,
260+
"documents_found": 2,
261+
"values_loaded": 5,
250262
"columns": [
251263
{ "name": "a", "type": "long"},
252264
{ "name": "b", "type": "long"},
@@ -271,7 +283,7 @@ Work around this limitation by converting the field to single value with one of:
271283
* <<esql-mv_min>>
272284
* <<esql-mv_sum>>
273285

274-
[source,console,esql-multivalued-fields-mv-into-null]
286+
[source,console,esql-multivalued-fields-mv-min]
275287
----
276288
POST /_query
277289
{
@@ -285,6 +297,8 @@ POST /_query
285297
{
286298
"took": 28,
287299
"is_partial": false,
300+
"documents_found": 2,
301+
"values_loaded": 5,
288302
"columns": [
289303
{ "name": "a", "type": "long"},
290304
{ "name": "b", "type": "long"},

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ static TransportVersion def(int id) {
253253
public static final TransportVersion SPARSE_VECTOR_FIELD_PRUNING_OPTIONS_8_19 = def(8_841_0_58);
254254
public static final TransportVersion ML_INFERENCE_ELASTIC_DENSE_TEXT_EMBEDDINGS_ADDED_8_19 = def(8_841_0_59);
255255
public static final TransportVersion ML_INFERENCE_COHERE_API_VERSION_8_19 = def(8_841_0_60);
256+
public static final TransportVersion ESQL_DOCUMENTS_FOUND_AND_VALUES_LOADED_8_19 = def(8_841_0_61);
256257

257258
/*
258259
* STOP! READ THIS FIRST! No, really,

server/src/main/java/org/elasticsearch/common/Strings.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,7 @@ public static String toString(ChunkedToXContent chunkedToXContent, boolean prett
818818
* Allows to configure the params.
819819
* Allows to control whether the outputted json needs to be pretty printed and human readable.
820820
*/
821-
private static String toString(ToXContent toXContent, ToXContent.Params params, boolean pretty, boolean human) {
821+
public static String toString(ToXContent toXContent, ToXContent.Params params, boolean pretty, boolean human) {
822822
try {
823823
XContentBuilder builder = createBuilder(pretty, human);
824824
if (toXContent.isFragment()) {

test/framework/src/main/java/org/elasticsearch/test/rest/ESRestTestCase.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2704,8 +2704,13 @@ protected static MapMatcher getProfileMatcher() {
27042704
.entry("drivers", instanceOf(List.class));
27052705
}
27062706

2707-
protected static MapMatcher getResultMatcher(boolean includeMetadata, boolean includePartial) {
2707+
protected static MapMatcher getResultMatcher(boolean includeMetadata, boolean includePartial, boolean includeDocumentsFound) {
27082708
MapMatcher mapMatcher = matchesMap();
2709+
if (includeDocumentsFound) {
2710+
// Older versions may not return documents_found and values_loaded.
2711+
mapMatcher = mapMatcher.entry("documents_found", greaterThanOrEqualTo(0));
2712+
mapMatcher = mapMatcher.entry("values_loaded", greaterThanOrEqualTo(0));
2713+
}
27092714
if (includeMetadata) {
27102715
mapMatcher = mapMatcher.entry("took", greaterThanOrEqualTo(0));
27112716
}
@@ -2720,7 +2725,7 @@ protected static MapMatcher getResultMatcher(boolean includeMetadata, boolean in
27202725
* Create empty result matcher from result, taking into account all metadata items.
27212726
*/
27222727
protected static MapMatcher getResultMatcher(Map<String, Object> result) {
2723-
return getResultMatcher(result.containsKey("took"), result.containsKey("is_partial"));
2728+
return getResultMatcher(result.containsKey("took"), result.containsKey("is_partial"), result.containsKey("documents_found"));
27242729
}
27252730

27262731
/**

x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/CompositeBlock.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,11 @@ public int getPositionCount() {
8383

8484
@Override
8585
public int getTotalValueCount() {
86-
throw new UnsupportedOperationException("Composite block");
86+
int totalValueCount = 0;
87+
for (Block b : blocks) {
88+
totalValueCount += b.getTotalValueCount();
89+
}
90+
return totalValueCount;
8791
}
8892

8993
@Override

x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/LuceneOperator.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,11 @@ public Map<String, LuceneSliceQueue.PartitioningStrategy> partitioningStrategies
437437
return partitioningStrategies;
438438
}
439439

440+
@Override
441+
public long documentsFound() {
442+
return rowsEmitted;
443+
}
444+
440445
@Override
441446
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
442447
builder.startObject();

x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/ValuesSourceReaderOperator.java

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
import java.util.function.IntFunction;
4747
import java.util.function.Supplier;
4848

49+
import static org.elasticsearch.TransportVersions.ESQL_DOCUMENTS_FOUND_AND_VALUES_LOADED_8_19;
50+
4951
/**
5052
* Operator that extracts doc_values from a Lucene index out of pages that have been produced by {@link LuceneSourceOperator}
5153
* and outputs them to a new column.
@@ -112,6 +114,7 @@ public record ShardContext(IndexReader reader, Supplier<SourceLoader> newSourceL
112114
private final BlockFactory blockFactory;
113115

114116
private final Map<String, Integer> readersBuilt = new TreeMap<>();
117+
private long valuesLoaded;
115118

116119
int lastShard = -1;
117120
int lastSegment = -1;
@@ -158,6 +161,9 @@ public int get(int i) {
158161
}
159162
}
160163
success = true;
164+
for (Block b : blocks) {
165+
valuesLoaded += b.getTotalValueCount();
166+
}
161167
return page.appendBlocks(blocks);
162168
} catch (IOException e) {
163169
throw new UncheckedIOException(e);
@@ -548,7 +554,7 @@ public String toString() {
548554

549555
@Override
550556
protected Status status(long processNanos, int pagesProcessed, long rowsReceived, long rowsEmitted) {
551-
return new Status(new TreeMap<>(readersBuilt), processNanos, pagesProcessed, rowsReceived, rowsEmitted);
557+
return new Status(new TreeMap<>(readersBuilt), processNanos, pagesProcessed, rowsReceived, rowsEmitted, valuesLoaded);
552558
}
553559

554560
/**
@@ -593,21 +599,34 @@ public static class Status extends AbstractPageMappingOperator.Status {
593599
);
594600

595601
private final Map<String, Integer> readersBuilt;
596-
597-
Status(Map<String, Integer> readersBuilt, long processNanos, int pagesProcessed, long rowsReceived, long rowsEmitted) {
602+
private final long valuesLoaded;
603+
604+
Status(
605+
Map<String, Integer> readersBuilt,
606+
long processNanos,
607+
int pagesProcessed,
608+
long rowsReceived,
609+
long rowsEmitted,
610+
long valuesLoaded
611+
) {
598612
super(processNanos, pagesProcessed, rowsReceived, rowsEmitted);
599613
this.readersBuilt = readersBuilt;
614+
this.valuesLoaded = valuesLoaded;
600615
}
601616

602617
Status(StreamInput in) throws IOException {
603618
super(in);
604619
readersBuilt = in.readOrderedMap(StreamInput::readString, StreamInput::readVInt);
620+
valuesLoaded = in.getTransportVersion().onOrAfter(ESQL_DOCUMENTS_FOUND_AND_VALUES_LOADED_8_19) ? in.readVLong() : 0;
605621
}
606622

607623
@Override
608624
public void writeTo(StreamOutput out) throws IOException {
609625
super.writeTo(out);
610626
out.writeMap(readersBuilt, StreamOutput::writeVInt);
627+
if (out.getTransportVersion().onOrAfter(ESQL_DOCUMENTS_FOUND_AND_VALUES_LOADED_8_19)) {
628+
out.writeVLong(valuesLoaded);
629+
}
611630
}
612631

613632
@Override
@@ -619,6 +638,11 @@ public Map<String, Integer> readersBuilt() {
619638
return readersBuilt;
620639
}
621640

641+
@Override
642+
public long valuesLoaded() {
643+
return valuesLoaded;
644+
}
645+
622646
@Override
623647
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
624648
builder.startObject();
@@ -627,6 +651,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
627651
builder.field(e.getKey(), e.getValue());
628652
}
629653
builder.endObject();
654+
builder.field("values_loaded", valuesLoaded);
630655
innerToXContent(builder);
631656
return builder.endObject();
632657
}
@@ -635,12 +660,12 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
635660
public boolean equals(Object o) {
636661
if (super.equals(o) == false) return false;
637662
Status status = (Status) o;
638-
return readersBuilt.equals(status.readersBuilt);
663+
return readersBuilt.equals(status.readersBuilt) && valuesLoaded == status.valuesLoaded;
639664
}
640665

641666
@Override
642667
public int hashCode() {
643-
return Objects.hash(super.hashCode(), readersBuilt);
668+
return Objects.hash(super.hashCode(), readersBuilt, valuesLoaded);
644669
}
645670

646671
@Override
@@ -750,6 +775,4 @@ public BlockLoader.AggregateMetricDoubleBuilder aggregateMetricDoubleBuilder(int
750775
return factory.newAggregateMetricDoubleBlockBuilder(count);
751776
}
752777
}
753-
754-
// TODO tests that mix source loaded fields and doc values in the same block
755778
}

x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/Driver.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public class Driver implements Releasable, Describable {
7777
private final DriverContext driverContext;
7878
private final Supplier<String> description;
7979
private final List<Operator> activeOperators;
80-
private final List<DriverStatus.OperatorStatus> statusOfCompletedOperators = new ArrayList<>();
80+
private final List<OperatorStatus> statusOfCompletedOperators = new ArrayList<>();
8181
private final Releasable releasable;
8282
private final long statusNanos;
8383

@@ -343,7 +343,7 @@ private void closeEarlyFinishedOperators() {
343343
Iterator<Operator> itr = finishedOperators.iterator();
344344
while (itr.hasNext()) {
345345
Operator op = itr.next();
346-
statusOfCompletedOperators.add(new DriverStatus.OperatorStatus(op.toString(), op.status()));
346+
statusOfCompletedOperators.add(new OperatorStatus(op.toString(), op.status()));
347347
op.close();
348348
itr.remove();
349349
}
@@ -570,7 +570,7 @@ private void updateStatus(long extraCpuNanos, int extraIterations, DriverStatus.
570570
prev.iterations() + extraIterations,
571571
status,
572572
statusOfCompletedOperators,
573-
activeOperators.stream().map(op -> new DriverStatus.OperatorStatus(op.toString(), op.status())).toList(),
573+
activeOperators.stream().map(op -> new OperatorStatus(op.toString(), op.status())).toList(),
574574
sleeps
575575
);
576576
});

0 commit comments

Comments
 (0)