Skip to content

Commit 243e047

Browse files
committed
Chunk aggregation output pages
1 parent de09149 commit 243e047

File tree

9 files changed

+249
-82
lines changed

9 files changed

+249
-82
lines changed

benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/AggregatorBenchmark.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,11 @@ private static Operator operator(DriverContext driverContext, String grouping, S
181181
);
182182
default -> throw new IllegalArgumentException("unsupported grouping [" + grouping + "]");
183183
};
184+
int pageSize = 16 * 1024;
184185
return new HashAggregationOperator(
185186
List.of(supplier(op, dataType, filter, groups.size()).groupingAggregatorFactory(AggregatorMode.SINGLE)),
186-
() -> BlockHash.build(groups, driverContext.blockFactory(), 16 * 1024, false),
187+
() -> BlockHash.build(groups, driverContext.blockFactory(), pageSize, false),
188+
pageSize,
187189
driverContext
188190
);
189191
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ static TransportVersion def(int id) {
156156
public static final TransportVersion REPLACE_FAILURE_STORE_OPTIONS_WITH_SELECTOR_SYNTAX = def(8_821_00_0);
157157
public static final TransportVersion ELASTIC_INFERENCE_SERVICE_UNIFIED_CHAT_COMPLETIONS_INTEGRATION = def(8_822_00_0);
158158
public static final TransportVersion KQL_QUERY_TECH_PREVIEW = def(8_823_00_0);
159+
public static final TransportVersion ESQL_CHUNK_AGGREGATION_OUTPUT = def(8_824_00_0);
159160

160161
/*
161162
* STOP! READ THIS FIRST! No, really,

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

Lines changed: 118 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.elasticsearch.compute.data.IntBlock;
2323
import org.elasticsearch.compute.data.IntVector;
2424
import org.elasticsearch.compute.data.Page;
25+
import org.elasticsearch.core.Releasable;
2526
import org.elasticsearch.core.Releasables;
2627
import org.elasticsearch.core.TimeValue;
2728
import org.elasticsearch.index.analysis.AnalysisRegistry;
@@ -58,12 +59,14 @@ public Operator get(DriverContext driverContext) {
5859
analysisRegistry,
5960
maxPageSize
6061
),
62+
maxPageSize,
6163
driverContext
6264
);
6365
}
6466
return new HashAggregationOperator(
6567
aggregators,
6668
() -> BlockHash.build(groups, driverContext.blockFactory(), maxPageSize, false),
69+
maxPageSize,
6770
driverContext
6871
);
6972
}
@@ -78,9 +81,10 @@ public String describe() {
7881
}
7982
}
8083

81-
private boolean finished;
82-
private Page output;
84+
private final int maxPageSize;
85+
private Emitter emitter;
8386

87+
private boolean blockHashClosed = false;
8488
private final BlockHash blockHash;
8589

8690
private final List<GroupingAggregator> aggregators;
@@ -96,16 +100,23 @@ public String describe() {
96100
*/
97101
private long aggregationNanos;
98102
/**
99-
* Count of pages this operator has processed.
103+
* Count of input pages this operator has processed.
100104
*/
101105
private int pagesProcessed;
102106

107+
/**
108+
* Count of output pages this operator has emitted
109+
*/
110+
private int pagesEmitted;
111+
103112
@SuppressWarnings("this-escape")
104113
public HashAggregationOperator(
105114
List<GroupingAggregator.Factory> aggregators,
106115
Supplier<BlockHash> blockHash,
116+
int maxPageSize,
107117
DriverContext driverContext
108118
) {
119+
this.maxPageSize = maxPageSize;
109120
this.aggregators = new ArrayList<>(aggregators.size());
110121
this.driverContext = driverContext;
111122
boolean success = false;
@@ -124,7 +135,7 @@ public HashAggregationOperator(
124135

125136
@Override
126137
public boolean needsInput() {
127-
return finished == false;
138+
return emitter == null;
128139
}
129140

130141
@Override
@@ -192,61 +203,102 @@ public void close() {
192203

193204
@Override
194205
public Page getOutput() {
195-
Page p = output;
196-
output = null;
197-
return p;
206+
if (emitter == null) {
207+
return null;
208+
}
209+
return emitter.nextPage();
198210
}
199211

200-
@Override
201-
public void finish() {
202-
if (finished) {
203-
return;
212+
private class Emitter implements Releasable {
213+
private final int[] aggBlockCounts;
214+
private int position = -1;
215+
private IntVector allSelected = null;
216+
private Block[] allKeys;
217+
218+
Emitter(int[] aggBlockCounts) {
219+
this.aggBlockCounts = aggBlockCounts;
204220
}
205-
finished = true;
206-
Block[] blocks = null;
207-
IntVector selected = null;
208-
boolean success = false;
209-
try {
210-
selected = blockHash.nonEmpty();
211-
Block[] keys = blockHash.getKeys();
212-
int[] aggBlockCounts = aggregators.stream().mapToInt(GroupingAggregator::evaluateBlockCount).toArray();
213-
blocks = new Block[keys.length + Arrays.stream(aggBlockCounts).sum()];
214-
System.arraycopy(keys, 0, blocks, 0, keys.length);
215-
int offset = keys.length;
216-
for (int i = 0; i < aggregators.size(); i++) {
217-
var aggregator = aggregators.get(i);
218-
aggregator.evaluate(blocks, offset, selected, driverContext);
219-
offset += aggBlockCounts[i];
221+
222+
Page nextPage() {
223+
if (position == -1) {
224+
position = 0;
225+
// TODO: chunk selected and keys
226+
allKeys = blockHash.getKeys();
227+
allSelected = blockHash.nonEmpty();
228+
blockHashClosed = true;
229+
blockHash.close();
220230
}
221-
output = new Page(blocks);
222-
success = true;
223-
} finally {
224-
// selected should always be closed
225-
if (selected != null) {
226-
selected.close();
231+
final int endPosition = Math.toIntExact(Math.min(position + (long) maxPageSize, allSelected.getPositionCount()));
232+
if (endPosition == position) {
233+
return null;
227234
}
228-
if (success == false && blocks != null) {
229-
Releasables.closeExpectNoException(blocks);
235+
final boolean singlePage = position == 0 && endPosition == allSelected.getPositionCount();
236+
final Block[] blocks = new Block[allKeys.length + Arrays.stream(aggBlockCounts).sum()];
237+
IntVector selected = null;
238+
boolean success = false;
239+
try {
240+
if (singlePage) {
241+
this.allSelected.incRef();
242+
selected = this.allSelected;
243+
for (int i = 0; i < allKeys.length; i++) {
244+
allKeys[i].incRef();
245+
blocks[i] = allKeys[i];
246+
}
247+
} else {
248+
final int[] positions = new int[endPosition - position];
249+
for (int i = 0; i < positions.length; i++) {
250+
positions[i] = position + i;
251+
}
252+
selected = allSelected.filter(positions);
253+
for (int keyIndex = 0; keyIndex < allKeys.length; keyIndex++) {
254+
blocks[keyIndex] = allKeys[keyIndex].filter(positions);
255+
}
256+
}
257+
int blockOffset = allKeys.length;
258+
for (int i = 0; i < aggregators.size(); i++) {
259+
aggregators.get(i).evaluate(blocks, blockOffset, selected, driverContext);
260+
blockOffset += aggBlockCounts[i];
261+
}
262+
var output = new Page(blocks);
263+
pagesEmitted++;
264+
success = true;
265+
return output;
266+
} finally {
267+
position = endPosition;
268+
Releasables.close(selected, success ? null : Releasables.wrap(blocks));
230269
}
231270
}
271+
272+
@Override
273+
public void close() {
274+
Releasables.close(allSelected, allKeys != null ? Releasables.wrap(allKeys) : null);
275+
}
276+
277+
boolean doneEmitting() {
278+
return allSelected != null && position >= allSelected.getPositionCount();
279+
}
280+
}
281+
282+
@Override
283+
public void finish() {
284+
if (emitter == null) {
285+
emitter = new Emitter(aggregators.stream().mapToInt(GroupingAggregator::evaluateBlockCount).toArray());
286+
}
232287
}
233288

234289
@Override
235290
public boolean isFinished() {
236-
return finished && output == null;
291+
return emitter != null && emitter.doneEmitting();
237292
}
238293

239294
@Override
240295
public void close() {
241-
if (output != null) {
242-
output.releaseBlocks();
243-
}
244-
Releasables.close(blockHash, () -> Releasables.close(aggregators));
296+
Releasables.close(emitter, blockHashClosed ? null : blockHash, () -> Releasables.close(aggregators));
245297
}
246298

247299
@Override
248300
public Operator.Status status() {
249-
return new Status(hashNanos, aggregationNanos, pagesProcessed);
301+
return new Status(hashNanos, aggregationNanos, pagesProcessed, pagesEmitted);
250302
}
251303

252304
protected static void checkState(boolean condition, String msg) {
@@ -285,33 +337,43 @@ public static class Status implements Operator.Status {
285337
*/
286338
private final long aggregationNanos;
287339
/**
288-
* Count of pages this operator has processed.
340+
* Count of input pages this operator has processed.
289341
*/
290342
private final int pagesProcessed;
291343

344+
/**
345+
* Count of output pages this operator has emitted
346+
*/
347+
private final int pageEmitted;
348+
292349
/**
293350
* Build.
294351
* @param hashNanos Nanoseconds this operator has spent hashing grouping keys.
295352
* @param aggregationNanos Nanoseconds this operator has spent running the aggregations.
296353
* @param pagesProcessed Count of pages this operator has processed.
297354
*/
298-
public Status(long hashNanos, long aggregationNanos, int pagesProcessed) {
355+
public Status(long hashNanos, long aggregationNanos, int pagesProcessed, int pagesEmitted) {
299356
this.hashNanos = hashNanos;
300357
this.aggregationNanos = aggregationNanos;
301358
this.pagesProcessed = pagesProcessed;
359+
this.pageEmitted = pagesEmitted;
302360
}
303361

304362
protected Status(StreamInput in) throws IOException {
305363
hashNanos = in.readVLong();
306364
aggregationNanos = in.readVLong();
307365
pagesProcessed = in.readVInt();
366+
pageEmitted = in.getTransportVersion().onOrAfter(TransportVersions.ESQL_CHUNK_AGGREGATION_OUTPUT) ? in.readVInt() : 0;
308367
}
309368

310369
@Override
311370
public void writeTo(StreamOutput out) throws IOException {
312371
out.writeVLong(hashNanos);
313372
out.writeVLong(aggregationNanos);
314373
out.writeVInt(pagesProcessed);
374+
if (out.getTransportVersion().onOrAfter(TransportVersions.ESQL_CHUNK_AGGREGATION_OUTPUT)) {
375+
out.writeVInt(pageEmitted);
376+
}
315377
}
316378

317379
@Override
@@ -334,12 +396,19 @@ public long aggregationNanos() {
334396
}
335397

336398
/**
337-
* Count of pages this operator has processed.
399+
* Count of input pages this operator has processed.
338400
*/
339401
public int pagesProcessed() {
340402
return pagesProcessed;
341403
}
342404

405+
/**
406+
* Count of output pages this operator has emitted
407+
*/
408+
public int pagesEmitted() {
409+
return pageEmitted;
410+
}
411+
343412
@Override
344413
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
345414
builder.startObject();
@@ -352,6 +421,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
352421
builder.field("aggregation_time", TimeValue.timeValueNanos(aggregationNanos));
353422
}
354423
builder.field("pages_processed", pagesProcessed);
424+
builder.field("pages_emitted", pageEmitted);
355425
return builder.endObject();
356426

357427
}
@@ -361,12 +431,15 @@ public boolean equals(Object o) {
361431
if (this == o) return true;
362432
if (o == null || getClass() != o.getClass()) return false;
363433
Status status = (Status) o;
364-
return hashNanos == status.hashNanos && aggregationNanos == status.aggregationNanos && pagesProcessed == status.pagesProcessed;
434+
return hashNanos == status.hashNanos
435+
&& aggregationNanos == status.aggregationNanos
436+
&& pagesProcessed == status.pagesProcessed
437+
&& pageEmitted == status.pageEmitted;
365438
}
366439

367440
@Override
368441
public int hashCode() {
369-
return Objects.hash(hashNanos, aggregationNanos, pagesProcessed);
442+
return Objects.hash(hashNanos, aggregationNanos, pagesProcessed, pageEmitted);
370443
}
371444

372445
@Override

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -205,16 +205,16 @@ public Page getOutput() {
205205
return null;
206206
}
207207
if (valuesAggregator != null) {
208-
try {
209-
return valuesAggregator.getOutput();
210-
} finally {
211-
final ValuesAggregator aggregator = this.valuesAggregator;
212-
this.valuesAggregator = null;
213-
Releasables.close(aggregator);
208+
final Page output = valuesAggregator.getOutput();
209+
if (output == null) {
210+
Releasables.close(valuesAggregator, () -> this.valuesAggregator = null);
211+
} else {
212+
return output;
214213
}
215214
}
216215
if (ordinalAggregators.isEmpty() == false) {
217216
try {
217+
// TODO: chunk output pages
218218
return mergeOrdinalsSegmentResults();
219219
} catch (IOException e) {
220220
throw new UncheckedIOException(e);
@@ -510,6 +510,7 @@ private static class ValuesAggregator implements Releasable {
510510
maxPageSize,
511511
false
512512
),
513+
maxPageSize,
513514
driverContext
514515
);
515516
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public Operator get(DriverContext driverContext) {
6262
return new HashAggregationOperator(
6363
aggregators,
6464
() -> new TimeSeriesBlockHash(tsHashChannel, timeBucketChannel, driverContext),
65+
maxPageSize,
6566
driverContext
6667
);
6768
}
@@ -97,6 +98,7 @@ public Operator get(DriverContext driverContext) {
9798
return new HashAggregationOperator(
9899
aggregators,
99100
() -> BlockHash.build(hashGroups, driverContext.blockFactory(), maxPageSize, false),
101+
maxPageSize,
100102
driverContext
101103
);
102104
}
@@ -125,6 +127,7 @@ public Operator get(DriverContext driverContext) {
125127
return new HashAggregationOperator(
126128
aggregators,
127129
() -> BlockHash.build(groupings, driverContext.blockFactory(), maxPageSize, false),
130+
maxPageSize,
128131
driverContext
129132
);
130133
}

x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/OperatorTests.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ public String toString() {
211211
randomPageSize(),
212212
false
213213
),
214+
randomPageSize(),
214215
driverContext
215216
)
216217
);

0 commit comments

Comments
 (0)