Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2b73b64
Enable skippers
romseygeek Aug 21, 2025
ed6a938
Merge branch 'main' into benchmark/main-enabled-skippers
romseygeek Sep 5, 2025
16b8100
Update docs/changelog/134221.yaml
romseygeek Sep 5, 2025
2d8ab3f
Delete docs/changelog/134221.yaml
romseygeek Sep 5, 2025
05c7e22
Extra load step in TimeSeriesIT
romseygeek Sep 8, 2025
451fb81
Merge remote-tracking branch 'romseygeek/benchmark/main-enabled-skipp…
romseygeek Sep 8, 2025
d3f3bd8
fix index version gating on host.name skipper
romseygeek Sep 8, 2025
f6caa0f
Index versions @timestamp gate
romseygeek Sep 9, 2025
5fe8969
Merge remote-tracking branch 'origin/main' into benchmark/main-enable…
romseygeek Sep 9, 2025
62d2505
... and tsid too
romseygeek Sep 10, 2025
b7dc09f
Merge remote-tracking branch 'origin/main' into benchmark/main-enable…
romseygeek Sep 10, 2025
3f9ad9d
[CI] Auto commit changes from spotless
Sep 10, 2025
09a195a
Merge remote-tracking branch 'origin/main' into benchmark/main-enable…
romseygeek Sep 24, 2025
612c1bc
Add ability to merge SortedNumericDocValuesRangeQueries
romseygeek Sep 19, 2025
b82f902
Fix versions
romseygeek Sep 24, 2025
447aa39
Merge remote-tracking branch 'romseygeek/benchmark/main-enabled-skipp…
romseygeek Sep 24, 2025
9e7e95f
[CI] Auto commit changes from spotless
Sep 24, 2025
129b84c
Rounding can use DocValuesSkipper
romseygeek Sep 25, 2025
d88afa5
Merge remote-tracking branch 'romseygeek/benchmark/main-enabled-skipp…
romseygeek Sep 25, 2025
7151761
Hack: be aware of doc values skippers in VSC.alignsWithSearchIndex()
romseygeek Sep 29, 2025
479c5b0
Use DocValuesSkippers in SearchContextStats
romseygeek Sep 30, 2025
ee9864b
[CI] Auto commit changes from spotless
Sep 30, 2025
b72a89b
Merge remote-tracking branch 'origin/main' into benchmark/main-enable…
romseygeek Oct 8, 2025
0e75c81
precommit
romseygeek Oct 8, 2025
9928e55
Merge remote-tracking branch 'romseygeek/benchmark/main-enabled-skipp…
romseygeek Oct 8, 2025
e49189f
version
romseygeek Oct 8, 2025
3ee948f
Merge remote-tracking branch 'origin/main' into benchmark/main-enable…
romseygeek Oct 9, 2025
125638e
[CI] Auto commit changes from spotless
Oct 9, 2025
ee0219d
tests
romseygeek Oct 9, 2025
a335ad3
Merge remote-tracking branch 'origin/main' into benchmark/main-enable…
romseygeek Oct 9, 2025
6c05d0f
Merge remote-tracking branch 'romseygeek/benchmark/main-enabled-skipp…
romseygeek Oct 9, 2025
7f64beb
Merge branch 'main' into benchmark/main-enabled-skippers
romseygeek Oct 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ public boolean isES87TSDBCodecEnabled() {
public static final boolean DOC_VALUES_SKIPPER = new FeatureFlag("doc_values_skipper").isEnabled();
public static final Setting<Boolean> USE_DOC_VALUES_SKIPPER = Setting.boolSetting(
"index.mapping.use_doc_values_skipper",
false,
true,
Property.IndexScope,
Property.Final
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@ private static Version parseUnchecked(String version) {
public static final IndexVersion KEYWORD_MULTI_FIELDS_NOT_STORED_WHEN_IGNORED = def(9_040_0_00, Version.LUCENE_10_3_0);
public static final IndexVersion UPGRADE_TO_LUCENE_10_3_1 = def(9_041_0_00, Version.LUCENE_10_3_1);

public static final IndexVersion REENABLED_HOSTNAME_DOC_VALUES_SPARSE_INDEX = def(9_042_0_00, Version.LUCENE_10_3_1);
public static final IndexVersion REENABLED_TIMESTAMP_DOC_VALUES_SPARSE_INDEX = def(9_043_0_00, Version.LUCENE_10_3_1);
public static final IndexVersion REENABLED_TIME_SERIES_ID_DOC_VALUES_SPARSE_INDEX = def(9_044_0_00, Version.LUCENE_10_3_1);

/*
* STOP! READ THIS FIRST! No, really,
* ____ _____ ___ ____ _ ____ _____ _ ____ _____ _ _ ___ ____ _____ ___ ____ ____ _____ _
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1184,7 +1184,7 @@ private static boolean shouldUseDocValuesSkipper(
final IndexSortConfig indexSortConfig,
final String fullFieldName
) {
return indexCreatedVersion.onOrAfter(IndexVersions.TIMESTAMP_DOC_VALUES_SPARSE_INDEX)
return indexCreatedVersion.onOrAfter(IndexVersions.REENABLED_TIMESTAMP_DOC_VALUES_SPARSE_INDEX)
&& useDocValuesSkipper
&& hasDocValues
&& (IndexMode.LOGSDB.equals(indexMode) || IndexMode.TIME_SERIES.equals(indexMode))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ private static FieldType resolveFieldType(
assert hasDocValues.getValue();
return new FieldType(Defaults.FIELD_TYPE_WITH_SKIP_DOC_VALUES);
}
if (indexCreatedVersion.onOrAfter(IndexVersions.HOSTNAME_DOC_VALUES_SPARSE_INDEX)
if (indexCreatedVersion.onOrAfter(IndexVersions.REENABLED_HOSTNAME_DOC_VALUES_SPARSE_INDEX)
&& shouldUseDocValuesSkipper(hasDocValues.getValue(), indexSortConfig, indexMode, fullFieldName)) {
return new FieldType(Defaults.FIELD_TYPE_WITH_SKIP_DOC_VALUES);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ public static TimeSeriesIdFieldMapper getInstance(boolean useDocValuesSkipper) {
}

public static TimeSeriesIdFieldMapper getInstance(MappingParserContext context) {
boolean useDocValuesSkipper = context.indexVersionCreated().onOrAfter(IndexVersions.TIME_SERIES_ID_DOC_VALUES_SPARSE_INDEX)
boolean useDocValuesSkipper = context.indexVersionCreated()
.onOrAfter(IndexVersions.REENABLED_TIME_SERIES_ID_DOC_VALUES_SPARSE_INDEX)
&& context.getIndexSettings().useDocValuesSkipper();
return TimeSeriesIdFieldMapper.getInstance(useDocValuesSkipper);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.search.aggregations.bucket.filter;

import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.core.SuppressForbidden;

import java.lang.reflect.Field;
import java.util.Objects;

class MergedDocValuesRangeQuery {

@SuppressForbidden(reason = "Uses reflection to access package-private lucene class")
public static Query merge(Query query, Query extraQuery) {
Class<? extends Query> queryClass = query.getClass();
Class<? extends Query> extraQueryClass = extraQuery.getClass();

if (queryClass.equals(extraQueryClass) == false
|| queryClass.getCanonicalName().equals("org.apache.lucene.document.SortedNumericDocValuesRangeQuery") == false) {
return null;
}

try {
Field fieldName = queryClass.getDeclaredField("field");
fieldName.setAccessible(true);

String field = fieldName.get(query).toString();
if (Objects.equals(field, fieldName.get(extraQuery)) == false) {
return null;
}

Field lowerValue = queryClass.getDeclaredField("lowerValue");
Field upperValue = queryClass.getDeclaredField("upperValue");
lowerValue.setAccessible(true);
upperValue.setAccessible(true);

long q1LowerValue = lowerValue.getLong(query);
long q1UpperValue = upperValue.getLong(query);
long q2LowerValue = lowerValue.getLong(extraQuery);
long q2UpperValue = upperValue.getLong(extraQuery);

if (q1UpperValue < q2LowerValue || q2UpperValue < q1LowerValue) {
return new MatchNoDocsQuery("Non-overlapping range queries");
}

return NumericDocValuesField.newSlowRangeQuery(
field,
Math.max(q1LowerValue, q2LowerValue),
Math.min(q1UpperValue, q2UpperValue)
);

} catch (NoSuchFieldException | IllegalAccessException e) {
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,12 @@ QueryToFilterAdapter union(Query extraQuery) throws IOException {
extraQuery = searcher().rewrite(new ConstantScoreQuery(extraQuery));
Query unwrappedExtraQuery = unwrap(extraQuery);
Query unwrappedQuery = unwrap(query);
if (unwrappedQuery instanceof PointRangeQuery q1 && unwrappedExtraQuery instanceof PointRangeQuery q2) {
Query merged = MergedPointRangeQuery.merge(q1, q2);
if (merged != null) {
// Should we rewrap here?
return new QueryToFilterAdapter(searcher(), key(), merged);
}

Query merged = maybeMergeRangeQueries(unwrappedQuery, unwrappedExtraQuery);
if (merged != null) {
return new QueryToFilterAdapter(searcher(), key(), merged);
}

BooleanQuery.Builder builder = new BooleanQuery.Builder();
builder.add(query, BooleanClause.Occur.FILTER);
builder.add(extraQuery, BooleanClause.Occur.FILTER);
Expand All @@ -155,6 +154,13 @@ public boolean isInefficientUnion() {
};
}

private static Query maybeMergeRangeQueries(Query query, Query extraQuery) throws IOException {
if (query instanceof PointRangeQuery q1 && extraQuery instanceof PointRangeQuery q2) {
return MergedPointRangeQuery.merge(q1, q2);
}
return MergedDocValuesRangeQuery.merge(query, extraQuery);
}

private static Query unwrap(Query query) {
while (true) {
switch (query) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.DocValuesSkipper;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.PointValues;
import org.apache.lucene.search.BooleanClause;
Expand Down Expand Up @@ -314,6 +315,12 @@ public Function<Rounding, Rounding.Prepared> roundingPreparer(AggregationContext
range[0] = dft.resolution().parsePointAsMillis(min);
range[1] = dft.resolution().parsePointAsMillis(max);
}
} else if (dft.hasDocValuesSkipper()) {
log.trace("Attempting to apply skipper-based data rounding");
range[0] = dft.resolution()
.roundDownToMillis(DocValuesSkipper.globalMinValue(context.searcher(), fieldContext.field()));
range[1] = dft.resolution()
.roundDownToMillis(DocValuesSkipper.globalMaxValue(context.searcher(), fieldContext.field()));
}
log.trace("Bounds after index bound date rounding: {}, {}", range[0], range[1]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.IndexGeoPointFieldData;
import org.elasticsearch.index.fielddata.IndexNumericFieldData;
import org.elasticsearch.index.mapper.DateFieldMapper;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.NumberFieldMapper;
import org.elasticsearch.index.mapper.RangeFieldMapper;
Expand Down Expand Up @@ -429,7 +430,11 @@ public Function<byte[], Number> getPointReaderOrNull() {
* the ordering.
*/
public boolean alignsWithSearchIndex() {
return script() == null && missing() == null && fieldType() != null && fieldType().indexType().supportsSortShortcuts();
boolean hasDocValuesSkipper = fieldType() instanceof DateFieldMapper.DateFieldType dft && dft.hasDocValuesSkipper();
return script() == null
&& missing() == null
&& fieldType() != null
&& (fieldType().indexType().supportsSortShortcuts() || hasDocValuesSkipper);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.search.aggregations.bucket.filter;

import org.apache.lucene.document.LongPoint;
import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.TermQuery;
import org.elasticsearch.test.ESTestCase;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.nullValue;

public class MergedDocValuesRangeQueryTests extends ESTestCase {

public void testNotRangeQueries() {
assertThat(
MergedDocValuesRangeQuery.merge(LongPoint.newRangeQuery("field", 1, 4), new TermQuery(new Term("field", "foo"))),
nullValue()
);

assertThat(
MergedDocValuesRangeQuery.merge(NumericDocValuesField.newSlowRangeQuery("field", 1, 4), LongPoint.newRangeQuery("field", 2, 4)),
nullValue()
);

assertThat(
MergedDocValuesRangeQuery.merge(LongPoint.newRangeQuery("field", 2, 4), NumericDocValuesField.newSlowRangeQuery("field", 1, 4)),
nullValue()
);
}

public void testDifferentFields() {
assertThat(
MergedDocValuesRangeQuery.merge(
NumericDocValuesField.newSlowRangeQuery("field1", 1, 4),
NumericDocValuesField.newSlowRangeQuery("field2", 2, 4)
),
nullValue()
);
}

public void testNoOverlap() {
assertThat(
MergedDocValuesRangeQuery.merge(
NumericDocValuesField.newSlowRangeQuery("field", 1, 4),
NumericDocValuesField.newSlowRangeQuery("field", 6, 8)
),
instanceOf(MatchNoDocsQuery.class)
);
}

public void testOverlap() {
assertThat(
MergedDocValuesRangeQuery.merge(
NumericDocValuesField.newSlowRangeQuery("field", 1, 6),
NumericDocValuesField.newSlowRangeQuery("field", 4, 8)
),
equalTo(NumericDocValuesField.newSlowRangeQuery("field", 4, 6))
);

assertThat(
MergedDocValuesRangeQuery.merge(
NumericDocValuesField.newSlowRangeQuery("field", 4, 8),
NumericDocValuesField.newSlowRangeQuery("field", 1, 6)
),
equalTo(NumericDocValuesField.newSlowRangeQuery("field", 4, 6))
);

assertThat(
MergedDocValuesRangeQuery.merge(
NumericDocValuesField.newSlowRangeQuery("field", 1, 8),
NumericDocValuesField.newSlowRangeQuery("field", 4, 6)
),
equalTo(NumericDocValuesField.newSlowRangeQuery("field", 4, 6))
);

assertThat(
MergedDocValuesRangeQuery.merge(
NumericDocValuesField.newSlowRangeQuery("field", 4, 6),
NumericDocValuesField.newSlowRangeQuery("field", 1, 8)
),
equalTo(NumericDocValuesField.newSlowRangeQuery("field", 4, 6))
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

package org.elasticsearch.xpack.esql.stats;

import org.apache.lucene.index.DocValuesSkipper;
import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.FieldInfos;
Expand All @@ -17,6 +18,7 @@
import org.apache.lucene.index.PointValues;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.Terms;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.NumericUtils;
import org.elasticsearch.index.mapper.ConstantFieldType;
Expand Down Expand Up @@ -201,21 +203,29 @@ public Object min(FieldName field) {
var stat = cache.computeIfAbsent(field.string(), this::makeFieldStats);
// Consolidate min for indexed date fields only, skip the others and mixed-typed fields.
MappedFieldType fieldType = stat.config.fieldType;
if (fieldType == null || stat.config.indexed == false || fieldType instanceof DateFieldType == false) {
boolean hasDocValueSkipper = fieldType instanceof DateFieldType dft && dft.hasDocValuesSkipper();
if (fieldType == null
|| (hasDocValueSkipper == false && stat.config.indexed == false)
|| fieldType instanceof DateFieldType == false) {
return null;
}
if (stat.min == null) {
var min = new long[] { Long.MAX_VALUE };
Holder<Boolean> foundMinValue = new Holder<>(false);
doWithContexts(r -> {
byte[] minPackedValue = PointValues.getMinPackedValue(r, field.string());
if (minPackedValue != null && minPackedValue.length == 8) {
long minValue = NumericUtils.sortableBytesToLong(minPackedValue, 0);
if (minValue <= min[0]) {
min[0] = minValue;
foundMinValue.set(true);
long minValue = Long.MAX_VALUE;
if (hasDocValueSkipper) {
minValue = DocValuesSkipper.globalMinValue(new IndexSearcher(r), field.string());
} else {
byte[] minPackedValue = PointValues.getMinPackedValue(r, field.string());
if (minPackedValue != null && minPackedValue.length == 8) {
minValue = NumericUtils.sortableBytesToLong(minPackedValue, 0);
}
}
if (minValue <= min[0]) {
min[0] = minValue;
foundMinValue.set(true);
}
return true;
}, true);
stat.min = foundMinValue.get() ? min[0] : null;
Expand All @@ -228,21 +238,29 @@ public Object max(FieldName field) {
var stat = cache.computeIfAbsent(field.string(), this::makeFieldStats);
// Consolidate max for indexed date fields only, skip the others and mixed-typed fields.
MappedFieldType fieldType = stat.config.fieldType;
if (fieldType == null || stat.config.indexed == false || fieldType instanceof DateFieldType == false) {
boolean hasDocValueSkipper = fieldType instanceof DateFieldType dft && dft.hasDocValuesSkipper();
if (fieldType == null
|| (hasDocValueSkipper == false && stat.config.indexed == false)
|| fieldType instanceof DateFieldType == false) {
return null;
}
if (stat.max == null) {
var max = new long[] { Long.MIN_VALUE };
Holder<Boolean> foundMaxValue = new Holder<>(false);
doWithContexts(r -> {
byte[] maxPackedValue = PointValues.getMaxPackedValue(r, field.string());
if (maxPackedValue != null && maxPackedValue.length == 8) {
long maxValue = NumericUtils.sortableBytesToLong(maxPackedValue, 0);
if (maxValue >= max[0]) {
max[0] = maxValue;
foundMaxValue.set(true);
long maxValue = Long.MIN_VALUE;
if (hasDocValueSkipper) {
maxValue = DocValuesSkipper.globalMaxValue(new IndexSearcher(r), field.string());
} else {
byte[] maxPackedValue = PointValues.getMaxPackedValue(r, field.string());
if (maxPackedValue != null && maxPackedValue.length == 8) {
maxValue = NumericUtils.sortableBytesToLong(maxPackedValue, 0);
}
}
if (maxValue >= max[0]) {
max[0] = maxValue;
foundMaxValue.set(true);
}
return true;
}, true);
stat.max = foundMaxValue.get() ? max[0] : null;
Expand Down