diff --git a/modules/data-streams/src/test/java/org/elasticsearch/datastreams/mapper/DataStreamTimestampFieldMapperTests.java b/modules/data-streams/src/test/java/org/elasticsearch/datastreams/mapper/DataStreamTimestampFieldMapperTests.java index b99b6396bd8eb..013020fc55592 100644 --- a/modules/data-streams/src/test/java/org/elasticsearch/datastreams/mapper/DataStreamTimestampFieldMapperTests.java +++ b/modules/data-streams/src/test/java/org/elasticsearch/datastreams/mapper/DataStreamTimestampFieldMapperTests.java @@ -8,6 +8,7 @@ */ package org.elasticsearch.datastreams.mapper; +import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.core.CheckedConsumer; import org.elasticsearch.datastreams.DataStreamsPlugin; @@ -537,4 +538,199 @@ public void testFieldTypeWithDocValuesSkipper_CustomTimestampField() throws IOEx assertFalse(defaultTimestamp.fieldType().hasDocValuesSkipper()); } } + + public void testFieldTypeWithDocValuesSkipper_TSDBModeDisabledDocValuesSkipper() throws IOException { + final Settings settings = Settings.builder() + .put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES.name()) + .put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "dim") + .put(IndexSettings.TIME_SERIES_START_TIME.getKey(), "2021-04-28T00:00:00Z") + .put(IndexSettings.TIME_SERIES_END_TIME.getKey(), "2021-04-29T00:00:00Z") + .put(IndexSettings.USE_DOC_VALUES_SKIPPER.getKey(), false) + .build(); + final MapperService mapperService = createMapperService(settings, timestampMapping(true, b -> { + b.startObject(DataStreamTimestampFieldMapper.DEFAULT_PATH); + b.field("type", "date"); + b.endObject(); + })); + + final DateFieldMapper timestampMapper = (DateFieldMapper) mapperService.documentMapper() + .mappers() + .getMapper(DataStreamTimestampFieldMapper.DEFAULT_PATH); + assumeTrue("doc_values_skipper feature flag enabled", IndexSettings.DOC_VALUES_SKIPPER.isEnabled()); + assertTrue(timestampMapper.fieldType().hasDocValues()); + assertFalse(timestampMapper.fieldType().hasDocValuesSkipper()); + assertTrue(timestampMapper.fieldType().isIndexed()); + } + + public void testFieldTypeWithDocValuesSkipper_TSDBMode() throws IOException { + final Settings settings = Settings.builder() + .put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES.name()) + .put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "dim") + .put(IndexSettings.TIME_SERIES_START_TIME.getKey(), "2021-04-28T00:00:00Z") + .put(IndexSettings.TIME_SERIES_END_TIME.getKey(), "2021-04-29T00:00:00Z") + .build(); + final MapperService mapperService = createMapperService(settings, timestampMapping(true, b -> { + b.startObject(DataStreamTimestampFieldMapper.DEFAULT_PATH); + b.field("type", "date"); + b.endObject(); + })); + + final DateFieldMapper timestampMapper = (DateFieldMapper) mapperService.documentMapper() + .mappers() + .getMapper(DataStreamTimestampFieldMapper.DEFAULT_PATH); + assertTrue(timestampMapper.fieldType().hasDocValues()); + if (IndexSettings.USE_DOC_VALUES_SKIPPER.get(settings)) { + assumeTrue("doc_values_skipper feature flag enabled", IndexSettings.DOC_VALUES_SKIPPER.isEnabled()); + assertFalse(timestampMapper.fieldType().isIndexed()); + assertTrue(timestampMapper.fieldType().hasDocValuesSkipper()); + } else { + // TODO: remove this 'else' branch when removing the `doc_values_skipper` feature flag + assumeFalse("doc_values_skipper feature flag enabled", IndexSettings.DOC_VALUES_SKIPPER.isEnabled() == false); + assertTrue(timestampMapper.fieldType().isIndexed()); + assertFalse(timestampMapper.fieldType().hasDocValuesSkipper()); + } + } + + public void testFieldTypeWithDocValuesSkipper_TSDBModeNoTimestampMapping() throws IOException { + final Settings settings = Settings.builder() + .put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES.name()) + .put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "dim") + .put(IndexSettings.TIME_SERIES_START_TIME.getKey(), "2021-04-28T00:00:00Z") + .put(IndexSettings.TIME_SERIES_END_TIME.getKey(), "2021-04-29T00:00:00Z") + .build(); + final MapperService mapperService = createMapperService(settings, timestampMapping(true, b -> {})); + + final DateFieldMapper timestampMapper = (DateFieldMapper) mapperService.documentMapper() + .mappers() + .getMapper(DataStreamTimestampFieldMapper.DEFAULT_PATH); + assertTrue(timestampMapper.fieldType().hasDocValues()); + if (IndexSettings.USE_DOC_VALUES_SKIPPER.get(settings)) { + assumeTrue("doc_values_skipper feature flag enabled", IndexSettings.DOC_VALUES_SKIPPER.isEnabled()); + assertFalse(timestampMapper.fieldType().isIndexed()); + assertTrue(timestampMapper.fieldType().hasDocValuesSkipper()); + } else { + // TODO: remove this 'else' branch when removing the `doc_values_skipper` feature flag + assumeFalse("doc_values_skipper feature flag enabled", IndexSettings.DOC_VALUES_SKIPPER.isEnabled() == false); + assertTrue(timestampMapper.fieldType().isIndexed()); + assertFalse(timestampMapper.fieldType().hasDocValuesSkipper()); + } + } + + public void testFieldTypeWithDocValuesSkipper_TSDBModeTimestampDateNanos() throws IOException { + final Settings settings = Settings.builder() + .put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES.name()) + .put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "dim") + .put(IndexSettings.TIME_SERIES_START_TIME.getKey(), "2021-04-28T00:00:00Z") + .put(IndexSettings.TIME_SERIES_END_TIME.getKey(), "2021-04-29T00:00:00Z") + .build(); + final MapperService mapperService = withMapping( + new TestMapperServiceBuilder().settings(settings).applyDefaultMapping(false).build(), + timestampMapping(true, b -> { + b.startObject(DataStreamTimestampFieldMapper.DEFAULT_PATH); + b.field("type", "date_nanos"); + b.endObject(); + }) + ); + + final DateFieldMapper timestampMapper = (DateFieldMapper) mapperService.documentMapper() + .mappers() + .getMapper(DataStreamTimestampFieldMapper.DEFAULT_PATH); + assertTrue(timestampMapper.fieldType().hasDocValues()); + if (IndexSettings.USE_DOC_VALUES_SKIPPER.get(settings)) { + assumeTrue("doc_values_skipper feature flag enabled", IndexSettings.DOC_VALUES_SKIPPER.isEnabled()); + assertFalse(timestampMapper.fieldType().isIndexed()); + assertTrue(timestampMapper.fieldType().hasDocValuesSkipper()); + } else { + // TODO: remove this 'else' branch when removing the `doc_values_skipper` feature flag + assumeFalse("doc_values_skipper feature flag enabled", IndexSettings.DOC_VALUES_SKIPPER.isEnabled() == false); + assertTrue(timestampMapper.fieldType().isIndexed()); + assertFalse(timestampMapper.fieldType().hasDocValuesSkipper()); + } + } + + public void testFieldTypeWithDocValuesSkipper_TSDBModeExplicitTimestampIndexEnabledDocValuesSkipper() throws IOException { + final Settings settings = Settings.builder() + .put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES.name()) + .put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "dim") + .put(IndexSettings.TIME_SERIES_START_TIME.getKey(), "2021-04-28T00:00:00Z") + .put(IndexSettings.TIME_SERIES_END_TIME.getKey(), "2021-04-29T00:00:00Z") + .build(); + final MapperService mapperService = createMapperService(settings, timestampMapping(true, b -> { + b.startObject(DataStreamTimestampFieldMapper.DEFAULT_PATH); + b.field("type", "date"); + b.field("index", true); + b.endObject(); + })); + + final DateFieldMapper timestampMapper = (DateFieldMapper) mapperService.documentMapper() + .mappers() + .getMapper(DataStreamTimestampFieldMapper.DEFAULT_PATH); + assertTrue(timestampMapper.fieldType().hasDocValues()); + if (IndexSettings.USE_DOC_VALUES_SKIPPER.get(settings)) { + assumeTrue("doc_values_skipper feature flag enabled", IndexSettings.DOC_VALUES_SKIPPER.isEnabled()); + assertFalse(timestampMapper.fieldType().isIndexed()); + assertTrue(timestampMapper.fieldType().hasDocValuesSkipper()); + } else { + // TODO: remove this 'else' branch when removing the `doc_values_skipper` feature flag + assumeFalse("doc_values_skipper feature flag enabled", IndexSettings.DOC_VALUES_SKIPPER.isEnabled() == false); + assertTrue(timestampMapper.fieldType().isIndexed()); + assertFalse(timestampMapper.fieldType().hasDocValuesSkipper()); + } + } + + public void testFieldTypeWithDocValuesSkipper_TSDBModeExplicitTimestampIndexDisabledDocValuesSkipper() throws IOException { + final Settings settings = Settings.builder() + .put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES.name()) + .put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "dim") + .put(IndexSettings.TIME_SERIES_START_TIME.getKey(), "2021-04-28T00:00:00Z") + .put(IndexSettings.TIME_SERIES_END_TIME.getKey(), "2021-04-29T00:00:00Z") + .put(IndexSettings.USE_DOC_VALUES_SKIPPER.getKey(), false) + .build(); + final MapperService mapperService = createMapperService(settings, timestampMapping(true, b -> { + b.startObject(DataStreamTimestampFieldMapper.DEFAULT_PATH); + b.field("type", "date"); + b.field("index", true); + b.endObject(); + })); + + final DateFieldMapper timestampMapper = (DateFieldMapper) mapperService.documentMapper() + .mappers() + .getMapper(DataStreamTimestampFieldMapper.DEFAULT_PATH); + assumeFalse("doc_values_skipper feature flag enabled", IndexSettings.DOC_VALUES_SKIPPER.isEnabled() == false); + assertTrue(timestampMapper.fieldType().hasDocValues()); + assertFalse(timestampMapper.fieldType().hasDocValuesSkipper()); + assertTrue(timestampMapper.fieldType().isIndexed()); + } + + public void testFieldTypeWithDocValuesSkipper_TSDBModeWithoutDefaultMapping() throws IOException { + final Settings settings = Settings.builder() + .put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES.name()) + .put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "dim") + .put(IndexSettings.TIME_SERIES_START_TIME.getKey(), "2021-04-28T00:00:00Z") + .put(IndexSettings.TIME_SERIES_END_TIME.getKey(), "2021-04-29T00:00:00Z") + .build(); + final MapperService mapperService = withMapping( + new TestMapperServiceBuilder().settings(settings).applyDefaultMapping(false).build(), + timestampMapping(true, b -> { + b.startObject(DataStreamTimestampFieldMapper.DEFAULT_PATH); + b.field("type", "date"); + b.endObject(); + }) + ); + + final DateFieldMapper timestampMapper = (DateFieldMapper) mapperService.documentMapper() + .mappers() + .getMapper(DataStreamTimestampFieldMapper.DEFAULT_PATH); + assertTrue(timestampMapper.fieldType().hasDocValues()); + if (IndexSettings.USE_DOC_VALUES_SKIPPER.get(settings)) { + assumeTrue("doc_values_skipper feature flag enabled", IndexSettings.DOC_VALUES_SKIPPER.isEnabled()); + assertFalse(timestampMapper.fieldType().isIndexed()); + assertTrue(timestampMapper.fieldType().hasDocValuesSkipper()); + } else { + // TODO: remove this 'else' branch when removing the `doc_values_skipper` feature flag + assumeFalse("doc_values_skipper feature flag enabled", IndexSettings.DOC_VALUES_SKIPPER.isEnabled() == false); + assertTrue(timestampMapper.fieldType().isIndexed()); + assertFalse(timestampMapper.fieldType().hasDocValuesSkipper()); + } + } } diff --git a/server/src/internalClusterTest/java/org/elasticsearch/index/shard/SearchIdleIT.java b/server/src/internalClusterTest/java/org/elasticsearch/index/shard/SearchIdleIT.java index dda65ac02a695..03319332003aa 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/index/shard/SearchIdleIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/index/shard/SearchIdleIT.java @@ -242,16 +242,31 @@ public void testSearchIdleBoolQueryMatchOneIndex() throws InterruptedException { // are executed only if we have enough shards. int idleIndexShardsCount = 3; int activeIndexShardsCount = 3; + + var idleIndexSettingsBuilder = Settings.builder() + .put(IndexSettings.INDEX_SEARCH_IDLE_AFTER.getKey(), "500ms") + .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, idleIndexShardsCount) + .put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES) + .put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "routing_field") + .put(IndexSettings.TIME_SERIES_START_TIME.getKey(), "2021-05-10T00:00:00.000Z") + .put(IndexSettings.TIME_SERIES_END_TIME.getKey(), "2021-05-11T00:00:00.000Z"); + + var activeIndexSettingsBuilder = Settings.builder() + .put(IndexSettings.INDEX_SEARCH_IDLE_AFTER.getKey(), "500ms") + .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, activeIndexShardsCount) + .put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES) + .put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "routing_field") + .put(IndexSettings.TIME_SERIES_START_TIME.getKey(), "2021-05-12T00:00:00.000Z") + .put(IndexSettings.TIME_SERIES_END_TIME.getKey(), "2021-05-13T23:59:59.999Z"); + + if (IndexSettings.DOC_VALUES_SKIPPER.isEnabled()) { + idleIndexSettingsBuilder.put(IndexSettings.USE_DOC_VALUES_SKIPPER.getKey(), false); + activeIndexSettingsBuilder.put(IndexSettings.USE_DOC_VALUES_SKIPPER.getKey(), false); + } + createIndex( idleIndex, - Settings.builder() - .put(IndexSettings.INDEX_SEARCH_IDLE_AFTER.getKey(), "500ms") - .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, idleIndexShardsCount) - .put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES) - .put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "routing_field") - .put(IndexSettings.TIME_SERIES_START_TIME.getKey(), "2021-05-10T00:00:00.000Z") - .put(IndexSettings.TIME_SERIES_END_TIME.getKey(), "2021-05-11T00:00:00.000Z") - .build(), + idleIndexSettingsBuilder.build(), "doc", "keyword", "type=keyword", @@ -262,14 +277,7 @@ public void testSearchIdleBoolQueryMatchOneIndex() throws InterruptedException { ); createIndex( activeIndex, - Settings.builder() - .put(IndexSettings.INDEX_SEARCH_IDLE_AFTER.getKey(), "500ms") - .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, activeIndexShardsCount) - .put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES) - .put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "routing_field") - .put(IndexSettings.TIME_SERIES_START_TIME.getKey(), "2021-05-12T00:00:00.000Z") - .put(IndexSettings.TIME_SERIES_END_TIME.getKey(), "2021-05-13T23:59:59.999Z") - .build(), + activeIndexSettingsBuilder.build(), "doc", "keyword", "type=keyword", diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/DataStream.java b/server/src/main/java/org/elasticsearch/cluster/metadata/DataStream.java index cf988f0bea80d..f3f2f6723a1a3 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/DataStream.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/DataStream.java @@ -9,6 +9,9 @@ package org.elasticsearch.cluster.metadata; import org.apache.lucene.document.LongPoint; +import org.apache.lucene.index.DocValuesSkipIndexType; +import org.apache.lucene.index.DocValuesSkipper; +import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.PointValues; import org.elasticsearch.ElasticsearchException; @@ -84,6 +87,12 @@ public static boolean isFailureStoreFeatureFlagEnabled() { // Timeseries indices' leaf readers should be sorted by desc order of their timestamp field, as it allows search time optimizations public static final Comparator TIMESERIES_LEAF_READERS_SORTER = Comparator.comparingLong((LeafReader r) -> { try { + FieldInfo info = r.getFieldInfos().fieldInfo(TIMESTAMP_FIELD_NAME); + if (info != null && info.docValuesSkipIndexType() == DocValuesSkipIndexType.RANGE) { + DocValuesSkipper skipper = r.getDocValuesSkipper(TIMESTAMP_FIELD_NAME); + return skipper.maxValue(); + } + PointValues points = r.getPointValues(TIMESTAMP_FIELD_NAME); if (points != null) { byte[] sortValue = points.getMaxPackedValue(); diff --git a/server/src/main/java/org/elasticsearch/common/lucene/uid/PerThreadIDVersionAndSeqNoLookup.java b/server/src/main/java/org/elasticsearch/common/lucene/uid/PerThreadIDVersionAndSeqNoLookup.java index 0c705f7a36021..19a527cf8648b 100644 --- a/server/src/main/java/org/elasticsearch/common/lucene/uid/PerThreadIDVersionAndSeqNoLookup.java +++ b/server/src/main/java/org/elasticsearch/common/lucene/uid/PerThreadIDVersionAndSeqNoLookup.java @@ -10,6 +10,9 @@ package org.elasticsearch.common.lucene.uid; import org.apache.lucene.document.LongPoint; +import org.apache.lucene.index.DocValuesSkipIndexType; +import org.apache.lucene.index.DocValuesSkipper; +import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.NumericDocValues; @@ -94,15 +97,27 @@ final class PerThreadIDVersionAndSeqNoLookup { this.loadedTimestampRange = loadTimestampRange; // Also check for the existence of the timestamp field, because sometimes a segment can only contain tombstone documents, // which don't have any mapped fields (also not the timestamp field) and just some meta fields like _id, _seq_no etc. - if (loadTimestampRange && reader.getFieldInfos().fieldInfo(DataStream.TIMESTAMP_FIELD_NAME) != null) { - PointValues tsPointValues = reader.getPointValues(DataStream.TIMESTAMP_FIELD_NAME); - assert tsPointValues != null : "no timestamp field for reader:" + reader + " and parent:" + reader.getContext().parent.reader(); - minTimestamp = LongPoint.decodeDimension(tsPointValues.getMinPackedValue(), 0); - maxTimestamp = LongPoint.decodeDimension(tsPointValues.getMaxPackedValue(), 0); - } else { - minTimestamp = 0; - maxTimestamp = Long.MAX_VALUE; + long minTimestamp = 0; + long maxTimestamp = Long.MAX_VALUE; + if (loadTimestampRange) { + FieldInfo info = reader.getFieldInfos().fieldInfo(DataStream.TIMESTAMP_FIELD_NAME); + if (info != null) { + if (info.docValuesSkipIndexType() == DocValuesSkipIndexType.RANGE) { + DocValuesSkipper skipper = reader.getDocValuesSkipper(DataStream.TIMESTAMP_FIELD_NAME); + assert skipper != null : "no skipper for reader:" + reader + " and parent:" + reader.getContext().parent.reader(); + minTimestamp = skipper.minValue(); + maxTimestamp = skipper.maxValue(); + } else { + PointValues tsPointValues = reader.getPointValues(DataStream.TIMESTAMP_FIELD_NAME); + assert tsPointValues != null + : "no timestamp field for reader:" + reader + " and parent:" + reader.getContext().parent.reader(); + minTimestamp = LongPoint.decodeDimension(tsPointValues.getMinPackedValue(), 0); + maxTimestamp = LongPoint.decodeDimension(tsPointValues.getMaxPackedValue(), 0); + } + } } + this.minTimestamp = minTimestamp; + this.maxTimestamp = maxTimestamp; } PerThreadIDVersionAndSeqNoLookup(LeafReader reader, boolean loadTimestampRange) throws IOException { diff --git a/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java index a4a6006b58fd5..2a1d761512852 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java @@ -1031,14 +1031,14 @@ private DateFieldMapper( *

* The doc values skipper is enabled only if {@code index.mapping.use_doc_values_skipper} is set to {@code true}, * the index was created on or after {@link IndexVersions#TIMESTAMP_DOC_VALUES_SPARSE_INDEX}, and the - * field has doc values enabled. Additionally, the index mode must be {@link IndexMode#LOGSDB}, and + * field has doc values enabled. Additionally, the index mode must be {@link IndexMode#LOGSDB} or {@link IndexMode#TIME_SERIES}, and * the index sorting configuration must include the {@code @timestamp} field. * * @param indexCreatedVersion The version of the index when it was created. * @param useDocValuesSkipper Whether the doc values skipper feature is enabled via the {@code index.mapping.use_doc_values_skipper} * setting. * @param hasDocValues Whether the field has doc values enabled. - * @param indexMode The index mode, which must be {@link IndexMode#LOGSDB}. + * @param indexMode The index mode, which must be {@link IndexMode#LOGSDB} or {@link IndexMode#TIME_SERIES}. * @param indexSortConfig The index sorting configuration, which must include the {@code @timestamp} field. * @param fullFieldName The full name of the field being checked, expected to be {@code @timestamp}. * @return {@code true} if the doc values skipper should be used, {@code false} otherwise. @@ -1055,7 +1055,7 @@ private static boolean shouldUseDocValuesSkipper( return indexCreatedVersion.onOrAfter(IndexVersions.TIMESTAMP_DOC_VALUES_SPARSE_INDEX) && useDocValuesSkipper && hasDocValues - && IndexMode.LOGSDB.equals(indexMode) + && (IndexMode.LOGSDB.equals(indexMode) || IndexMode.TIME_SERIES.equals(indexMode)) && indexSortConfig != null && indexSortConfig.hasSortOnField(fullFieldName) && DataStreamTimestampFieldMapper.DEFAULT_PATH.equals(fullFieldName); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/MappingLookup.java b/server/src/main/java/org/elasticsearch/index/mapper/MappingLookup.java index ed02e5fc29617..d0e9c1835bfd4 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/MappingLookup.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/MappingLookup.java @@ -500,13 +500,15 @@ public boolean isDataStreamTimestampFieldEnabled() { } /** - * Returns if this mapping contains a timestamp field that is of type date, indexed and has doc values. - * @return {@code true} if contains a timestamp field of type date that is indexed and has doc values, {@code false} otherwise. + * Returns if this mapping contains a timestamp field that is of type date, has doc values, and is either indexed or uses a doc values + * skipper. + * @return {@code true} if contains a timestamp field of type date that has doc values and is either indexed or uses a doc values + * skipper, {@code false} otherwise. */ public boolean hasTimestampField() { final MappedFieldType mappedFieldType = fieldTypesLookup().get(DataStream.TIMESTAMP_FIELD_NAME); - if (mappedFieldType instanceof DateFieldMapper.DateFieldType) { - return mappedFieldType.isIndexed() && mappedFieldType.hasDocValues(); + if (mappedFieldType instanceof DateFieldMapper.DateFieldType dateMappedFieldType) { + return dateMappedFieldType.hasDocValues() && (dateMappedFieldType.isIndexed() || dateMappedFieldType.hasDocValuesSkipper()); } else { return false; }