Skip to content

Commit 6cccbf5

Browse files
csouliosnik9000
andauthored
[7.x] Add time_series_dimension and time_series_metric mapping parameters (#78265)
Backports the following PRs: * Add dimension mapping parameter (#74450) Added the dimension parameter to the following field types: keyword ip Numeric field types (integer, long, byte, short) The dimension parameter is of type boolean (default: false) and is used to mark that a field is a time series dimension field. Relates to #74014 * Add constraints to dimension fields (#74939) This PR adds the following constraints to dimension fields: It must be an indexed field and must has doc values It cannot be multi-valued The number of dimension fields in the index mapping must not be more than 16. This should be configurable through an index property (index.mapping.dimension_fields.limit) keyword fields cannot be more than 1024 bytes long keyword fields must not use a normalizer Based on the code added in PR #74450 Relates to #74660 * Expand DocumentMapperTests (#76368) Adds a test for setting the maximum number of dimensions setting and tests the names and types of the metadata fields in the index. Previously we just asserted the count of metadata fields. That made it hard to read failures. * Fix broken test for dimension keywords (#75408) Test was failing because it was testing 1024 bytes long keyword and assertion was failing. Closes #75225 * Checkstyle * Add time_series_metric parameter (#76766) This PR adds the time_series_metric parameter to the following field types: Numeric field types histogram aggregate_metric_double * Rename `dimension` mapping parameter to `time_series_dimension` (#78012) This PR renames dimension mapping parameter to time_series_dimension to make it consistent with time_series_metric parameter (#76766) Relates to #74450 and #74014 * Add time series params to `unsigned_long` and `scaled_float` (#78204) Added the time_series_metric mapping parameter to the unsigned_long and scaled_float field types Added the time_series_dimension mapping parameter to the unsigned_long field type Fixes #78100 Relates to #76766, #74450 and #74014 Co-authored-by: Nik Everett <[email protected]>
1 parent 9800df6 commit 6cccbf5

File tree

54 files changed

+1361
-85
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1361
-85
lines changed

benchmarks/src/main/java/org/elasticsearch/benchmark/script/ScriptScoreBenchmark.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public class ScriptScoreBenchmark {
8383
private final Map<String, MappedFieldType> fieldTypes = org.elasticsearch.core.Map.ofEntries(
8484
org.elasticsearch.core.Map.entry(
8585
"n",
86-
new NumberFieldType("n", NumberType.LONG, false, false, true, true, null, org.elasticsearch.core.Map.of(), null)
86+
new NumberFieldType("n", NumberType.LONG, false, false, true, true, null, org.elasticsearch.core.Map.of(), null, false, null)
8787
)
8888
);
8989
private final IndexFieldDataCache fieldDataCache = new IndexFieldDataCache.None();

docs/reference/mapping/types/keyword.asciidoc

-1
Original file line numberDiff line numberDiff line change
@@ -171,4 +171,3 @@ Dimension fields have the following constraints:
171171
include::constant-keyword.asciidoc[]
172172

173173
include::wildcard.asciidoc[]
174-

modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/ScaledFloatFieldMapper.java

+52-7
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ public static class Builder extends FieldMapper.Builder {
8585

8686
private final Parameter<Map<String, String>> meta = Parameter.metaParam();
8787

88+
/**
89+
* Parameter that marks this field as a time series metric defining its time series metric type.
90+
* For the numeric fields gauge and counter metric types are
91+
* supported
92+
*/
93+
private final Parameter<TimeSeriesParams.MetricType> metric;
94+
8895
public Builder(String name, Settings settings) {
8996
this(name, IGNORE_MALFORMED_SETTING.get(settings), COERCE_SETTING.get(settings));
9097
}
@@ -95,6 +102,18 @@ public Builder(String name, boolean ignoreMalformedByDefault, boolean coerceByDe
95102
= Parameter.explicitBoolParam("ignore_malformed", true, m -> toType(m).ignoreMalformed, ignoreMalformedByDefault);
96103
this.coerce
97104
= Parameter.explicitBoolParam("coerce", true, m -> toType(m).coerce, coerceByDefault);
105+
106+
this.metric = TimeSeriesParams.metricParam(
107+
m -> toType(m).metricType,
108+
TimeSeriesParams.MetricType.gauge,
109+
TimeSeriesParams.MetricType.counter
110+
).addValidator(v -> {
111+
if (v != null && hasDocValues.getValue() == false) {
112+
throw new IllegalArgumentException(
113+
"Field [" + TimeSeriesParams.TIME_SERIES_METRIC_PARAM + "] requires that [" + hasDocValues.name + "] is true"
114+
);
115+
}
116+
});
98117
}
99118

100119
Builder scalingFactor(double scalingFactor) {
@@ -107,15 +126,28 @@ Builder nullValue(double nullValue) {
107126
return this;
108127
}
109128

129+
public Builder metric(TimeSeriesParams.MetricType metric) {
130+
this.metric.setValue(metric);
131+
return this;
132+
}
133+
110134
@Override
111135
protected List<Parameter<?>> getParameters() {
112-
return Arrays.asList(indexed, hasDocValues, stored, ignoreMalformed, meta, scalingFactor, coerce, nullValue);
136+
return Arrays.asList(indexed, hasDocValues, stored, ignoreMalformed, meta, scalingFactor, coerce, nullValue, metric);
113137
}
114138

115139
@Override
116140
public ScaledFloatFieldMapper build(MapperBuilderContext context) {
117-
ScaledFloatFieldType type = new ScaledFloatFieldType(context.buildFullName(name), indexed.getValue(), stored.getValue(),
118-
hasDocValues.getValue(), meta.getValue(), scalingFactor.getValue(), nullValue.getValue());
141+
ScaledFloatFieldType type = new ScaledFloatFieldType(
142+
context.buildFullName(name),
143+
indexed.getValue(),
144+
stored.getValue(),
145+
hasDocValues.getValue(),
146+
meta.getValue(),
147+
scalingFactor.getValue(),
148+
nullValue.getValue(),
149+
metric.getValue()
150+
);
119151
return new ScaledFloatFieldMapper(name, type, multiFieldsBuilder.build(this, context), copyTo.build(), this);
120152
}
121153
}
@@ -126,16 +158,20 @@ public static final class ScaledFloatFieldType extends SimpleMappedFieldType {
126158

127159
private final double scalingFactor;
128160
private final Double nullValue;
161+
private final TimeSeriesParams.MetricType metricType;
162+
129163

130164
public ScaledFloatFieldType(String name, boolean indexed, boolean stored, boolean hasDocValues,
131-
Map<String, String> meta, double scalingFactor, Double nullValue) {
165+
Map<String, String> meta, double scalingFactor, Double nullValue,
166+
TimeSeriesParams.MetricType metricType) {
132167
super(name, indexed, stored, hasDocValues, TextSearchInfo.SIMPLE_MATCH_WITHOUT_TERMS, meta);
133168
this.scalingFactor = scalingFactor;
134169
this.nullValue = nullValue;
170+
this.metricType = metricType;
135171
}
136172

137173
public ScaledFloatFieldType(String name, double scalingFactor) {
138-
this(name, true, false, true, Collections.emptyMap(), scalingFactor, null);
174+
this(name, true, false, true, Collections.emptyMap(), scalingFactor, null, null);
139175
}
140176

141177
public double getScalingFactor() {
@@ -266,6 +302,14 @@ public DocValueFormat docValueFormat(String format, ZoneId timeZone) {
266302
private double scale(Object input) {
267303
return new BigDecimal(Double.toString(parse(input))).multiply(BigDecimal.valueOf(scalingFactor)).doubleValue();
268304
}
305+
306+
/**
307+
* If field is a time series metric field, returns its metric type
308+
* @return the metric type or null
309+
*/
310+
public TimeSeriesParams.MetricType getMetricType() {
311+
return metricType;
312+
}
269313
}
270314

271315
private final Explicit<Boolean> ignoreMalformed;
@@ -278,6 +322,7 @@ private double scale(Object input) {
278322

279323
private final boolean ignoreMalformedByDefault;
280324
private final boolean coerceByDefault;
325+
private final TimeSeriesParams.MetricType metricType;
281326

282327
private ScaledFloatFieldMapper(
283328
String simpleName,
@@ -295,6 +340,7 @@ private ScaledFloatFieldMapper(
295340
this.coerce = builder.coerce.getValue();
296341
this.ignoreMalformedByDefault = builder.ignoreMalformed.getDefaultValue().value();
297342
this.coerceByDefault = builder.coerce.getDefaultValue().value();
343+
this.metricType = builder.metric.getValue();
298344
}
299345

300346
boolean coerce() {
@@ -317,12 +363,11 @@ protected String contentType() {
317363

318364
@Override
319365
public FieldMapper.Builder getMergeBuilder() {
320-
return new Builder(simpleName(), ignoreMalformedByDefault, coerceByDefault).init(this);
366+
return new Builder(simpleName(), ignoreMalformedByDefault, coerceByDefault).metric(metricType).init(this);
321367
}
322368

323369
@Override
324370
protected void parseCreateField(DocumentParserContext context) throws IOException {
325-
326371
XContentParser parser = context.parser();
327372
Object value;
328373
Number numericValue = null;

modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/TokenCountFieldMapper.java

+21-3
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,27 @@ public TokenCountFieldMapper build(MapperBuilderContext context) {
7575

7676
static class TokenCountFieldType extends NumberFieldMapper.NumberFieldType {
7777

78-
TokenCountFieldType(String name, boolean isSearchable, boolean isStored,
79-
boolean hasDocValues, Number nullValue, Map<String, String> meta) {
80-
super(name, NumberFieldMapper.NumberType.INTEGER, isSearchable, isStored, hasDocValues, false, nullValue, meta, null);
78+
TokenCountFieldType(
79+
String name,
80+
boolean isSearchable,
81+
boolean isStored,
82+
boolean hasDocValues,
83+
Number nullValue,
84+
Map<String, String> meta
85+
) {
86+
super(
87+
name,
88+
NumberFieldMapper.NumberType.INTEGER,
89+
isSearchable,
90+
isStored,
91+
hasDocValues,
92+
false,
93+
nullValue,
94+
meta,
95+
null,
96+
false,
97+
null
98+
);
8199
}
82100

83101
@Override

modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/ScaledFloatFieldMapperTests.java

+41
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,47 @@ public void testRejectIndexOptions() {
272272
assertWarnings("Parameter [index_options] has no effect on type [scaled_float] and will be removed in future");
273273
}
274274

275+
public void testMetricType() throws IOException {
276+
// Test default setting
277+
MapperService mapperService = createMapperService(fieldMapping(b -> minimalMapping(b)));
278+
ScaledFloatFieldMapper.ScaledFloatFieldType ft = (ScaledFloatFieldMapper.ScaledFloatFieldType) mapperService.fieldType("field");
279+
assertNull(ft.getMetricType());
280+
281+
assertMetricType("gauge", ScaledFloatFieldMapper.ScaledFloatFieldType::getMetricType);
282+
assertMetricType("counter", ScaledFloatFieldMapper.ScaledFloatFieldType::getMetricType);
283+
284+
{
285+
// Test invalid metric type for this field type
286+
Exception e = expectThrows(MapperParsingException.class, () -> createMapperService(fieldMapping(b -> {
287+
minimalMapping(b);
288+
b.field("time_series_metric", "histogram");
289+
})));
290+
assertThat(
291+
e.getCause().getMessage(),
292+
containsString("Unknown value [histogram] for field [time_series_metric] - accepted values are [gauge, counter]")
293+
);
294+
}
295+
{
296+
// Test invalid metric type for this field type
297+
Exception e = expectThrows(MapperParsingException.class, () -> createMapperService(fieldMapping(b -> {
298+
minimalMapping(b);
299+
b.field("time_series_metric", "unknown");
300+
})));
301+
assertThat(
302+
e.getCause().getMessage(),
303+
containsString("Unknown value [unknown] for field [time_series_metric] - accepted values are [gauge, counter]")
304+
);
305+
}
306+
}
307+
308+
public void testMetricAndDocvalues() {
309+
Exception e = expectThrows(MapperParsingException.class, () -> createDocumentMapper(fieldMapping(b -> {
310+
minimalMapping(b);
311+
b.field("time_series_metric", "counter").field("doc_values", false);
312+
})));
313+
assertThat(e.getCause().getMessage(), containsString("Field [time_series_metric] requires that [doc_values] is true"));
314+
}
315+
275316
@Override
276317
protected void randomFetchTestFieldConfig(XContentBuilder b) throws IOException {
277318
// Large floats are a terrible idea but the round trip should still work no matter how badly you configure the field

modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/ScaledFloatFieldTypeTests.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,15 @@ public void testRangeQuery() throws IOException {
5454
// this test checks that searching scaled floats yields the same results as
5555
// searching doubles that are rounded to the closest half float
5656
ScaledFloatFieldMapper.ScaledFloatFieldType ft = new ScaledFloatFieldMapper.ScaledFloatFieldType(
57-
"scaled_float", true, false, false, Collections.emptyMap(), 0.1 + randomDouble() * 100, null);
57+
"scaled_float",
58+
true,
59+
false,
60+
false,
61+
Collections.emptyMap(),
62+
0.1 + randomDouble() * 100,
63+
null,
64+
null
65+
);
5866
Directory dir = newDirectory();
5967
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(null));
6068
final int numDocs = 1000;

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/tsdb/10_settings.yml

+2
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ enable:
1818
type: date
1919
metricset:
2020
type: keyword
21+
time_series_dimension: true
2122
k8s:
2223
properties:
2324
pod:
2425
properties:
2526
uid:
2627
type: keyword
28+
time_series_dimension: true
2729
name:
2830
type: keyword
2931
ip:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
add time series mappings:
2+
- skip:
3+
version: " - 7.99.99"
4+
reason: introduced in 8.0.0 to be backported to 7.16.0
5+
6+
- do:
7+
indices.create:
8+
index: test_index
9+
body:
10+
settings:
11+
index:
12+
mode: time_series
13+
number_of_replicas: 0
14+
number_of_shards: 2
15+
mappings:
16+
properties:
17+
"@timestamp":
18+
type: date
19+
metricset:
20+
type: keyword
21+
time_series_dimension: true
22+
k8s:
23+
properties:
24+
pod:
25+
properties:
26+
availability_zone:
27+
type: short
28+
time_series_dimension: true
29+
uid:
30+
type: keyword
31+
time_series_dimension: true
32+
name:
33+
type: keyword
34+
ip:
35+
type: ip
36+
time_series_dimension: true
37+
network:
38+
properties:
39+
tx:
40+
type: long
41+
time_series_metric: counter
42+
rx:
43+
type: integer
44+
time_series_metric: gauge
45+
packets_dropped:
46+
type: long
47+
time_series_metric: gauge
48+
latency:
49+
type: double
50+
time_series_metric: gauge

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

+1
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ public final class IndexScopedSettings extends AbstractScopedSettings {
149149
MapperService.INDEX_MAPPING_NESTED_DOCS_LIMIT_SETTING,
150150
MapperService.INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING,
151151
MapperService.INDEX_MAPPING_DEPTH_LIMIT_SETTING,
152+
MapperService.INDEX_MAPPING_DIMENSION_FIELDS_LIMIT_SETTING,
152153
MapperService.INDEX_MAPPING_FIELD_NAME_LENGTH_LIMIT_SETTING,
153154
BitsetFilterCache.INDEX_LOAD_RANDOM_ACCESS_FILTERS_EAGERLY_SETTING,
154155
IndexModule.INDEX_STORE_TYPE_SETTING,

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

+12
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.util.function.Function;
3636

3737
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_DEPTH_LIMIT_SETTING;
38+
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_DIMENSION_FIELDS_LIMIT_SETTING;
3839
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_FIELD_NAME_LENGTH_LIMIT_SETTING;
3940
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_NESTED_DOCS_LIMIT_SETTING;
4041
import static org.elasticsearch.index.mapper.MapperService.INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING;
@@ -452,6 +453,7 @@ private void setRetentionLeaseMillis(final TimeValue retentionLease) {
452453
private volatile long mappingTotalFieldsLimit;
453454
private volatile long mappingDepthLimit;
454455
private volatile long mappingFieldNameLengthLimit;
456+
private volatile long mappingDimensionFieldsLimit;
455457

456458
/**
457459
* The maximum number of refresh listeners allows on this shard.
@@ -579,6 +581,7 @@ public IndexSettings(final IndexMetadata indexMetadata, final Settings nodeSetti
579581
mappingTotalFieldsLimit = scopedSettings.get(INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING);
580582
mappingDepthLimit = scopedSettings.get(INDEX_MAPPING_DEPTH_LIMIT_SETTING);
581583
mappingFieldNameLengthLimit = scopedSettings.get(INDEX_MAPPING_FIELD_NAME_LENGTH_LIMIT_SETTING);
584+
mappingDimensionFieldsLimit = scopedSettings.get(INDEX_MAPPING_DIMENSION_FIELDS_LIMIT_SETTING);
582585

583586
scopedSettings.addSettingsUpdateConsumer(MergePolicyConfig.INDEX_COMPOUND_FORMAT_SETTING, mergePolicyConfig::setNoCFSRatio);
584587
scopedSettings.addSettingsUpdateConsumer(MergePolicyConfig.INDEX_MERGE_POLICY_DELETES_PCT_ALLOWED_SETTING,
@@ -637,6 +640,7 @@ public IndexSettings(final IndexMetadata indexMetadata, final Settings nodeSetti
637640
scopedSettings.addSettingsUpdateConsumer(INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING, this::setMappingTotalFieldsLimit);
638641
scopedSettings.addSettingsUpdateConsumer(INDEX_MAPPING_DEPTH_LIMIT_SETTING, this::setMappingDepthLimit);
639642
scopedSettings.addSettingsUpdateConsumer(INDEX_MAPPING_FIELD_NAME_LENGTH_LIMIT_SETTING, this::setMappingFieldNameLengthLimit);
643+
scopedSettings.addSettingsUpdateConsumer(INDEX_MAPPING_DIMENSION_FIELDS_LIMIT_SETTING, this::setMappingDimensionFieldsLimit);
640644
}
641645

642646
private void setSearchIdleAfter(TimeValue searchIdleAfter) { this.searchIdleAfter = searchIdleAfter; }
@@ -1173,4 +1177,12 @@ public long getMappingFieldNameLengthLimit() {
11731177
private void setMappingFieldNameLengthLimit(long value) {
11741178
this.mappingFieldNameLengthLimit = value;
11751179
}
1180+
1181+
public long getMappingDimensionFieldsLimit() {
1182+
return mappingDimensionFieldsLimit;
1183+
}
1184+
1185+
private void setMappingDimensionFieldsLimit(long value) {
1186+
this.mappingDimensionFieldsLimit = value;
1187+
}
11761188
}

0 commit comments

Comments
 (0)