-
Notifications
You must be signed in to change notification settings - Fork 25.2k
Limit the number of nested documents #27405
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,12 +19,12 @@ | |
|
||
package org.elasticsearch.index.mapper; | ||
|
||
import java.util.HashMap; | ||
import java.util.HashSet; | ||
import org.apache.lucene.index.IndexableField; | ||
import org.elasticsearch.Version; | ||
import org.elasticsearch.common.compress.CompressedXContent; | ||
import org.elasticsearch.common.settings.Settings; | ||
import org.elasticsearch.common.xcontent.XContentBuilder; | ||
import org.elasticsearch.common.xcontent.XContentFactory; | ||
import org.elasticsearch.common.xcontent.XContentType; | ||
import org.elasticsearch.index.mapper.MapperService.MergeReason; | ||
|
@@ -524,4 +524,144 @@ public void testParentObjectMapperAreNested() throws Exception { | |
assertFalse(objectMapper.parentObjectMapperAreNested(mapperService)); | ||
} | ||
|
||
public void testLimitNestedDocsDefaultSettings() throws Exception{ | ||
Settings settings = Settings.builder().build(); | ||
MapperService mapperService = createIndex("test1", settings).mapperService(); | ||
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type").startObject("properties") | ||
.startObject("nested1").field("type", "nested").endObject() | ||
.endObject().endObject().endObject().string(); | ||
DocumentMapper docMapper = mapperService.documentMapperParser().parse("type", new CompressedXContent(mapping)); | ||
long defaultMaxNoNestedDocs = MapperService.INDEX_MAPPING_NESTED_DOCS_LIMIT_SETTING.get(settings); | ||
|
||
// parsing a doc with No. nested objects > defaultMaxNoNestedDocs fails | ||
XContentBuilder docBuilder = XContentFactory.jsonBuilder(); | ||
docBuilder.startObject(); | ||
{ | ||
docBuilder.startArray("nested1"); | ||
{ | ||
for(int i = 0; i <= defaultMaxNoNestedDocs; i++) { | ||
docBuilder.startObject().field("f", i).endObject(); | ||
} | ||
} | ||
docBuilder.endArray(); | ||
} | ||
docBuilder.endObject(); | ||
SourceToParse source1 = SourceToParse.source("test1", "type", "1", docBuilder.bytes(), XContentType.JSON); | ||
MapperParsingException e = expectThrows(MapperParsingException.class, () -> docMapper.parse(source1)); | ||
assertEquals( | ||
"The number of nested documents has exceeded the allowed limit of [" + defaultMaxNoNestedDocs | ||
+ "]. This limit can be set by changing the [" + MapperService.INDEX_MAPPING_NESTED_DOCS_LIMIT_SETTING.getKey() | ||
+ "] index level setting.", | ||
e.getMessage() | ||
); | ||
} | ||
|
||
public void testLimitNestedDocs() throws Exception{ | ||
// setting limit to allow only two nested objects in the whole doc | ||
long maxNoNestedDocs = 2L; | ||
MapperService mapperService = createIndex("test1", Settings.builder() | ||
.put(MapperService.INDEX_MAPPING_NESTED_DOCS_LIMIT_SETTING.getKey(), maxNoNestedDocs).build()).mapperService(); | ||
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type").startObject("properties") | ||
.startObject("nested1").field("type", "nested").endObject() | ||
.endObject().endObject().endObject().string(); | ||
DocumentMapper docMapper = mapperService.documentMapperParser().parse("type", new CompressedXContent(mapping)); | ||
|
||
// parsing a doc with 2 nested objects succeeds | ||
XContentBuilder docBuilder = XContentFactory.jsonBuilder(); | ||
docBuilder.startObject(); | ||
{ | ||
docBuilder.startArray("nested1"); | ||
{ | ||
docBuilder.startObject().field("field1", "11").field("field2", "21").endObject(); | ||
docBuilder.startObject().field("field1", "12").field("field2", "22").endObject(); | ||
} | ||
docBuilder.endArray(); | ||
} | ||
docBuilder.endObject(); | ||
SourceToParse source1 = SourceToParse.source("test1", "type", "1", docBuilder.bytes(), XContentType.JSON); | ||
ParsedDocument doc = docMapper.parse(source1); | ||
assertThat(doc.docs().size(), equalTo(3)); | ||
|
||
// parsing a doc with 3 nested objects fails | ||
XContentBuilder docBuilder2 = XContentFactory.jsonBuilder(); | ||
docBuilder2.startObject(); | ||
{ | ||
docBuilder2.startArray("nested1"); | ||
{ | ||
docBuilder2.startObject().field("field1", "11").field("field2", "21").endObject(); | ||
docBuilder2.startObject().field("field1", "12").field("field2", "22").endObject(); | ||
docBuilder2.startObject().field("field1", "13").field("field2", "23").endObject(); | ||
} | ||
docBuilder2.endArray(); | ||
} | ||
docBuilder2.endObject(); | ||
SourceToParse source2 = SourceToParse.source("test1", "type", "2", docBuilder2.bytes(), XContentType.JSON); | ||
MapperParsingException e = expectThrows(MapperParsingException.class, () -> docMapper.parse(source2)); | ||
assertEquals( | ||
"The number of nested documents has exceeded the allowed limit of [" + maxNoNestedDocs | ||
+ "]. This limit can be set by changing the [" + MapperService.INDEX_MAPPING_NESTED_DOCS_LIMIT_SETTING.getKey() | ||
+ "] index level setting.", | ||
e.getMessage() | ||
); | ||
} | ||
|
||
public void testLimitNestedDocsMultipleNestedFields() throws Exception{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is interesting (and a great test by the way), because I just realized that the maximum number of nested docs check is applied across all nested fields in the document. I didn't immediately get that from the desciption in #26962 or the PR so far, maybe worth adding to the migration notest and docs as well. |
||
// setting limit to allow only two nested objects in the whole doc | ||
long maxNoNestedDocs = 2L; | ||
MapperService mapperService = createIndex("test1", Settings.builder() | ||
.put(MapperService.INDEX_MAPPING_NESTED_DOCS_LIMIT_SETTING.getKey(), maxNoNestedDocs).build()).mapperService(); | ||
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type").startObject("properties") | ||
.startObject("nested1").field("type", "nested").endObject() | ||
.startObject("nested2").field("type", "nested").endObject() | ||
.endObject().endObject().endObject().string(); | ||
DocumentMapper docMapper = mapperService.documentMapperParser().parse("type", new CompressedXContent(mapping)); | ||
|
||
// parsing a doc with 2 nested objects succeeds | ||
XContentBuilder docBuilder = XContentFactory.jsonBuilder(); | ||
docBuilder.startObject(); | ||
{ | ||
docBuilder.startArray("nested1"); | ||
{ | ||
docBuilder.startObject().field("field1", "11").field("field2", "21").endObject(); | ||
} | ||
docBuilder.endArray(); | ||
docBuilder.startArray("nested2"); | ||
{ | ||
docBuilder.startObject().field("field1", "21").field("field2", "22").endObject(); | ||
} | ||
docBuilder.endArray(); | ||
} | ||
docBuilder.endObject(); | ||
SourceToParse source1 = SourceToParse.source("test1", "type", "1", docBuilder.bytes(), XContentType.JSON); | ||
ParsedDocument doc = docMapper.parse(source1); | ||
assertThat(doc.docs().size(), equalTo(3)); | ||
|
||
// parsing a doc with 3 nested objects fails | ||
XContentBuilder docBuilder2 = XContentFactory.jsonBuilder(); | ||
docBuilder2.startObject(); | ||
{ | ||
docBuilder2.startArray("nested1"); | ||
{ | ||
docBuilder2.startObject().field("field1", "11").field("field2", "21").endObject(); | ||
} | ||
docBuilder2.endArray(); | ||
docBuilder2.startArray("nested2"); | ||
{ | ||
docBuilder2.startObject().field("field1", "12").field("field2", "22").endObject(); | ||
docBuilder2.startObject().field("field1", "13").field("field2", "23").endObject(); | ||
} | ||
docBuilder2.endArray(); | ||
|
||
} | ||
docBuilder2.endObject(); | ||
SourceToParse source2 = SourceToParse.source("test1", "type", "2", docBuilder2.bytes(), XContentType.JSON); | ||
MapperParsingException e = expectThrows(MapperParsingException.class, () -> docMapper.parse(source2)); | ||
assertEquals( | ||
"The number of nested documents has exceeded the allowed limit of [" + maxNoNestedDocs | ||
+ "]. This limit can be set by changing the [" + MapperService.INDEX_MAPPING_NESTED_DOCS_LIMIT_SETTING.getKey() | ||
+ "] index level setting.", | ||
e.getMessage() | ||
); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -90,6 +90,12 @@ causing a mapping explosion: | |
Indexing 1 document with 100 nested fields actually indexes 101 documents | ||
as each nested document is indexed as a separate hidden document. | ||
|
||
`index.mapping.nested_objects.limit`:: | ||
The maximum number of `nested` json objects within a single document across | ||
all nested fields, defaults to 10000. Indexing one document with an array of | ||
100 objects within a nested field, will actually create 101 documents, as | ||
each nested object will be indexed as a separate hidden document. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add something that this limit applies accros all nested fields in a document. (see above) |
||
|
||
|
||
[float] | ||
== Dynamic mapping | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
--- | ||
setup: | ||
- do: | ||
indices.create: | ||
index: test_1 | ||
body: | ||
settings: | ||
index.mapping.nested_objects.limit: 2 | ||
mappings: | ||
test_type: | ||
properties: | ||
nested1: | ||
type: nested | ||
|
||
--- | ||
"Indexing a doc with No. nested objects less or equal to index.mapping.nested_objects.limit should succeed": | ||
- skip: | ||
version: " - 6.99.99" | ||
reason: index.mapping.nested_objects setting has been added in 7.0.0 | ||
- do: | ||
create: | ||
index: test_1 | ||
type: test_type | ||
id: 1 | ||
body: | ||
"nested1" : [ { "foo": "bar" }, { "foo": "bar2" } ] | ||
- match: { _version: 1} | ||
|
||
--- | ||
"Indexing a doc with No. nested objects more than index.mapping.nested_objects.limit should fail": | ||
- skip: | ||
version: " - 6.99.99" | ||
reason: index.mapping.nested_objects setting has been added in 7.0.0 | ||
- do: | ||
catch: /The number of nested documents has exceeded the allowed limit of \[2\]. This limit can be set by changing the \[index.mapping.nested_objects.limit\] index level setting\./ | ||
create: | ||
index: test_1 | ||
type: test_type | ||
id: 1 | ||
body: | ||
"nested1" : [ { "foo": "bar" }, { "foo": "bar2" }, { "foo": "bar3" } ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for my understanding, is this only called for nested documents? I see there is another ParseContext implementation with "addDoc()", I guess that one isn't used for parsing nested documents?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cbuescher Yes, this method only invoked to add nested documents. The Lucene document for the non nested parts of the document being indexed is added to the
documents
list in the constructor of this class.