Skip to content

Commit b05ba31

Browse files
committed
ESQL: Reserve memory for Lucene's TopN
Lucene doesn't track memory usage for TopN and can use a fair bit of it. Try this query: ``` FROM big_table | SORT a, b, c, d, e | LIMIT 1000000 | STATS MAX(a) ``` We attempt to return all million documents from lucene. Is we did this with the compute engine we're track all of the memory usage. With lucene we have to reserve it. In the case of the query above the sort keys weight 8 bytes each. 40 bytes total. Plus another 72 for Lucene's `FieldDoc`. And another 40 at least for copying to the values to `FieldDoc`. That totals something like 152 bytes a piece. That's 145mb. Worth tracking!
1 parent e45e451 commit b05ba31

File tree

2 files changed

+273
-69
lines changed

2 files changed

+273
-69
lines changed

test/external-modules/esql-heap-attack/src/javaRestTest/java/org/elasticsearch/xpack/esql/heap_attack/HeapAttackIT.java

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public void skipOnAborted() {
9191
* This used to fail, but we've since compacted top n so it actually succeeds now.
9292
*/
9393
public void testSortByManyLongsSuccess() throws IOException {
94-
initManyLongs();
94+
initManyLongs(10);
9595
Map<String, Object> response = sortByManyLongs(500);
9696
ListMatcher columns = matchesList().item(matchesMap().entry("name", "a").entry("type", "long"))
9797
.item(matchesMap().entry("name", "b").entry("type", "long"));
@@ -108,7 +108,7 @@ public void testSortByManyLongsSuccess() throws IOException {
108108
* This used to crash the node with an out of memory, but now it just trips a circuit breaker.
109109
*/
110110
public void testSortByManyLongsTooMuchMemory() throws IOException {
111-
initManyLongs();
111+
initManyLongs(10);
112112
// 5000 is plenty to break on most nodes
113113
assertCircuitBreaks(attempt -> sortByManyLongs(attempt * 5000));
114114
}
@@ -117,7 +117,7 @@ public void testSortByManyLongsTooMuchMemory() throws IOException {
117117
* This should record an async response with a {@link CircuitBreakingException}.
118118
*/
119119
public void testSortByManyLongsTooMuchMemoryAsync() throws IOException {
120-
initManyLongs();
120+
initManyLongs(10);
121121
Request request = new Request("POST", "/_query/async");
122122
request.addParameter("error_trace", "");
123123
request.setJsonEntity(makeSortByManyLongs(5000).toString().replace("\n", "\\n"));
@@ -194,6 +194,24 @@ public void testSortByManyLongsTooMuchMemoryAsync() throws IOException {
194194
);
195195
}
196196

197+
public void testSortByManyLongsGiantTopN() throws IOException {
198+
initManyLongs(10);
199+
assertMap(sortBySomeLongsLimit(100000),
200+
matchesMap()
201+
.entry("took", greaterThan(0))
202+
.entry("is_partial", false)
203+
.entry("columns", List.of(Map.of("name", "MAX(a)", "type", "long")))
204+
.entry("values", List.of(List.of(9)))
205+
.entry("documents_found", greaterThan(0))
206+
.entry("values_loaded", greaterThan(0))
207+
);
208+
}
209+
210+
public void testSortByManyLongsGiantTopNTooMuchMemory() throws IOException {
211+
initManyLongs(20);
212+
assertCircuitBreaks(attempt -> sortBySomeLongsLimit(attempt * 500000));
213+
}
214+
197215
private static final int MAX_ATTEMPTS = 5;
198216

199217
interface TryCircuitBreaking {
@@ -252,11 +270,25 @@ private StringBuilder makeSortByManyLongs(int count) {
252270
return query;
253271
}
254272

273+
private Map<String, Object> sortBySomeLongsLimit(int count) throws IOException {
274+
logger.info("sorting by 5 longs, keeping {}", count);
275+
return responseAsMap(query(makeSortBySomeLongsLimit(count), null));
276+
}
277+
278+
private String makeSortBySomeLongsLimit(int count) {
279+
StringBuilder query = new StringBuilder("{\"query\": \"FROM manylongs\n");
280+
query.append("| SORT a, b, c, d, e\n");
281+
query.append("| LIMIT ").append(count).append("\n");
282+
query.append("| STATS MAX(a)\n");
283+
query.append("\"}");
284+
return query.toString();
285+
}
286+
255287
/**
256288
* This groups on about 200 columns which is a lot but has never caused us trouble.
257289
*/
258290
public void testGroupOnSomeLongs() throws IOException {
259-
initManyLongs();
291+
initManyLongs(10);
260292
Response resp = groupOnManyLongs(200);
261293
Map<String, Object> map = responseAsMap(resp);
262294
ListMatcher columns = matchesList().item(matchesMap().entry("name", "MAX(a)").entry("type", "long"));
@@ -268,7 +300,7 @@ public void testGroupOnSomeLongs() throws IOException {
268300
* This groups on 5000 columns which used to throw a {@link StackOverflowError}.
269301
*/
270302
public void testGroupOnManyLongs() throws IOException {
271-
initManyLongs();
303+
initManyLongs(10);
272304
Response resp = groupOnManyLongs(5000);
273305
Map<String, Object> map = responseAsMap(resp);
274306
ListMatcher columns = matchesList().item(matchesMap().entry("name", "MAX(a)").entry("type", "long"));
@@ -336,15 +368,15 @@ private Response concat(int evals) throws IOException {
336368
*/
337369
public void testManyConcat() throws IOException {
338370
int strings = 300;
339-
initManyLongs();
371+
initManyLongs(10);
340372
assertManyStrings(manyConcat("FROM manylongs", strings), strings);
341373
}
342374

343375
/**
344376
* Hits a circuit breaker by building many moderately long strings.
345377
*/
346378
public void testHugeManyConcat() throws IOException {
347-
initManyLongs();
379+
initManyLongs(10);
348380
// 2000 is plenty to break on most nodes
349381
assertCircuitBreaks(attempt -> manyConcat("FROM manylongs", attempt * 2000));
350382
}
@@ -415,15 +447,15 @@ private Map<String, Object> manyConcat(String init, int strings) throws IOExcept
415447
*/
416448
public void testManyRepeat() throws IOException {
417449
int strings = 30;
418-
initManyLongs();
450+
initManyLongs(10);
419451
assertManyStrings(manyRepeat("FROM manylongs", strings), 30);
420452
}
421453

422454
/**
423455
* Hits a circuit breaker by building many moderately long strings.
424456
*/
425457
public void testHugeManyRepeat() throws IOException {
426-
initManyLongs();
458+
initManyLongs(10);
427459
// 75 is plenty to break on most nodes
428460
assertCircuitBreaks(attempt -> manyRepeat("FROM manylongs", attempt * 75));
429461
}
@@ -481,7 +513,7 @@ private void assertManyStrings(Map<String, Object> resp, int strings) throws IOE
481513
}
482514

483515
public void testManyEval() throws IOException {
484-
initManyLongs();
516+
initManyLongs(10);
485517
Map<String, Object> response = manyEval(1);
486518
ListMatcher columns = matchesList();
487519
columns = columns.item(matchesMap().entry("name", "a").entry("type", "long"));
@@ -496,7 +528,7 @@ public void testManyEval() throws IOException {
496528
}
497529

498530
public void testTooManyEval() throws IOException {
499-
initManyLongs();
531+
initManyLongs(10);
500532
// 490 is plenty to fail on most nodes
501533
assertCircuitBreaks(attempt -> manyEval(attempt * 490));
502534
}
@@ -855,24 +887,34 @@ private Map<String, Object> enrichExplosion(int sensorDataCount, int lookupEntri
855887
}
856888
}
857889

858-
private void initManyLongs() throws IOException {
890+
private void initManyLongs(int countPerLong) throws IOException {
859891
logger.info("loading many documents with longs");
860892
StringBuilder bulk = new StringBuilder();
861-
for (int a = 0; a < 10; a++) {
862-
for (int b = 0; b < 10; b++) {
863-
for (int c = 0; c < 10; c++) {
864-
for (int d = 0; d < 10; d++) {
865-
for (int e = 0; e < 10; e++) {
893+
int flush = 0;
894+
for (int a = 0; a < countPerLong; a++) {
895+
for (int b = 0; b < countPerLong; b++) {
896+
for (int c = 0; c < countPerLong; c++) {
897+
for (int d = 0; d < countPerLong; d++) {
898+
for (int e = 0; e < countPerLong; e++) {
866899
bulk.append(String.format(Locale.ROOT, """
867900
{"create":{}}
868901
{"a":%d,"b":%d,"c":%d,"d":%d,"e":%d}
869902
""", a, b, c, d, e));
903+
flush++;
904+
if (flush % 10_000 == 0) {
905+
bulk("manylongs", bulk.toString());
906+
bulk.setLength(0);
907+
logger.info(
908+
"flushing {}/{} to manylongs",
909+
flush,
910+
countPerLong * countPerLong * countPerLong * countPerLong * countPerLong
911+
);
912+
913+
}
870914
}
871915
}
872916
}
873917
}
874-
bulk("manylongs", bulk.toString());
875-
bulk.setLength(0);
876918
}
877919
initIndex("manylongs", bulk.toString());
878920
}

0 commit comments

Comments
 (0)