Skip to content

Commit f642b8a

Browse files
authored
Add setting to ignore dynamic fields when field limit is reached (#96235)
Adds a new `index.mapping.total_fields.ignore_dynamic_beyond_limit` index setting. When set to `true`, new fields are added to the mapping as long as the field limit (`index.mapping.total_fields.limit`) is not exceeded. Fields that would exceed the limit are not added to the mapping, similar to `dynamic: false`. Ignored fields are added to the `_ignored` metadata field. Relates to #89911 To make this easier to review, this is split into the following PRs: - [x] #102915 - [x] #102936 - [x] #104769 Related but not a prerequisite: - [ ] #102885
1 parent 75e1181 commit f642b8a

File tree

24 files changed

+795
-128
lines changed

24 files changed

+795
-128
lines changed

docs/changelog/96235.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 96235
2+
summary: Add `index.mapping.total_fields.ignore_dynamic_beyond_limit` setting to ignore dynamic fields when field limit is reached
3+
area: Mapping
4+
type: enhancement
5+
issues: []

docs/reference/mapping/fields/ignored-field.asciidoc

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
The `_ignored` field indexes and stores the names of every field in a document
55
that has been ignored when the document was indexed. This can, for example,
66
be the case when the field was malformed and <<ignore-malformed,`ignore_malformed`>>
7-
was turned on, or when a `keyword` fields value exceeds its optional
8-
<<ignore-above,`ignore_above`>> setting.
7+
was turned on, when a `keyword` field's value exceeds its optional
8+
<<ignore-above,`ignore_above`>> setting, or when
9+
<<mapping-settings-limit,`index.mapping.total_fields.limit`>> has been reached and
10+
<<mapping-settings-limit,`index.mapping.total_fields.ignore_dynamic_beyond_limit`>>
11+
is set to `true`.
912

1013
This field is searchable with <<query-dsl-term-query,`term`>>,
1114
<<query-dsl-terms-query,`terms`>> and <<query-dsl-exists-query,`exists`>>

docs/reference/mapping/mapping-settings-limit.asciidoc

+10-1
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,18 @@ limits the maximum number of clauses in a query.
2020
+
2121
[TIP]
2222
====
23-
If your field mappings contain a large, arbitrary set of keys, consider using the <<flattened,flattened>> data type.
23+
If your field mappings contain a large, arbitrary set of keys, consider using the <<flattened,flattened>> data type,
24+
or setting the index setting `index.mapping.total_fields.ignore_dynamic_beyond_limit` to `true`.
2425
====
2526

27+
`index.mapping.total_fields.ignore_dynamic_beyond_limit`::
28+
This setting determines what happens when a dynamically mapped field would exceed the total fields limit.
29+
When set to `false` (the default), the index request of the document that tries to add a dynamic field to the mapping will fail with the message `Limit of total fields [X] has been exceeded`.
30+
When set to `true`, the index request will not fail.
31+
Instead, fields that would exceed the limit are not added to the mapping, similar to <<dynamic, `dynamic: false`>>.
32+
The fields that were not added to the mapping will be added to the <<mapping-ignored-field, `_ignored` field>>.
33+
The default value is `false`.
34+
2635
`index.mapping.depth.limit`::
2736
The maximum depth for a field, which is measured as the number of inner
2837
objects. For instance, if all fields are defined at the root object level,

docs/reference/mapping/params/dynamic.asciidoc

+7
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,10 @@ accepts the following parameters:
9090
to the mapping, and new fields must be added explicitly.
9191
`strict`:: If new fields are detected, an exception is thrown and the document
9292
is rejected. New fields must be explicitly added to the mapping.
93+
94+
[[dynamic-field-limit]]
95+
==== Behavior when reaching the field limit
96+
Setting `dynamic` to either `true` or `runtime` will only add dynamic fields until <<mapping-settings-limit,`index.mapping.total_fields.limit`>> is reached.
97+
By default, index requests for documents that would exceed the field limit will fail,
98+
unless <<mapping-settings-limit,`index.mapping.total_fields.ignore_dynamic_beyond_limit`>> is set to `true`.
99+
In that case, ignored fields are added to the <<mapping-ignored-field, `_ignored` metadata field>>.

docs/reference/troubleshooting/common-issues/mapping-explosion.asciidoc

+4-1
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,16 @@ timing out in the browser's Developer Tools Network tab.
4141
doesn't normally cause problems unless it's combined with overriding
4242
<<mapping-settings-limit,`index.mapping.total_fields.limit`>>. The
4343
default `1000` limit is considered generous, though overriding to `10000`
44-
doesn't cause noticable impact depending on use case. However, to give
44+
doesn't cause noticeable impact depending on use case. However, to give
4545
a bad example, overriding to `100000` and this limit being hit
4646
by mapping totals would usually have strong performance implications.
4747

4848
If your index mapped fields expect to contain a large, arbitrary set of
4949
keys, you may instead consider:
5050

51+
* Setting <<mapping-settings-limit,`index.mapping.total_fields.ignore_dynamic_beyond_limit`>> to `true`.
52+
Instead of rejecting documents that exceed the field limit, this will ignore dynamic fields once the limit is reached.
53+
5154
* Using the <<flattened,flattened>> data type. Please note,
5255
however, that flattened objects is link:https://github.com/elastic/kibana/issues/25820[not fully supported in {kib}] yet. For example, this could apply to sub-mappings like { `host.name` ,
5356
`host.os`, `host.version` }. Desired fields are still accessed by

server/src/internalClusterTest/java/org/elasticsearch/index/mapper/DynamicMappingIT.java

+227-19
Large diffs are not rendered by default.

server/src/main/java/org/elasticsearch/cluster/metadata/MetadataMappingService.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@ private static ClusterState applyRequest(
151151
MapperService.mergeMappings(
152152
mapperService.documentMapper(),
153153
mapping,
154-
request.autoUpdate() ? MergeReason.MAPPING_AUTO_UPDATE : MergeReason.MAPPING_UPDATE
154+
request.autoUpdate() ? MergeReason.MAPPING_AUTO_UPDATE : MergeReason.MAPPING_UPDATE,
155+
mapperService.getIndexSettings()
155156
);
156157
}
157158
Metadata.Builder builder = Metadata.builder(metadata);

server/src/main/java/org/elasticsearch/common/settings/IndexScopedSettings.java

+1
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ public final class IndexScopedSettings extends AbstractScopedSettings {
151151
Store.INDEX_STORE_STATS_REFRESH_INTERVAL_SETTING,
152152
MapperService.INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING,
153153
MapperService.INDEX_MAPPING_NESTED_DOCS_LIMIT_SETTING,
154+
MapperService.INDEX_MAPPING_IGNORE_DYNAMIC_BEYOND_LIMIT_SETTING,
154155
MapperService.INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING,
155156
MapperService.INDEX_MAPPING_DEPTH_LIMIT_SETTING,
156157
MapperService.INDEX_MAPPING_DIMENSION_FIELDS_LIMIT_SETTING,

server/src/main/java/org/elasticsearch/index/IndexSettings.java

+15
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_DEPTH_LIMIT_SETTING;
4343
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_DIMENSION_FIELDS_LIMIT_SETTING;
4444
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_FIELD_NAME_LENGTH_LIMIT_SETTING;
45+
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_IGNORE_DYNAMIC_BEYOND_LIMIT_SETTING;
4546
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_NESTED_DOCS_LIMIT_SETTING;
4647
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING;
4748
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING;
@@ -753,6 +754,7 @@ private void setRetentionLeaseMillis(final TimeValue retentionLease) {
753754
private volatile long mappingNestedFieldsLimit;
754755
private volatile long mappingNestedDocsLimit;
755756
private volatile long mappingTotalFieldsLimit;
757+
private volatile boolean ignoreDynamicFieldsBeyondLimit;
756758
private volatile long mappingDepthLimit;
757759
private volatile long mappingFieldNameLengthLimit;
758760
private volatile long mappingDimensionFieldsLimit;
@@ -897,6 +899,7 @@ public IndexSettings(final IndexMetadata indexMetadata, final Settings nodeSetti
897899
mappingNestedFieldsLimit = scopedSettings.get(INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING);
898900
mappingNestedDocsLimit = scopedSettings.get(INDEX_MAPPING_NESTED_DOCS_LIMIT_SETTING);
899901
mappingTotalFieldsLimit = scopedSettings.get(INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING);
902+
ignoreDynamicFieldsBeyondLimit = scopedSettings.get(INDEX_MAPPING_IGNORE_DYNAMIC_BEYOND_LIMIT_SETTING);
900903
mappingDepthLimit = scopedSettings.get(INDEX_MAPPING_DEPTH_LIMIT_SETTING);
901904
mappingFieldNameLengthLimit = scopedSettings.get(INDEX_MAPPING_FIELD_NAME_LENGTH_LIMIT_SETTING);
902905
mappingDimensionFieldsLimit = scopedSettings.get(INDEX_MAPPING_DIMENSION_FIELDS_LIMIT_SETTING);
@@ -976,6 +979,10 @@ public IndexSettings(final IndexMetadata indexMetadata, final Settings nodeSetti
976979
scopedSettings.addSettingsUpdateConsumer(INDEX_SOFT_DELETES_RETENTION_LEASE_PERIOD_SETTING, this::setRetentionLeaseMillis);
977980
scopedSettings.addSettingsUpdateConsumer(INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING, this::setMappingNestedFieldsLimit);
978981
scopedSettings.addSettingsUpdateConsumer(INDEX_MAPPING_NESTED_DOCS_LIMIT_SETTING, this::setMappingNestedDocsLimit);
982+
scopedSettings.addSettingsUpdateConsumer(
983+
INDEX_MAPPING_IGNORE_DYNAMIC_BEYOND_LIMIT_SETTING,
984+
this::setIgnoreDynamicFieldsBeyondLimit
985+
);
979986
scopedSettings.addSettingsUpdateConsumer(INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING, this::setMappingTotalFieldsLimit);
980987
scopedSettings.addSettingsUpdateConsumer(INDEX_MAPPING_DEPTH_LIMIT_SETTING, this::setMappingDepthLimit);
981988
scopedSettings.addSettingsUpdateConsumer(INDEX_MAPPING_FIELD_NAME_LENGTH_LIMIT_SETTING, this::setMappingFieldNameLengthLimit);
@@ -1519,6 +1526,14 @@ private void setMappingTotalFieldsLimit(long value) {
15191526
this.mappingTotalFieldsLimit = value;
15201527
}
15211528

1529+
private void setIgnoreDynamicFieldsBeyondLimit(boolean ignoreDynamicFieldsBeyondLimit) {
1530+
this.ignoreDynamicFieldsBeyondLimit = ignoreDynamicFieldsBeyondLimit;
1531+
}
1532+
1533+
public boolean isIgnoreDynamicFieldsBeyondLimit() {
1534+
return ignoreDynamicFieldsBeyondLimit;
1535+
}
1536+
15221537
public long getMappingDepthLimit() {
15231538
return mappingDepthLimit;
15241539
}

server/src/main/java/org/elasticsearch/index/engine/Engine.java

-5
Original file line numberDiff line numberDiff line change
@@ -629,11 +629,6 @@ public DeleteResult(Exception failure, long version, long term, long seqNo, bool
629629
this.found = found;
630630
}
631631

632-
public DeleteResult(Mapping requiredMappingUpdate, String id) {
633-
super(Operation.TYPE.DELETE, requiredMappingUpdate, id);
634-
this.found = false;
635-
}
636-
637632
public boolean isFound() {
638633
return found;
639634
}

server/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java

+12-3
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,11 @@ private static void parseObjectDynamic(DocumentParserContext context, String cur
514514

515515
}
516516
if (context.dynamic() != ObjectMapper.Dynamic.RUNTIME) {
517-
context.addDynamicMapper(dynamicObjectMapper);
517+
if (context.addDynamicMapper(dynamicObjectMapper) == false) {
518+
failIfMatchesRoutingPath(context, currentFieldName);
519+
context.parser().skipChildren();
520+
return;
521+
}
518522
}
519523
if (dynamicObjectMapper instanceof NestedObjectMapper && context.isWithinCopyTo()) {
520524
throwOnCreateDynamicNestedViaCopyTo(dynamicObjectMapper, context);
@@ -556,7 +560,10 @@ private static void parseArrayDynamic(DocumentParserContext context, String curr
556560
parseNonDynamicArray(context, currentFieldName, currentFieldName);
557561
} else {
558562
if (parsesArrayValue(objectMapperFromTemplate)) {
559-
context.addDynamicMapper(objectMapperFromTemplate);
563+
if (context.addDynamicMapper(objectMapperFromTemplate) == false) {
564+
context.parser().skipChildren();
565+
return;
566+
}
560567
context.path().add(currentFieldName);
561568
parseObjectOrField(context, objectMapperFromTemplate);
562569
context.path().remove();
@@ -674,7 +681,9 @@ private static void parseDynamicValue(final DocumentParserContext context, Strin
674681
failIfMatchesRoutingPath(context, currentFieldName);
675682
return;
676683
}
677-
context.dynamic().getDynamicFieldsBuilder().createDynamicFieldFromValue(context, currentFieldName);
684+
if (context.dynamic().getDynamicFieldsBuilder().createDynamicFieldFromValue(context, currentFieldName) == false) {
685+
failIfMatchesRoutingPath(context, currentFieldName);
686+
}
678687
}
679688

680689
private static void ensureNotStrict(DocumentParserContext context, String currentFieldName) {

0 commit comments

Comments
 (0)