Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/changelog/134235.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 134235
summary: Reserve memory for Lucene's TopN
area: ES|QL
type: bug
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public void skipOnAborted() {
* This used to fail, but we've since compacted top n so it actually succeeds now.
*/
public void testSortByManyLongsSuccess() throws IOException {
initManyLongs();
initManyLongs(10);
Map<String, Object> response = sortByManyLongs(500);
ListMatcher columns = matchesList().item(matchesMap().entry("name", "a").entry("type", "long"))
.item(matchesMap().entry("name", "b").entry("type", "long"));
Expand All @@ -107,7 +107,7 @@ public void testSortByManyLongsSuccess() throws IOException {
* This used to crash the node with an out of memory, but now it just trips a circuit breaker.
*/
public void testSortByManyLongsTooMuchMemory() throws IOException {
initManyLongs();
initManyLongs(10);
// 5000 is plenty to break on most nodes
assertCircuitBreaks(attempt -> sortByManyLongs(attempt * 5000));
}
Expand All @@ -116,7 +116,7 @@ public void testSortByManyLongsTooMuchMemory() throws IOException {
* This should record an async response with a {@link CircuitBreakingException}.
*/
public void testSortByManyLongsTooMuchMemoryAsync() throws IOException {
initManyLongs();
initManyLongs(10);
Request request = new Request("POST", "/_query/async");
request.addParameter("error_trace", "");
request.setJsonEntity(makeSortByManyLongs(5000).toString().replace("\n", "\\n"));
Expand Down Expand Up @@ -193,6 +193,29 @@ public void testSortByManyLongsTooMuchMemoryAsync() throws IOException {
);
}

public void testSortByManyLongsGiantTopN() throws IOException {
initManyLongs(10);
assertMap(
sortBySomeLongsLimit(100000),
matchesMap().entry("took", greaterThan(0))
.entry("is_partial", false)
.entry("columns", List.of(Map.of("name", "MAX(a)", "type", "long")))
.entry("values", List.of(List.of(9)))
.entry("documents_found", greaterThan(0))
.entry("values_loaded", greaterThan(0))
);
}

public void testSortByManyLongsGiantTopNTooMuchMemory() throws IOException {
initManyLongs(20);
assertCircuitBreaks(attempt -> sortBySomeLongsLimit(attempt * 500000));
}

public void testStupidTopN() throws IOException {
initManyLongs(1); // Doesn't actually matter how much data there is.
assertCircuitBreaks(attempt -> sortBySomeLongsLimit(2147483630));
}

private static final int MAX_ATTEMPTS = 5;

interface TryCircuitBreaking {
Expand Down Expand Up @@ -251,11 +274,25 @@ private StringBuilder makeSortByManyLongs(int count) {
return query;
}

private Map<String, Object> sortBySomeLongsLimit(int count) throws IOException {
logger.info("sorting by 5 longs, keeping {}", count);
return responseAsMap(query(makeSortBySomeLongsLimit(count), null));
}

private String makeSortBySomeLongsLimit(int count) {
StringBuilder query = new StringBuilder("{\"query\": \"FROM manylongs\n");
query.append("| SORT a, b, c, d, e\n");
query.append("| LIMIT ").append(count).append("\n");
query.append("| STATS MAX(a)\n");
query.append("\"}");
return query.toString();
}

/**
* This groups on about 200 columns which is a lot but has never caused us trouble.
*/
public void testGroupOnSomeLongs() throws IOException {
initManyLongs();
initManyLongs(10);
Response resp = groupOnManyLongs(200);
Map<String, Object> map = responseAsMap(resp);
ListMatcher columns = matchesList().item(matchesMap().entry("name", "MAX(a)").entry("type", "long"));
Expand All @@ -267,7 +304,7 @@ public void testGroupOnSomeLongs() throws IOException {
* This groups on 5000 columns which used to throw a {@link StackOverflowError}.
*/
public void testGroupOnManyLongs() throws IOException {
initManyLongs();
initManyLongs(10);
Response resp = groupOnManyLongs(5000);
Map<String, Object> map = responseAsMap(resp);
ListMatcher columns = matchesList().item(matchesMap().entry("name", "MAX(a)").entry("type", "long"));
Expand Down Expand Up @@ -335,15 +372,15 @@ private Response concat(int evals) throws IOException {
*/
public void testManyConcat() throws IOException {
int strings = 300;
initManyLongs();
initManyLongs(10);
assertManyStrings(manyConcat("FROM manylongs", strings), strings);
}

/**
* Hits a circuit breaker by building many moderately long strings.
*/
public void testHugeManyConcat() throws IOException {
initManyLongs();
initManyLongs(10);
// 2000 is plenty to break on most nodes
assertCircuitBreaks(attempt -> manyConcat("FROM manylongs", attempt * 2000));
}
Expand Down Expand Up @@ -414,15 +451,15 @@ private Map<String, Object> manyConcat(String init, int strings) throws IOExcept
*/
public void testManyRepeat() throws IOException {
int strings = 30;
initManyLongs();
initManyLongs(10);
assertManyStrings(manyRepeat("FROM manylongs", strings), 30);
}

/**
* Hits a circuit breaker by building many moderately long strings.
*/
public void testHugeManyRepeat() throws IOException {
initManyLongs();
initManyLongs(10);
// 75 is plenty to break on most nodes
assertCircuitBreaks(attempt -> manyRepeat("FROM manylongs", attempt * 75));
}
Expand Down Expand Up @@ -480,7 +517,7 @@ private void assertManyStrings(Map<String, Object> resp, int strings) throws IOE
}

public void testManyEval() throws IOException {
initManyLongs();
initManyLongs(10);
Map<String, Object> response = manyEval(1);
ListMatcher columns = matchesList();
columns = columns.item(matchesMap().entry("name", "a").entry("type", "long"));
Expand All @@ -495,7 +532,7 @@ public void testManyEval() throws IOException {
}

public void testTooManyEval() throws IOException {
initManyLongs();
initManyLongs(10);
// 490 is plenty to fail on most nodes
assertCircuitBreaks(attempt -> manyEval(attempt * 490));
}
Expand Down Expand Up @@ -810,24 +847,34 @@ private Map<String, Object> enrichExplosion(int sensorDataCount, int lookupEntri
}
}

private void initManyLongs() throws IOException {
private void initManyLongs(int countPerLong) throws IOException {
logger.info("loading many documents with longs");
StringBuilder bulk = new StringBuilder();
for (int a = 0; a < 10; a++) {
for (int b = 0; b < 10; b++) {
for (int c = 0; c < 10; c++) {
for (int d = 0; d < 10; d++) {
for (int e = 0; e < 10; e++) {
int flush = 0;
for (int a = 0; a < countPerLong; a++) {
for (int b = 0; b < countPerLong; b++) {
for (int c = 0; c < countPerLong; c++) {
for (int d = 0; d < countPerLong; d++) {
for (int e = 0; e < countPerLong; e++) {
bulk.append(String.format(Locale.ROOT, """
{"create":{}}
{"a":%d,"b":%d,"c":%d,"d":%d,"e":%d}
""", a, b, c, d, e));
flush++;
if (flush % 10_000 == 0) {
bulk("manylongs", bulk.toString());
bulk.setLength(0);
logger.info(
"flushing {}/{} to manylongs",
flush,
countPerLong * countPerLong * countPerLong * countPerLong * countPerLong
);

}
}
}
}
}
bulk("manylongs", bulk.toString());
bulk.setLength(0);
}
initIndex("manylongs", bulk.toString());
}
Expand Down
Loading
Loading