Skip to content

Commit 349dac2

Browse files
authored
Stop iterating over all fields to extract @timestamp value (#110603)
1 parent ccf66a8 commit 349dac2

File tree

11 files changed

+72
-42
lines changed

11 files changed

+72
-42
lines changed

docs/changelog/110603.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 110603
2+
summary: Stop iterating over all fields to extract @timestamp value
3+
area: TSDB
4+
type: enhancement
5+
issues:
6+
- 92297

plugins/mapper-annotated-text/src/test/java/org/elasticsearch/index/mapper/annotatedtext/AnnotatedTextFieldMapperTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ public void testStoreParameterDefaults() throws IOException {
335335
var source = source(TimeSeriesRoutingHashFieldMapper.DUMMY_ENCODED_VALUE, b -> {
336336
b.field("field", "1234");
337337
if (timeSeriesIndexMode) {
338-
b.field("@timestamp", randomMillisUpToYear9999());
338+
b.field("@timestamp", "2000-10-10T23:40:53.384Z");
339339
b.field("dimension", "dimension1");
340340
}
341341
}, null);

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

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
package org.elasticsearch.index.mapper;
1010

11-
import org.apache.lucene.index.DocValuesType;
11+
import org.apache.lucene.document.Field;
12+
import org.apache.lucene.document.LongField;
1213
import org.apache.lucene.index.IndexableField;
1314
import org.apache.lucene.search.Query;
1415
import org.elasticsearch.common.bytes.BytesReference;
@@ -22,7 +23,6 @@
2223
import java.io.IOException;
2324
import java.io.UncheckedIOException;
2425
import java.time.Instant;
25-
import java.util.List;
2626
import java.util.Map;
2727

2828
import static org.elasticsearch.core.TimeValue.NSEC_PER_MSEC;
@@ -36,6 +36,7 @@ public class DataStreamTimestampFieldMapper extends MetadataFieldMapper {
3636

3737
public static final String NAME = "_data_stream_timestamp";
3838
public static final String DEFAULT_PATH = "@timestamp";
39+
public static final String TIMESTAMP_VALUE_KEY = "@timestamp._value";
3940

4041
public static final DataStreamTimestampFieldMapper ENABLED_INSTANCE = new DataStreamTimestampFieldMapper(true);
4142
private static final DataStreamTimestampFieldMapper DISABLED_INSTANCE = new DataStreamTimestampFieldMapper(false);
@@ -189,43 +190,44 @@ public void doValidate(MappingLookup lookup) {
189190
}
190191
}
191192

193+
public static void storeTimestampValueForReuse(LuceneDocument document, long timestamp) {
194+
var existingField = document.getByKey(DataStreamTimestampFieldMapper.TIMESTAMP_VALUE_KEY);
195+
if (existingField != null) {
196+
throw new IllegalArgumentException("data stream timestamp field [" + DEFAULT_PATH + "] encountered multiple values");
197+
}
198+
199+
document.onlyAddKey(
200+
DataStreamTimestampFieldMapper.TIMESTAMP_VALUE_KEY,
201+
new LongField(DataStreamTimestampFieldMapper.TIMESTAMP_VALUE_KEY, timestamp, Field.Store.NO)
202+
);
203+
}
204+
205+
public static long extractTimestampValue(LuceneDocument document) {
206+
IndexableField timestampValueField = document.getByKey(TIMESTAMP_VALUE_KEY);
207+
if (timestampValueField == null) {
208+
throw new IllegalArgumentException("data stream timestamp field [" + DEFAULT_PATH + "] is missing");
209+
}
210+
211+
return timestampValueField.numericValue().longValue();
212+
}
213+
192214
@Override
193215
public void postParse(DocumentParserContext context) throws IOException {
194216
if (enabled == false) {
195217
// not configured, so skip the validation
196218
return;
197219
}
198-
boolean foundFsTimestampField = false;
199-
IndexableField first = null;
200-
final List<IndexableField> fields = context.rootDoc().getFields();
201-
for (int i = 0; i < fields.size(); i++) {
202-
IndexableField indexableField = fields.get(i);
203-
if (DEFAULT_PATH.equals(indexableField.name()) == false) {
204-
continue;
205-
}
206-
if (first == null) {
207-
first = indexableField;
208-
}
209-
if (indexableField.fieldType().docValuesType() == DocValuesType.SORTED_NUMERIC) {
210-
if (foundFsTimestampField) {
211-
throw new IllegalArgumentException("data stream timestamp field [" + DEFAULT_PATH + "] encountered multiple values");
212-
}
213-
foundFsTimestampField = true;
214-
}
215-
}
216220

217-
if (first == null) {
218-
throw new IllegalArgumentException("data stream timestamp field [" + DEFAULT_PATH + "] is missing");
219-
}
221+
long timestamp = extractTimestampValue(context.doc());
222+
220223
var indexMode = context.indexSettings().getMode();
221224
if (indexMode.shouldValidateTimestamp()) {
222225
TimestampBounds bounds = context.indexSettings().getTimestampBounds();
223-
validateTimestamp(bounds, first, context);
226+
validateTimestamp(bounds, timestamp, context);
224227
}
225228
}
226229

227-
private static void validateTimestamp(TimestampBounds bounds, IndexableField field, DocumentParserContext context) {
228-
long originValue = field.numericValue().longValue();
230+
private static void validateTimestamp(TimestampBounds bounds, long originValue, DocumentParserContext context) {
229231
long value = originValue;
230232

231233
Resolution resolution;

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ public DateFieldMapper build(MapperBuilderContext context) {
364364
&& ignoreMalformed.isConfigured() == false) {
365365
ignoreMalformed.setValue(false);
366366
}
367+
367368
return new DateFieldMapper(
368369
leafName(),
369370
ft,
@@ -868,8 +869,10 @@ public DocValueFormat docValueFormat(@Nullable String format, ZoneId timeZone) {
868869
private final ScriptCompiler scriptCompiler;
869870
private final FieldValues<Long> scriptValues;
870871

872+
private final boolean isDataStreamTimestampField;
873+
871874
private DateFieldMapper(
872-
String simpleName,
875+
String leafName,
873876
MappedFieldType mappedFieldType,
874877
MultiFields multiFields,
875878
CopyTo copyTo,
@@ -878,7 +881,7 @@ private DateFieldMapper(
878881
boolean isSourceSynthetic,
879882
Builder builder
880883
) {
881-
super(simpleName, mappedFieldType, multiFields, copyTo, builder.script.get() != null, builder.onScriptError.get());
884+
super(leafName, mappedFieldType, multiFields, copyTo, builder.script.get() != null, builder.onScriptError.get());
882885
this.store = builder.store.getValue();
883886
this.indexed = builder.index.getValue();
884887
this.hasDocValues = builder.docValues.getValue();
@@ -894,6 +897,7 @@ private DateFieldMapper(
894897
this.script = builder.script.get();
895898
this.scriptCompiler = builder.scriptCompiler;
896899
this.scriptValues = builder.scriptValues();
900+
this.isDataStreamTimestampField = mappedFieldType.name().equals(DataStreamTimestampFieldMapper.DEFAULT_PATH);
897901
}
898902

899903
@Override
@@ -942,6 +946,16 @@ protected void parseCreateField(DocumentParserContext context) throws IOExceptio
942946
}
943947

944948
private void indexValue(DocumentParserContext context, long timestamp) {
949+
// DataStreamTimestampFieldMapper and TsidExtractingFieldMapper need to use timestamp value,
950+
// so when this is true we store it in a well-known place
951+
// instead of forcing them to iterate over all fields.
952+
//
953+
// DataStreamTimestampFieldMapper is present and enabled both
954+
// in data streams and standalone indices in time_series mode
955+
if (isDataStreamTimestampField && context.mappingLookup().isDataStreamTimestampFieldEnabled()) {
956+
DataStreamTimestampFieldMapper.storeTimestampValueForReuse(context.doc(), timestamp);
957+
}
958+
945959
if (indexed && hasDocValues) {
946960
context.doc().add(new LongField(fieldType().name(), timestamp));
947961
} else if (hasDocValues) {

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,7 @@ public IndexFieldData.Builder fielddataBuilder(FieldDataContext fieldDataContext
4646
private static final long SEED = 0;
4747

4848
public static void createField(DocumentParserContext context, IndexRouting.ExtractFromSource.Builder routingBuilder, BytesRef tsid) {
49-
final IndexableField timestampField = context.rootDoc().getField(DataStreamTimestampFieldMapper.DEFAULT_PATH);
50-
if (timestampField == null) {
51-
throw new IllegalArgumentException(
52-
"data stream timestamp field [" + DataStreamTimestampFieldMapper.DEFAULT_PATH + "] is missing"
53-
);
54-
}
55-
long timestamp = timestampField.numericValue().longValue();
49+
final long timestamp = DataStreamTimestampFieldMapper.extractTimestampValue(context.doc());
5650
String id;
5751
if (routingBuilder != null) {
5852
byte[] suffix = new byte[16];

server/src/test/java/org/elasticsearch/index/TimeSeriesModeTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,10 @@ public void testSetDefaultTimeRangeValue() {
121121

122122
public void testRequiredRouting() {
123123
Settings s = getSettings();
124+
var mapperService = new TestMapperServiceBuilder().settings(s).applyDefaultMapping(false).build();
124125
Exception e = expectThrows(
125126
IllegalArgumentException.class,
126-
() -> createMapperService(s, topMapping(b -> b.startObject("_routing").field("required", true).endObject()))
127+
() -> withMapping(mapperService, topMapping(b -> b.startObject("_routing").field("required", true).endObject()))
127128
);
128129
assertThat(e.getMessage(), equalTo("routing is forbidden on CRUD operations that target indices in [index.mode=time_series]"));
129130
}

server/src/test/java/org/elasticsearch/index/mapper/BinaryFieldMapperTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ public void testDefaultsForTimeSeriesIndex() throws IOException {
167167

168168
var source = source(TimeSeriesRoutingHashFieldMapper.DUMMY_ENCODED_VALUE, b -> {
169169
b.field("field", Base64.getEncoder().encodeToString(randomByteArrayOfLength(10)));
170-
b.field("@timestamp", randomMillisUpToYear9999());
170+
b.field("@timestamp", "2000-10-10T23:40:53.384Z");
171171
b.field("dimension", "dimension1");
172172
}, null);
173173
ParsedDocument doc = mapper.parse(source);

server/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ public void testStoreParameterDefaults() throws IOException {
294294
var source = source(TimeSeriesRoutingHashFieldMapper.DUMMY_ENCODED_VALUE, b -> {
295295
b.field("field", "1234");
296296
if (timeSeriesIndexMode) {
297-
b.field("@timestamp", randomMillisUpToYear9999());
297+
b.field("@timestamp", "2000-10-10T23:40:53.384Z");
298298
b.field("dimension", "dimension1");
299299
}
300300
}, null);

server/src/test/java/org/elasticsearch/index/mapper/TimeSeriesIdFieldMapperTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ private DocumentMapper createDocumentMapper(String routingPath, XContentBuilder
6767
.put(MapperService.INDEX_MAPPING_DIMENSION_FIELDS_LIMIT_SETTING.getKey(), 200) // Allow tests that use many dimensions
6868
.put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), routingPath)
6969
.put(IndexSettings.TIME_SERIES_START_TIME.getKey(), "2021-04-28T00:00:00Z")
70-
.put(IndexSettings.TIME_SERIES_END_TIME.getKey(), "2021-04-29T00:00:00Z")
70+
.put(IndexSettings.TIME_SERIES_END_TIME.getKey(), "2021-10-29T00:00:00Z")
7171
.build(),
7272
mappings
7373
).documentMapper();

server/src/test/java/org/elasticsearch/index/mapper/TimeSeriesRoutingHashFieldMapperTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ private DocumentMapper createMapper(XContentBuilder mappings) throws IOException
4444
getIndexSettingsBuilder().put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES.name())
4545
.put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "routing path is required")
4646
.put(IndexSettings.TIME_SERIES_START_TIME.getKey(), "2021-04-28T00:00:00Z")
47-
.put(IndexSettings.TIME_SERIES_END_TIME.getKey(), "2021-04-29T00:00:00Z")
47+
.put(IndexSettings.TIME_SERIES_END_TIME.getKey(), "2021-10-29T00:00:00Z")
4848
.build(),
4949
mappings
5050
).documentMapper();

0 commit comments

Comments
 (0)