Skip to content

Commit 6a6c7c4

Browse files
authored
Avoid O(N^2) in VALUES with ordinals grouping (#130576) (#130775)
Using the VALUES aggregator with ordinals grouping led to accidental quadratic complexity. Queries like FROM .. | STATS ... VALUES(field) ... BY keyword-field are affected by this performance issue. This change caches a sorted structure - previously used to fix a similar O(N^2) problem when emitting the output block - during the merging phase of the OrdinalGroupingOperator.
1 parent a711a51 commit 6a6c7c4

File tree

15 files changed

+682
-317
lines changed

15 files changed

+682
-317
lines changed

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

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ public class ValuesAggregatorBenchmark {
8888
try {
8989
for (String groups : ValuesAggregatorBenchmark.class.getField("groups").getAnnotationsByType(Param.class)[0].value()) {
9090
for (String dataType : ValuesAggregatorBenchmark.class.getField("dataType").getAnnotationsByType(Param.class)[0].value()) {
91-
run(Integer.parseInt(groups), dataType, 10);
91+
run(Integer.parseInt(groups), dataType, 10, 0);
92+
run(Integer.parseInt(groups), dataType, 10, 1);
9293
}
9394
}
9495
} catch (NoSuchFieldException e) {
@@ -106,7 +107,10 @@ public class ValuesAggregatorBenchmark {
106107
@Param({ BYTES_REF, INT, LONG })
107108
public String dataType;
108109

109-
private static Operator operator(DriverContext driverContext, int groups, String dataType) {
110+
@Param({ "0", "1" })
111+
public int numOrdinalMerges;
112+
113+
private static Operator operator(DriverContext driverContext, int groups, String dataType, int numOrdinalMerges) {
110114
if (groups == 1) {
111115
return new AggregationOperator(
112116
List.of(supplier(dataType).aggregatorFactory(AggregatorMode.SINGLE, List.of(0)).apply(driverContext)),
@@ -118,7 +122,24 @@ private static Operator operator(DriverContext driverContext, int groups, String
118122
List.of(supplier(dataType).groupingAggregatorFactory(AggregatorMode.SINGLE, List.of(1))),
119123
() -> BlockHash.build(groupSpec, driverContext.blockFactory(), 16 * 1024, false),
120124
driverContext
121-
);
125+
) {
126+
@Override
127+
public Page getOutput() {
128+
mergeOrdinal();
129+
return super.getOutput();
130+
}
131+
132+
// simulate OrdinalsGroupingOperator
133+
void mergeOrdinal() {
134+
var merged = supplier(dataType).groupingAggregatorFactory(AggregatorMode.SINGLE, List.of(1)).apply(driverContext);
135+
for (int i = 0; i < numOrdinalMerges; i++) {
136+
for (int p = 0; p < groups; p++) {
137+
merged.addIntermediateRow(p, aggregators.get(0), p);
138+
}
139+
}
140+
aggregators.set(0, merged);
141+
}
142+
};
122143
}
123144

124145
private static AggregatorFunctionSupplier supplier(String dataType) {
@@ -324,12 +345,12 @@ private static Block groupingBlock(int groups) {
324345

325346
@Benchmark
326347
public void run() {
327-
run(groups, dataType, OP_COUNT);
348+
run(groups, dataType, OP_COUNT, numOrdinalMerges);
328349
}
329350

330-
private static void run(int groups, String dataType, int opCount) {
351+
private static void run(int groups, String dataType, int opCount, int numOrdinalMerges) {
331352
DriverContext driverContext = driverContext();
332-
try (Operator operator = operator(driverContext, groups, dataType)) {
353+
try (Operator operator = operator(driverContext, groups, dataType, numOrdinalMerges)) {
333354
Page page = page(groups, dataType);
334355
for (int i = 0; i < opCount; i++) {
335356
operator.addInput(page.shallowCopy());

docs/changelog/130576.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 130576
2+
summary: Avoid O(N^2) in VALUES with ordinals grouping
3+
area: ES|QL
4+
type: bug
5+
issues: []

x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/aggregation/ValuesBytesRefAggregator.java

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

0 commit comments

Comments
 (0)