Skip to content

Commit 08fd489

Browse files
[ML] Limit ML filter items to 10K (#31731)
Add hard limit to the number of items a filter may have. This serves to protect from excessive overhead due to the filters taking too much memory or lookups becoming too expensive.
1 parent 924b94d commit 08fd489

File tree

3 files changed

+40
-3
lines changed

3 files changed

+40
-3
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/MlFilter.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@
2929

3030
public class MlFilter implements ToXContentObject, Writeable {
3131

32+
/**
33+
* The max number of items allowed per filter.
34+
* Limiting the number of items protects users
35+
* from running into excessive overhead due to
36+
* filters using too much memory and lookups
37+
* becoming too expensive.
38+
*/
39+
private static final int MAX_ITEMS = 10000;
40+
3241
public static final String DOCUMENT_ID_PREFIX = "filter_";
3342

3443
public static final String FILTER_TYPE = "filter";
@@ -62,7 +71,7 @@ private static ObjectParser<Builder, Void> createParser(boolean ignoreUnknownFie
6271
private MlFilter(String id, String description, SortedSet<String> items) {
6372
this.id = Objects.requireNonNull(id);
6473
this.description = description;
65-
this.items = Objects.requireNonNull(items, ITEMS.getPreferredName() + " must not be null");
74+
this.items = Objects.requireNonNull(items);
6675
}
6776

6877
public MlFilter(StreamInput in) throws IOException {
@@ -182,9 +191,13 @@ public Builder setItems(String... items) {
182191

183192
public MlFilter build() {
184193
ExceptionsHelper.requireNonNull(id, MlFilter.ID.getPreferredName());
194+
ExceptionsHelper.requireNonNull(items, MlFilter.ITEMS.getPreferredName());
185195
if (!MlStrings.isValidId(id)) {
186196
throw ExceptionsHelper.badRequestException(Messages.getMessage(Messages.INVALID_ID, ID.getPreferredName(), id));
187197
}
198+
if (items.size() > MAX_ITEMS) {
199+
throw ExceptionsHelper.badRequestException(Messages.getMessage(Messages.FILTER_CONTAINS_TOO_MANY_ITEMS, id, MAX_ITEMS));
200+
}
188201
return new MlFilter(id, description, items);
189202
}
190203
}

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/messages/Messages.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public final class Messages {
4343
"Datafeed frequency [{0}] must be a multiple of the aggregation interval [{1}]";
4444

4545
public static final String FILTER_NOT_FOUND = "No filter with id [{0}] exists";
46+
public static final String FILTER_CONTAINS_TOO_MANY_ITEMS = "Filter [{0}] contains too many items; up to [{1}] items are allowed";
4647

4748
public static final String INCONSISTENT_ID =
4849
"Inconsistent {0}; ''{1}'' specified in the body differs from ''{2}'' specified as a URL argument";

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/MlFilterTests.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import org.elasticsearch.test.AbstractSerializingTestCase;
1414

1515
import java.io.IOException;
16+
import java.util.ArrayList;
17+
import java.util.List;
1618
import java.util.SortedSet;
1719
import java.util.TreeSet;
1820

@@ -71,9 +73,9 @@ public void testNullId() {
7173
}
7274

7375
public void testNullItems() {
74-
NullPointerException ex = expectThrows(NullPointerException.class,
76+
Exception ex = expectThrows(IllegalArgumentException.class,
7577
() -> MlFilter.builder(randomValidFilterId()).setItems((SortedSet<String>) null).build());
76-
assertEquals(MlFilter.ITEMS.getPreferredName() + " must not be null", ex.getMessage());
78+
assertEquals("[items] must not be null.", ex.getMessage());
7779
}
7880

7981
public void testDocumentId() {
@@ -102,6 +104,27 @@ public void testInvalidId() {
102104
assertThat(e.getMessage(), startsWith("Invalid filter_id; 'Invalid id' can contain lowercase"));
103105
}
104106

107+
public void testTooManyItems() {
108+
List<String> items = new ArrayList<>(10001);
109+
for (int i = 0; i < 10001; ++i) {
110+
items.add("item_" + i);
111+
}
112+
ElasticsearchStatusException e = expectThrows(ElasticsearchStatusException.class,
113+
() -> MlFilter.builder("huge").setItems(items).build());
114+
assertThat(e.getMessage(), startsWith("Filter [huge] contains too many items"));
115+
}
116+
117+
public void testGivenItemsAreMaxAllowed() {
118+
List<String> items = new ArrayList<>(10000);
119+
for (int i = 0; i < 10000; ++i) {
120+
items.add("item_" + i);
121+
}
122+
123+
MlFilter hugeFilter = MlFilter.builder("huge").setItems(items).build();
124+
125+
assertThat(hugeFilter.getItems().size(), equalTo(items.size()));
126+
}
127+
105128
public void testItemsAreSorted() {
106129
MlFilter filter = MlFilter.builder("foo").setItems("c", "b", "a").build();
107130
assertThat(filter.getItems(), contains("a", "b", "c"));

0 commit comments

Comments
 (0)