Skip to content

Commit 9f68c08

Browse files
authored
Configure IndexSearcher.maxClauseCount() based on Node characteristics (#81525) (#81850)
This commit deprecates the indices.query.bool.max_clause_count node setting, and instead configures the maximum clause count for lucene based on the available heap and the size of the thread pool. Closes #46433
1 parent 917a8cf commit 9f68c08

File tree

13 files changed

+248
-131
lines changed

13 files changed

+248
-131
lines changed

docs/reference/migration/migrate_8_0/cluster-node-setting-changes.asciidoc

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ The default for the `action.destructive_requires_name` setting changes from `fal
1616
to `true` in {es} 8.0.0.
1717
1818
Previously, defaulting to `false` allowed users to use wildcard
19-
patterns to delete, close, or change index blocks on indices.
19+
patterns to delete, close, or change index blocks on indices.
2020
To prevent the accidental deletion of indices that happen to match a
2121
wildcard pattern, we now default to requiring that destructive
2222
operations explicitly name the indices to be modified.
@@ -44,19 +44,28 @@ non-frozen node will result in an error on startup.
4444
====
4545

4646
[[max_clause_count_change]]
47-
.The `indices.query.bool.max_clause_count` setting now limits all query clauses.
47+
.The `indices.query.bool.max_clause_count` setting has been deprecated, and no longer has any effect.
4848
[%collapsible]
4949
====
5050
*Details* +
51-
Previously, the `indices.query.bool.max_clause_count` would apply to the number
52-
of clauses of a single `bool` query. It now applies to the total number of
53-
clauses of the rewritten query. To reduce chances of breaks, its
54-
default value has been bumped from 1024 to 4096.
51+
Elasticsearch will now dynamically set the maximum number of allowed clauses
52+
in a query, using a heuristic based on the size of the search thread pool and
53+
the size of the heap allocated to the JVM. This limit has a minimum value of
54+
1024 and will in most cases be larger (for example, a node with 30Gb RAM and
55+
48 CPUs will have a maximum clause count of around 27,000). Larger heaps lead
56+
to higher values, and larger thread pools result in lower values.
5557
5658
*Impact* +
57-
Queries with many clauses should be avoided whenever possible.
58-
If you previously bumped this setting to accommodate heavy queries,
59-
you might need to increase it further.
59+
Queries with many clauses should be avoided whenever possible.
60+
If you previously bumped this setting to accommodate heavy queries,
61+
you might need to increase the amount of memory available to Elasticsearch,
62+
or to reduce the size of your search thread pool so that more memory is
63+
available to each concurrent search.
64+
65+
In previous versions of Lucene you could get around this limit by nesting
66+
boolean queries within each other, but the limit is now based on the total
67+
number of leaf queries within the query as a whole and this workaround will
68+
no longer help.
6069
====
6170

6271
[[ilm-poll-interval-limit]]
@@ -224,7 +233,7 @@ Remove the `http.content_type.required` setting from `elasticsearch.yml`. Specif
224233
The `http.tcp_no_delay` setting was deprecated in 7.x and has been removed in 8.0. Use`http.tcp.no_delay` instead.
225234
226235
*Impact* +
227-
Replace the `http.tcp_no_delay` setting with `http.tcp.no_delay`.
236+
Replace the `http.tcp_no_delay` setting with `http.tcp.no_delay`.
228237
Specifying `http.tcp_no_delay` in `elasticsearch.yml` will
229238
result in an error on startup.
230239
====
@@ -237,7 +246,7 @@ The `network.tcp.connect_timeout` setting was deprecated in 7.x and has been rem
237246
was a fallback setting for `transport.connect_timeout`.
238247
239248
*Impact* +
240-
Remove the`network.tcp.connect_timeout` setting.
249+
Remove the`network.tcp.connect_timeout` setting.
241250
Use the `transport.connect_timeout` setting to change the default connection
242251
timeout for client connections. Specifying
243252
`network.tcp.connect_timeout` in `elasticsearch.yml` will result in an
@@ -282,7 +291,7 @@ since the 5.2 release of {es}.
282291
283292
*Impact* +
284293
Remove the `xpack.security.authz.store.roles.index.cache.max_size`
285-
and `xpack.security.authz.store.roles.index.cache.ttl` settings from `elasticsearch.yml` .
294+
and `xpack.security.authz.store.roles.index.cache.ttl` settings from `elasticsearch.yml` .
286295
Specifying these settings will result in an error on startup.
287296
====
288297

server/src/internalClusterTest/java/org/elasticsearch/search/aggregations/bucket/AdjacencyMatrixIT.java

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@
88

99
package org.elasticsearch.search.aggregations.bucket;
1010

11+
import org.apache.lucene.search.IndexSearcher;
1112
import org.elasticsearch.ElasticsearchException;
1213
import org.elasticsearch.action.index.IndexRequestBuilder;
1314
import org.elasticsearch.action.search.SearchPhaseExecutionException;
1415
import org.elasticsearch.action.search.SearchResponse;
15-
import org.elasticsearch.common.settings.Settings;
1616
import org.elasticsearch.index.query.BoolQueryBuilder;
1717
import org.elasticsearch.index.query.QueryBuilder;
18-
import org.elasticsearch.search.SearchModule;
1918
import org.elasticsearch.search.aggregations.InternalAggregation;
2019
import org.elasticsearch.search.aggregations.bucket.adjacency.AdjacencyMatrix;
2120
import org.elasticsearch.search.aggregations.bucket.adjacency.AdjacencyMatrix.Bucket;
@@ -276,20 +275,28 @@ public void testWithSubAggregation() throws Exception {
276275

277276
}
278277

279-
public void testTooLargeMatrix() throws Exception {
278+
public void testTooLargeMatrix() {
280279

281-
// Create more filters than is permitted by Lucene Bool clause settings.
282-
MapBuilder filtersMap = new MapBuilder();
283-
int maxFilters = SearchModule.INDICES_MAX_CLAUSE_COUNT_SETTING.get(Settings.EMPTY);
284-
for (int i = 0; i <= maxFilters; i++) {
285-
filtersMap.add("tag" + i, termQuery("tag", "tag" + i));
286-
}
280+
int originalMaxClauses = IndexSearcher.getMaxClauseCount();
287281

288282
try {
289-
client().prepareSearch("idx").addAggregation(adjacencyMatrix("tags", "\t", filtersMap)).get();
290-
fail("SearchPhaseExecutionException should have been thrown");
291-
} catch (SearchPhaseExecutionException ex) {
292-
assertThat(ex.getCause().getMessage(), containsString("Number of filters is too large"));
283+
// Create more filters than is permitted by Lucene Bool clause settings.
284+
MapBuilder filtersMap = new MapBuilder();
285+
int maxFilters = randomIntBetween(50, 100);
286+
IndexSearcher.setMaxClauseCount(maxFilters);
287+
for (int i = 0; i <= maxFilters; i++) {
288+
filtersMap.add("tag" + i, termQuery("tag", "tag" + i));
289+
}
290+
291+
try {
292+
client().prepareSearch("idx").addAggregation(adjacencyMatrix("tags", "\t", filtersMap)).get();
293+
fail("SearchPhaseExecutionException should have been thrown");
294+
} catch (SearchPhaseExecutionException ex) {
295+
assertThat(ex.getCause().getMessage(), containsString("Number of filters is too large"));
296+
}
297+
298+
} finally {
299+
IndexSearcher.setMaxClauseCount(originalMaxClauses);
293300
}
294301
}
295302

server/src/internalClusterTest/java/org/elasticsearch/search/query/QueryStringIT.java

Lines changed: 25 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
package org.elasticsearch.search.query;
1010

11+
import org.apache.lucene.search.IndexSearcher;
1112
import org.elasticsearch.ExceptionsHelper;
1213
import org.elasticsearch.action.index.IndexRequestBuilder;
1314
import org.elasticsearch.action.search.SearchResponse;
@@ -17,12 +18,10 @@
1718
import org.elasticsearch.index.query.QueryStringQueryBuilder;
1819
import org.elasticsearch.search.SearchHit;
1920
import org.elasticsearch.search.SearchHits;
20-
import org.elasticsearch.search.SearchModule;
2121
import org.elasticsearch.test.ESIntegTestCase;
2222
import org.elasticsearch.xcontent.XContentBuilder;
2323
import org.elasticsearch.xcontent.XContentType;
2424
import org.junit.Before;
25-
import org.junit.BeforeClass;
2625

2726
import java.io.IOException;
2827
import java.util.ArrayList;
@@ -42,28 +41,13 @@
4241

4342
public class QueryStringIT extends ESIntegTestCase {
4443

45-
private static int CLUSTER_MAX_CLAUSE_COUNT;
46-
47-
@BeforeClass
48-
public static void createRandomClusterSetting() {
49-
CLUSTER_MAX_CLAUSE_COUNT = randomIntBetween(50, 100);
50-
}
51-
5244
@Before
5345
public void setup() throws Exception {
5446
String indexBody = copyToStringFromClasspath("/org/elasticsearch/search/query/all-query-index.json");
5547
prepareCreate("test").setSource(indexBody, XContentType.JSON).get();
5648
ensureGreen("test");
5749
}
5850

59-
@Override
60-
protected Settings nodeSettings(int nodeOrdinal, Settings otherSettings) {
61-
return Settings.builder()
62-
.put(super.nodeSettings(nodeOrdinal, otherSettings))
63-
.put(SearchModule.INDICES_MAX_CLAUSE_COUNT_SETTING.getKey(), CLUSTER_MAX_CLAUSE_COUNT)
64-
.build();
65-
}
66-
6751
public void testBasicAllQuery() throws Exception {
6852
List<IndexRequestBuilder> reqs = new ArrayList<>();
6953
reqs.add(client().prepareIndex("test").setId("1").setSource("f1", "foo bar baz"));
@@ -250,40 +234,18 @@ public void testAllFieldsWithSpecifiedLeniency() throws IOException {
250234
assertThat(e.getCause().getMessage(), containsString("unit [D] not supported for date math [-2D]"));
251235
}
252236

253-
// The only expectation for this test is to not throw exception
254-
public void testLimitOnExpandedFieldsButIgnoreUnmappedFields() throws Exception {
255-
XContentBuilder builder = jsonBuilder();
256-
builder.startObject();
257-
builder.startObject("_doc");
258-
builder.startObject("properties");
259-
for (int i = 0; i < CLUSTER_MAX_CLAUSE_COUNT; i++) {
260-
builder.startObject("field" + i).field("type", "text").endObject();
261-
}
262-
builder.endObject(); // properties
263-
builder.endObject(); // type1
264-
builder.endObject();
265-
266-
assertAcked(prepareCreate("ignoreunmappedfields").setMapping(builder));
267-
268-
client().prepareIndex("ignoreunmappedfields").setId("1").setSource("field1", "foo bar baz").get();
269-
refresh();
237+
public void testLimitOnExpandedFields() throws Exception {
270238

271-
QueryStringQueryBuilder qb = queryStringQuery("bar");
272-
if (randomBoolean()) {
273-
qb.field("*").field("unmappedField1").field("unmappedField2").field("unmappedField3").field("unmappedField4");
274-
}
275-
client().prepareSearch("ignoreunmappedfields").setQuery(qb).get();
276-
}
239+
final int maxClauseCount = randomIntBetween(50, 100);
277240

278-
public void testLimitOnExpandedFields() throws Exception {
279241
XContentBuilder builder = jsonBuilder();
280242
builder.startObject();
281243
{
282244
builder.startObject("_doc");
283245
{
284246
builder.startObject("properties");
285247
{
286-
for (int i = 0; i < CLUSTER_MAX_CLAUSE_COUNT; i++) {
248+
for (int i = 0; i < maxClauseCount; i++) {
287249
builder.startObject("field_A" + i).field("type", "text").endObject();
288250
builder.startObject("field_B" + i).field("type", "text").endObject();
289251
}
@@ -296,25 +258,34 @@ public void testLimitOnExpandedFields() throws Exception {
296258

297259
assertAcked(
298260
prepareCreate("testindex").setSettings(
299-
Settings.builder().put(MapperService.INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING.getKey(), CLUSTER_MAX_CLAUSE_COUNT + 100)
261+
Settings.builder().put(MapperService.INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING.getKey(), maxClauseCount + 100)
300262
).setMapping(builder)
301263
);
302264

303265
client().prepareIndex("testindex").setId("1").setSource("field_A0", "foo bar baz").get();
304266
refresh();
305267

306-
// single field shouldn't trigger the limit
307-
doAssertOneHitForQueryString("field_A0:foo");
308-
// expanding to the limit should work
309-
doAssertOneHitForQueryString("field_A\\*:foo");
268+
int originalMaxClauses = IndexSearcher.getMaxClauseCount();
269+
try {
270+
271+
IndexSearcher.setMaxClauseCount(maxClauseCount);
272+
273+
// single field shouldn't trigger the limit
274+
doAssertOneHitForQueryString("field_A0:foo");
275+
// expanding to the limit should work
276+
doAssertOneHitForQueryString("field_A\\*:foo");
310277

311-
// adding a non-existing field on top shouldn't overshoot the limit
312-
doAssertOneHitForQueryString("field_A\\*:foo unmapped:something");
278+
// adding a non-existing field on top shouldn't overshoot the limit
279+
doAssertOneHitForQueryString("field_A\\*:foo unmapped:something");
313280

314-
// the following should exceed the limit
315-
doAssertLimitExceededException("foo", CLUSTER_MAX_CLAUSE_COUNT * 2, "*");
316-
doAssertLimitExceededException("*:foo", CLUSTER_MAX_CLAUSE_COUNT * 2, "*");
317-
doAssertLimitExceededException("field_\\*:foo", CLUSTER_MAX_CLAUSE_COUNT * 2, "field_*");
281+
// the following should exceed the limit
282+
doAssertLimitExceededException("foo", IndexSearcher.getMaxClauseCount() * 2, "*");
283+
doAssertLimitExceededException("*:foo", IndexSearcher.getMaxClauseCount() * 2, "*");
284+
doAssertLimitExceededException("field_\\*:foo", IndexSearcher.getMaxClauseCount() * 2, "field_*");
285+
286+
} finally {
287+
IndexSearcher.setMaxClauseCount(originalMaxClauses);
288+
}
318289
}
319290

320291
private void doAssertOneHitForQueryString(String queryString) {
@@ -340,7 +311,7 @@ private void doAssertLimitExceededException(String queryString, int exceedingFie
340311
"field expansion for ["
341312
+ inputFieldPattern
342313
+ "] matches too many fields, limit: "
343-
+ CLUSTER_MAX_CLAUSE_COUNT
314+
+ IndexSearcher.getMaxClauseCount()
344315
+ ", got: "
345316
+ exceedingFieldCount
346317
)

server/src/internalClusterTest/java/org/elasticsearch/search/query/SimpleQueryStringIT.java

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.apache.lucene.analysis.TokenFilter;
1212
import org.apache.lucene.analysis.TokenStream;
1313
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
14+
import org.apache.lucene.search.IndexSearcher;
1415
import org.elasticsearch.ExceptionsHelper;
1516
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
1617
import org.elasticsearch.action.index.IndexRequestBuilder;
@@ -29,13 +30,11 @@
2930
import org.elasticsearch.plugins.Plugin;
3031
import org.elasticsearch.search.SearchHit;
3132
import org.elasticsearch.search.SearchHits;
32-
import org.elasticsearch.search.SearchModule;
3333
import org.elasticsearch.search.builder.SearchSourceBuilder;
3434
import org.elasticsearch.test.ESIntegTestCase;
3535
import org.elasticsearch.xcontent.XContentBuilder;
3636
import org.elasticsearch.xcontent.XContentFactory;
3737
import org.elasticsearch.xcontent.XContentType;
38-
import org.junit.BeforeClass;
3938

4039
import java.io.IOException;
4140
import java.util.ArrayList;
@@ -69,21 +68,6 @@
6968
*/
7069
public class SimpleQueryStringIT extends ESIntegTestCase {
7170

72-
private static int CLUSTER_MAX_CLAUSE_COUNT;
73-
74-
@BeforeClass
75-
public static void createRandomClusterSetting() {
76-
CLUSTER_MAX_CLAUSE_COUNT = randomIntBetween(60, 100);
77-
}
78-
79-
@Override
80-
protected Settings nodeSettings(int nodeOrdinal, Settings otherSettings) {
81-
return Settings.builder()
82-
.put(super.nodeSettings(nodeOrdinal, otherSettings))
83-
.put(SearchModule.INDICES_MAX_CLAUSE_COUNT_SETTING.getKey(), CLUSTER_MAX_CLAUSE_COUNT)
84-
.build();
85-
}
86-
8771
@Override
8872
protected Collection<Class<? extends Plugin>> nodePlugins() {
8973
return Collections.singletonList(MockAnalysisPlugin.class);
@@ -578,11 +562,14 @@ public void testAllFieldsWithSpecifiedLeniency() throws IOException {
578562
}
579563

580564
public void testLimitOnExpandedFields() throws Exception {
565+
566+
final int maxClauseCount = randomIntBetween(50, 100);
567+
581568
XContentBuilder builder = jsonBuilder();
582569
builder.startObject();
583570
builder.startObject("_doc");
584571
builder.startObject("properties");
585-
for (int i = 0; i < CLUSTER_MAX_CLAUSE_COUNT + 1; i++) {
572+
for (int i = 0; i < maxClauseCount + 1; i++) {
586573
builder.startObject("field" + i).field("type", "text").endObject();
587574
}
588575
builder.endObject(); // properties
@@ -591,15 +578,21 @@ public void testLimitOnExpandedFields() throws Exception {
591578

592579
assertAcked(
593580
prepareCreate("toomanyfields").setSettings(
594-
Settings.builder().put(MapperService.INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING.getKey(), CLUSTER_MAX_CLAUSE_COUNT + 100)
581+
Settings.builder().put(MapperService.INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING.getKey(), maxClauseCount + 100)
595582
).setMapping(builder)
596583
);
597584

598585
client().prepareIndex("toomanyfields").setId("1").setSource("field1", "foo bar baz").get();
599586
refresh();
600587

601-
doAssertLimitExceededException("*", CLUSTER_MAX_CLAUSE_COUNT + 1);
602-
doAssertLimitExceededException("field*", CLUSTER_MAX_CLAUSE_COUNT + 1);
588+
int originalMaxClauses = IndexSearcher.getMaxClauseCount();
589+
try {
590+
IndexSearcher.setMaxClauseCount(maxClauseCount);
591+
doAssertLimitExceededException("*", maxClauseCount + 1);
592+
doAssertLimitExceededException("field*", maxClauseCount + 1);
593+
} finally {
594+
IndexSearcher.setMaxClauseCount(originalMaxClauses);
595+
}
603596
}
604597

605598
private void doAssertLimitExceededException(String field, int exceedingFieldCount) {
@@ -610,7 +603,9 @@ private void doAssertLimitExceededException(String field, int exceedingFieldCoun
610603
});
611604
assertThat(
612605
ExceptionsHelper.unwrap(e, IllegalArgumentException.class).getMessage(),
613-
containsString("field expansion matches too many fields, limit: " + CLUSTER_MAX_CLAUSE_COUNT + ", got: " + exceedingFieldCount)
606+
containsString(
607+
"field expansion matches too many fields, limit: " + IndexSearcher.getMaxClauseCount() + ", got: " + exceedingFieldCount
608+
)
614609
);
615610
}
616611

0 commit comments

Comments
 (0)