Skip to content

Commit f32c348

Browse files
authored
ESQL: Track memory in evaluators (#133392)
If you write very very large ESQL queries you can spend a lot of memory on the expression evaluators themselves. You can certainly do it in real life, but our tests do something like: ``` FROM foo | EVAL a0001 = n + 1 | EVAL a0002 = a0001 + 1 | EVAL a0003 = a0002 + 1 ... | EVAL a5000 = a4999 + 1 | STATS MAX(a5000) ``` Each evaluator costs like 200 bytes a pop. For thousands of evaluators this adds up. So! We have to track it. Nhat had suggested charging a flat 200 bytes a pop. I thought about it and decided that it'd be pretty easy to get the actual size. Most of the evaluators are generated and it's a fairly small generated change to pick that up. So I did. We *do* build the evaluators before we cost them, but that's fine because they are very very small. So long as we account for them, I think it's safe.
1 parent a635834 commit f32c348

File tree

461 files changed

+5242
-472
lines changed

Some content is hidden

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

461 files changed

+5242
-472
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,11 @@ public Block eval(Page page) {
666666
return mask;
667667
}
668668

669+
@Override
670+
public long baseRamBytesUsed() {
671+
return 0;
672+
}
673+
669674
@Override
670675
public void close() {
671676
mask.close();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ static void selfTest() {
144144
public String operation;
145145

146146
private static Operator operator(String operation) {
147-
return new EvalOperator(driverContext.blockFactory(), evaluator(operation));
147+
return new EvalOperator(driverContext, evaluator(operation));
148148
}
149149

150150
private static EvalOperator.ExpressionEvaluator evaluator(String operation) {

docs/changelog/133392.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 133392
2+
summary: Track memory in evaluators
3+
area: ES|QL
4+
type: bug
5+
issues: []

x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/Literal.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
*/
77
package org.elasticsearch.xpack.esql.core.expression;
88

9+
import org.apache.lucene.util.Accountable;
910
import org.apache.lucene.util.BytesRef;
11+
import org.apache.lucene.util.RamUsageEstimator;
1012
import org.elasticsearch.TransportVersions;
1113
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
1214
import org.elasticsearch.common.io.stream.StreamInput;
@@ -35,7 +37,9 @@
3537
/**
3638
* Literal or constant.
3739
*/
38-
public class Literal extends LeafExpression {
40+
public class Literal extends LeafExpression implements Accountable {
41+
private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(Literal.class);
42+
3943
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(
4044
Expression.class,
4145
"Literal",
@@ -169,6 +173,17 @@ public String nodeString() {
169173
return toString() + "[" + dataType + "]";
170174
}
171175

176+
@Override
177+
public long ramBytesUsed() {
178+
long ramBytesUsed = BASE_RAM_BYTES_USED;
179+
if (value instanceof BytesRef b) {
180+
ramBytesUsed += b.length;
181+
} else {
182+
ramBytesUsed += RamUsageEstimator.sizeOfObject(value);
183+
}
184+
return ramBytesUsed;
185+
}
186+
172187
/**
173188
* Utility method for creating a literal out of a foldable expression.
174189
* Throws an exception if the expression is not foldable.

x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/ConvertEvaluatorImplementer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import javax.lang.model.type.TypeMirror;
2424
import javax.lang.model.util.Elements;
2525

26+
import static org.elasticsearch.compute.gen.EvaluatorImplementer.baseRamBytesUsed;
2627
import static org.elasticsearch.compute.gen.Methods.buildFromFactory;
2728
import static org.elasticsearch.compute.gen.Methods.getMethod;
2829
import static org.elasticsearch.compute.gen.Types.ABSTRACT_CONVERT_FUNCTION_EVALUATOR;
@@ -98,6 +99,7 @@ private TypeSpec type() {
9899
builder.addJavadoc("This class is generated. Edit {@code " + getClass().getSimpleName() + "} instead.");
99100
builder.addModifiers(Modifier.PUBLIC, Modifier.FINAL);
100101
builder.superclass(ABSTRACT_CONVERT_FUNCTION_EVALUATOR);
102+
builder.addField(baseRamBytesUsed(implementation));
101103

102104
for (EvaluatorImplementer.ProcessFunctionArg a : processFunction.args) {
103105
a.declareField(builder);
@@ -113,6 +115,7 @@ private TypeSpec type() {
113115
}
114116
builder.addMethod(processFunction.toStringMethod(implementation));
115117
builder.addMethod(processFunction.close());
118+
builder.addMethod(processFunction.baseRamBytesUsed());
116119
builder.addType(factory());
117120
return builder.build();
118121
}

x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/EvaluatorImplementer.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import com.squareup.javapoet.ArrayTypeName;
1111
import com.squareup.javapoet.ClassName;
12+
import com.squareup.javapoet.FieldSpec;
1213
import com.squareup.javapoet.JavaFile;
1314
import com.squareup.javapoet.MethodSpec;
1415
import com.squareup.javapoet.ParameterizedTypeName;
@@ -47,6 +48,7 @@
4748
import static org.elasticsearch.compute.gen.Types.INT_BLOCK;
4849
import static org.elasticsearch.compute.gen.Types.LONG_BLOCK;
4950
import static org.elasticsearch.compute.gen.Types.PAGE;
51+
import static org.elasticsearch.compute.gen.Types.RAM_USAGE_ESIMATOR;
5052
import static org.elasticsearch.compute.gen.Types.RELEASABLE;
5153
import static org.elasticsearch.compute.gen.Types.RELEASABLES;
5254
import static org.elasticsearch.compute.gen.Types.SOURCE;
@@ -96,6 +98,7 @@ private TypeSpec type() {
9698
builder.addJavadoc("This class is generated. Edit {@code " + getClass().getSimpleName() + "} instead.");
9799
builder.addModifiers(Modifier.PUBLIC, Modifier.FINAL);
98100
builder.addSuperinterface(EXPRESSION_EVALUATOR);
101+
builder.addField(baseRamBytesUsed(implementation));
99102
builder.addType(factory());
100103

101104
builder.addField(SOURCE, "source", Modifier.PRIVATE, Modifier.FINAL);
@@ -106,6 +109,7 @@ private TypeSpec type() {
106109

107110
builder.addMethod(ctor());
108111
builder.addMethod(eval());
112+
builder.addMethod(processFunction.baseRamBytesUsed());
109113

110114
if (processOutputsMultivalued) {
111115
if (processFunction.args.stream().anyMatch(x -> x instanceof FixedProcessFunctionArg == false)) {
@@ -123,6 +127,19 @@ private TypeSpec type() {
123127
return builder.build();
124128
}
125129

130+
static FieldSpec baseRamBytesUsed(ClassName implementation) {
131+
FieldSpec.Builder builder = FieldSpec.builder(
132+
TypeName.LONG,
133+
"BASE_RAM_BYTES_USED",
134+
Modifier.PRIVATE,
135+
Modifier.STATIC,
136+
Modifier.FINAL
137+
);
138+
builder.initializer("$T.shallowSizeOfInstance($T.class)", RAM_USAGE_ESIMATOR, implementation);
139+
140+
return builder.build();
141+
}
142+
126143
private MethodSpec ctor() {
127144
MethodSpec.Builder builder = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC);
128145
builder.addParameter(SOURCE, "source");
@@ -411,6 +428,11 @@ interface ProcessFunctionArg {
411428
* The string to close this argument or {@code null}.
412429
*/
413430
String closeInvocation();
431+
432+
/**
433+
* Invokes {@code baseRamBytesUsed} on sub-expressions an
434+
*/
435+
void sumBaseRamBytesUsed(MethodSpec.Builder builder);
414436
}
415437

416438
record StandardProcessFunctionArg(TypeName type, String name) implements ProcessFunctionArg {
@@ -535,6 +557,11 @@ public void buildToStringInvocation(StringBuilder pattern, List<Object> args, St
535557
public String closeInvocation() {
536558
return name;
537559
}
560+
561+
@Override
562+
public void sumBaseRamBytesUsed(MethodSpec.Builder builder) {
563+
builder.addStatement("baseRamBytesUsed += $L.baseRamBytesUsed()", name);
564+
}
538565
}
539566

540567
private record ArrayProcessFunctionArg(TypeName componentType, String name) implements ProcessFunctionArg {
@@ -667,6 +694,13 @@ public void buildToStringInvocation(StringBuilder pattern, List<Object> args, St
667694
public String closeInvocation() {
668695
return "() -> Releasables.close(" + name + ")";
669696
}
697+
698+
@Override
699+
public void sumBaseRamBytesUsed(MethodSpec.Builder builder) {
700+
builder.beginControlFlow("for ($T e : $L)", EXPRESSION_EVALUATOR, name);
701+
builder.addStatement("baseRamBytesUsed += e.baseRamBytesUsed()");
702+
builder.endControlFlow();
703+
}
670704
}
671705

672706
record FixedProcessFunctionArg(TypeName type, String name, boolean includeInToString, Scope scope, boolean releasable)
@@ -769,6 +803,9 @@ public void buildToStringInvocation(StringBuilder pattern, List<Object> args, St
769803
public String closeInvocation() {
770804
return releasable ? name : null;
771805
}
806+
807+
@Override
808+
public void sumBaseRamBytesUsed(MethodSpec.Builder builder) {}
772809
}
773810

774811
private record BuilderProcessFunctionArg(ClassName type, String name) implements ProcessFunctionArg {
@@ -853,6 +890,9 @@ public void buildToStringInvocation(StringBuilder pattern, List<Object> args, St
853890
public String closeInvocation() {
854891
return null;
855892
}
893+
894+
@Override
895+
public void sumBaseRamBytesUsed(MethodSpec.Builder builder) {}
856896
}
857897

858898
private record BlockProcessFunctionArg(TypeName type, String name) implements ProcessFunctionArg {
@@ -940,6 +980,11 @@ public void buildToStringInvocation(StringBuilder pattern, List<Object> args, St
940980
public String closeInvocation() {
941981
return name;
942982
}
983+
984+
@Override
985+
public void sumBaseRamBytesUsed(MethodSpec.Builder builder) {
986+
builder.addStatement("baseRamBytesUsed += $L.baseRamBytesUsed()", name);
987+
}
943988
}
944989

945990
static class ProcessFunction {
@@ -1085,6 +1130,18 @@ MethodSpec close() {
10851130
}
10861131
return builder.build();
10871132
}
1133+
1134+
MethodSpec baseRamBytesUsed() {
1135+
MethodSpec.Builder builder = MethodSpec.methodBuilder("baseRamBytesUsed").addAnnotation(Override.class);
1136+
builder.addModifiers(Modifier.PUBLIC).returns(TypeName.LONG);
1137+
1138+
builder.addStatement("long baseRamBytesUsed = BASE_RAM_BYTES_USED");
1139+
for (ProcessFunctionArg arg : args) {
1140+
arg.sumBaseRamBytesUsed(builder);
1141+
}
1142+
builder.addStatement("return baseRamBytesUsed");
1143+
return builder.build();
1144+
}
10881145
}
10891146

10901147
static boolean isBlockType(TypeName type) {

x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/MvEvaluatorImplementer.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ private TypeSpec type() {
137137
builder.addField(SOURCE, "source", Modifier.PRIVATE, Modifier.FINAL);
138138
builder.addField(WARNINGS, "warnings", Modifier.PRIVATE);
139139
}
140+
builder.addField(EvaluatorImplementer.baseRamBytesUsed(implementation));
140141

141142
builder.addMethod(ctor());
142143
builder.addMethod(name());
@@ -159,6 +160,7 @@ private TypeSpec type() {
159160
if (warnExceptions.isEmpty() == false) {
160161
builder.addMethod(EvaluatorImplementer.warnings());
161162
}
163+
builder.addMethod(baseRamBytesUsed());
162164
return builder.build();
163165
}
164166

@@ -581,4 +583,12 @@ private void call(MethodSpec.Builder builder) {
581583
}
582584
}
583585
}
586+
587+
MethodSpec baseRamBytesUsed() {
588+
MethodSpec.Builder builder = MethodSpec.methodBuilder("baseRamBytesUsed").addAnnotation(Override.class);
589+
builder.addModifiers(Modifier.PUBLIC).returns(TypeName.LONG);
590+
591+
builder.addStatement("return BASE_RAM_BYTES_USED + field.baseRamBytesUsed()");
592+
return builder.build();
593+
}
584594
}

x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/Types.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class Types {
4242

4343
static final ClassName CIRCUIT_BREAKER = ClassName.get("org.elasticsearch.common.breaker", "CircuitBreaker");
4444
static final ClassName BIG_ARRAYS = ClassName.get("org.elasticsearch.common.util", "BigArrays");
45+
static final ClassName RAM_USAGE_ESIMATOR = ClassName.get("org.apache.lucene.util", "RamUsageEstimator");
4546

4647
static final ClassName BOOLEAN_BLOCK = ClassName.get(DATA_PACKAGE, "BooleanBlock");
4748
static final ClassName BYTES_REF_BLOCK = ClassName.get(DATA_PACKAGE, "BytesRefBlock");

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.apache.lucene.search.Query;
1111
import org.apache.lucene.search.Scorable;
1212
import org.apache.lucene.search.ScoreMode;
13+
import org.apache.lucene.util.RamUsageEstimator;
1314
import org.elasticsearch.compute.data.Block;
1415
import org.elasticsearch.compute.data.BlockFactory;
1516
import org.elasticsearch.compute.data.BooleanBlock;
@@ -27,6 +28,7 @@
2728
* @see LuceneQueryScoreEvaluator
2829
*/
2930
public class LuceneQueryExpressionEvaluator extends LuceneQueryEvaluator<BooleanBlock.Builder> implements EvalOperator.ExpressionEvaluator {
31+
private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(LuceneQueryExpressionEvaluator.class);
3032

3133
LuceneQueryExpressionEvaluator(BlockFactory blockFactory, ShardConfig[] shards) {
3234
super(blockFactory, shards);
@@ -62,6 +64,11 @@ protected void appendMatch(BooleanBlock.Builder builder, Scorable scorer) throws
6264
builder.appendBoolean(true);
6365
}
6466

67+
@Override
68+
public long baseRamBytesUsed() {
69+
return BASE_RAM_BYTES_USED;
70+
}
71+
6572
public record Factory(ShardConfig[] shardConfigs) implements EvalOperator.ExpressionEvaluator.Factory {
6673
@Override
6774
public EvalOperator.ExpressionEvaluator get(DriverContext context) {

0 commit comments

Comments
 (0)