Skip to content

Commit 27adf20

Browse files
Refactoring doc values sparse index enabling for the host.name field (#121751)
In this PR, we change how the doc values sparse index is enabled for the host.name keyword field. The initial implementation of the sparse index for host.name was introduced in #120741. Previously, the choice between using an inverted index or a doc values sparse index was determined by the index parameter. With this change, we introduce a new final index-level setting, index.mapping.use_doc_values_sparse_index: - When the setting is true, we enable the sparse index and omit the inverted index for host.name. - When the setting is false (default), we retain the inverted index instead. Additionally, this setting is only exposed if the doc_values_sparse_index feature flag is enabled. This change simplifies enabling the doc values sparse index and makes the selection of indexing strategies explicit at the index level. Moreover, the setting is not dynamic and is exposed only for stateful deployments. The plan is to enable this setting in our nightly benchmarks and evaluate its impact on LogsDB indexing throughput, storage footprint and query latency. Based on benchmarking results, we will decide whether to adopt the sparse index and determine the best way to configure it.
1 parent 1e5ac8b commit 27adf20

File tree

6 files changed

+394
-334
lines changed

6 files changed

+394
-334
lines changed

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

Lines changed: 186 additions & 171 deletions
Large diffs are not rendered by default.

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,14 @@ public boolean isES87TSDBCodecEnabled() {
685685
Property.Final
686686
);
687687

688+
public static final FeatureFlag DOC_VALUES_SKIPPER = new FeatureFlag("doc_values_skipper");
689+
public static final Setting<Boolean> USE_DOC_VALUES_SKIPPER = Setting.boolSetting(
690+
"index.mapping.use_doc_values_skipper",
691+
IndexSettings.DOC_VALUES_SKIPPER.isEnabled(),
692+
Property.IndexScope,
693+
Property.Final
694+
);
695+
688696
/**
689697
* The {@link IndexMode "mode"} of the index.
690698
*/
@@ -922,6 +930,7 @@ private void setRetentionLeaseMillis(final TimeValue retentionLease) {
922930
private final SourceFieldMapper.Mode indexMappingSourceMode;
923931
private final boolean recoverySourceEnabled;
924932
private final boolean recoverySourceSyntheticEnabled;
933+
private final boolean useDocValuesSkipper;
925934

926935
/**
927936
* The maximum number of refresh listeners allows on this shard.
@@ -1103,6 +1112,7 @@ public IndexSettings(final IndexMetadata indexMetadata, final Settings nodeSetti
11031112
recoverySourceEnabled = RecoverySettings.INDICES_RECOVERY_SOURCE_ENABLED_SETTING.get(nodeSettings);
11041113
recoverySourceSyntheticEnabled = DiscoveryNode.isStateless(nodeSettings) == false
11051114
&& scopedSettings.get(RECOVERY_USE_SYNTHETIC_SOURCE_SETTING);
1115+
useDocValuesSkipper = DOC_VALUES_SKIPPER.isEnabled() && scopedSettings.get(USE_DOC_VALUES_SKIPPER);
11061116
if (recoverySourceSyntheticEnabled) {
11071117
if (DiscoveryNode.isStateless(settings)) {
11081118
throw new IllegalArgumentException("synthetic recovery source is only allowed in stateful");
@@ -1822,6 +1832,10 @@ public boolean isRecoverySourceSyntheticEnabled() {
18221832
return recoverySourceSyntheticEnabled;
18231833
}
18241834

1835+
public boolean useDocValuesSkipper() {
1836+
return useDocValuesSkipper;
1837+
}
1838+
18251839
/**
18261840
* The bounds for {@code @timestamp} on this index or
18271841
* {@code null} if there are no bounds.

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,15 @@ private static void validateIndexSortField(SortField sortField) {
302302
}
303303
}
304304

305+
public boolean hasSortOnField(final String fieldName) {
306+
for (FieldSortSpec sortSpec : sortSpecs) {
307+
if (sortSpec.field.equals(fieldName)) {
308+
return true;
309+
}
310+
}
311+
return false;
312+
}
313+
305314
public static class FieldSortSpec {
306315
final String field;
307316
SortOrder order;

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import org.elasticsearch.common.settings.Setting;
2222
import org.elasticsearch.common.settings.Setting.Property;
2323
import org.elasticsearch.common.util.CollectionUtils;
24-
import org.elasticsearch.common.util.FeatureFlag;
2524
import org.elasticsearch.common.util.Maps;
2625
import org.elasticsearch.common.xcontent.support.XContentMapValues;
2726
import org.elasticsearch.index.IndexMode;
@@ -63,8 +62,6 @@
6362

6463
public abstract class FieldMapper extends Mapper {
6564
private static final Logger logger = LogManager.getLogger(FieldMapper.class);
66-
67-
public static final FeatureFlag DOC_VALUES_SPARSE_INDEX = new FeatureFlag("doc_values_sparse_index");
6865
public static final Setting<Boolean> IGNORE_MALFORMED_SETTING = Setting.boolSetting("index.mapping.ignore_malformed", settings -> {
6966
if (IndexSettings.MODE.get(settings) == IndexMode.LOGSDB
7067
&& IndexMetadata.SETTING_INDEX_VERSION_CREATED.get(settings).onOrAfter(IndexVersions.ENABLE_IGNORE_MALFORMED_LOGSDB)) {

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

Lines changed: 47 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
import static org.apache.lucene.index.IndexWriter.MAX_TERM_LENGTH;
8585
import static org.elasticsearch.core.Strings.format;
8686
import static org.elasticsearch.index.IndexSettings.IGNORE_ABOVE_SETTING;
87+
import static org.elasticsearch.index.IndexSettings.USE_DOC_VALUES_SKIPPER;
8788

8889
/**
8990
* A field mapper for keywords. This mapper accepts strings and indexes them as-is.
@@ -201,6 +202,7 @@ public static final class Builder extends FieldMapper.DimensionBuilder {
201202
private final IndexAnalyzers indexAnalyzers;
202203
private final ScriptCompiler scriptCompiler;
203204
private final IndexVersion indexCreatedVersion;
205+
private final boolean useDocValuesSkipper;
204206

205207
public Builder(final String name, final MappingParserContext mappingParserContext) {
206208
this(
@@ -210,7 +212,8 @@ public Builder(final String name, final MappingParserContext mappingParserContex
210212
IGNORE_ABOVE_SETTING.get(mappingParserContext.getSettings()),
211213
mappingParserContext.getIndexSettings().getIndexVersionCreated(),
212214
mappingParserContext.getIndexSettings().getMode(),
213-
mappingParserContext.getIndexSettings().getIndexSortConfig()
215+
mappingParserContext.getIndexSettings().getIndexSortConfig(),
216+
USE_DOC_VALUES_SKIPPER.get(mappingParserContext.getSettings())
214217
);
215218
}
216219

@@ -221,7 +224,7 @@ public Builder(final String name, final MappingParserContext mappingParserContex
221224
int ignoreAboveDefault,
222225
IndexVersion indexCreatedVersion
223226
) {
224-
this(name, indexAnalyzers, scriptCompiler, ignoreAboveDefault, indexCreatedVersion, IndexMode.STANDARD, null);
227+
this(name, indexAnalyzers, scriptCompiler, ignoreAboveDefault, indexCreatedVersion, IndexMode.STANDARD, null, false);
225228
}
226229

227230
private Builder(
@@ -231,7 +234,8 @@ private Builder(
231234
int ignoreAboveDefault,
232235
IndexVersion indexCreatedVersion,
233236
IndexMode indexMode,
234-
IndexSortConfig indexSortConfig
237+
IndexSortConfig indexSortConfig,
238+
boolean useDocValuesSkipper
235239
) {
236240
super(name);
237241
this.indexAnalyzers = indexAnalyzers;
@@ -268,6 +272,7 @@ private Builder(
268272
});
269273
this.indexSortConfig = indexSortConfig;
270274
this.indexMode = indexMode;
275+
this.useDocValuesSkipper = useDocValuesSkipper;
271276
}
272277

273278
public Builder(String name, IndexVersion indexCreatedVersion) {
@@ -394,7 +399,13 @@ private KeywordFieldType buildFieldType(MapperBuilderContext context, FieldType
394399

395400
@Override
396401
public KeywordFieldMapper build(MapperBuilderContext context) {
397-
FieldType fieldtype = resolveFieldType(indexCreatedVersion, indexSortConfig, indexMode, context.buildFullName(leafName()));
402+
FieldType fieldtype = resolveFieldType(
403+
useDocValuesSkipper,
404+
indexCreatedVersion,
405+
indexSortConfig,
406+
indexMode,
407+
context.buildFullName(leafName())
408+
);
398409
fieldtype.setOmitNorms(this.hasNorms.getValue() == false);
399410
fieldtype.setStored(this.stored.getValue());
400411
fieldtype.setDocValuesType(this.hasDocValues.getValue() ? DocValuesType.SORTED_SET : DocValuesType.NONE);
@@ -417,65 +428,40 @@ public KeywordFieldMapper build(MapperBuilderContext context) {
417428
buildFieldType(context, fieldtype),
418429
builderParams(this, context),
419430
context.isSourceSynthetic(),
431+
useDocValuesSkipper,
420432
this
421433
);
422434
}
423435

424436
private FieldType resolveFieldType(
437+
final boolean useDocValuesSkipper,
425438
final IndexVersion indexCreatedVersion,
426439
final IndexSortConfig indexSortConfig,
427440
final IndexMode indexMode,
428441
final String fullFieldName
429442
) {
430-
if (FieldMapper.DOC_VALUES_SPARSE_INDEX.isEnabled()
443+
if (useDocValuesSkipper
431444
&& indexCreatedVersion.onOrAfter(IndexVersions.HOSTNAME_DOC_VALUES_SPARSE_INDEX)
432-
&& shouldUseDocValuesSparseIndex(indexSortConfig, indexMode, fullFieldName)) {
445+
&& shouldUseDocValuesSkipper(hasDocValues.getValue(), indexSortConfig, indexMode, fullFieldName)) {
433446
return new FieldType(Defaults.FIELD_TYPE_WITH_SKIP_DOC_VALUES);
434447
}
435448
return new FieldType(Defaults.FIELD_TYPE);
436449
}
437450

438-
/**
439-
* Determines whether to use a sparse index representation for doc values.
440-
*
441-
* <p>If the field is explicitly indexed by setting {@code index: true}, we do not use
442-
* a sparse doc values index but instead rely on the inverted index, as is typically
443-
* the case for keyword fields.</p>
444-
*
445-
* <p>This method checks several conditions to decide if the sparse index format
446-
* should be applied:</p>
447-
*
448-
* <ul>
449-
* <li>Returns {@code false} immediately if the field is explicitly indexed.</li>
450-
* <li>Ensures the field is not explicitly configured as indexed (i.e., {@code index} has its default value).</li>
451-
* <li>Requires doc values to be enabled.</li>
452-
* <li>Index mode must be {@link IndexMode#LOGSDB}.</li>
453-
* <li>Field name must be {@code host.name}.</li>
454-
* <li>The {@code host.name} field must be a primary sort field.</li>
455-
* </ul>
456-
*
457-
* <p>Returns {@code true} if all conditions are met, indicating that sparse doc values
458-
* should be used. Otherwise, returns {@code false}.</p>
459-
*
460-
* @param indexSortConfig The index sort configuration, used to check primary sorting.
461-
* @param indexMode The mode of the index, which must be {@link IndexMode#LOGSDB}.
462-
* @param fullFieldName The name of the field being checked, which must be {@code host.name}.
463-
* @return {@code true} if sparse doc values should be used, otherwise {@code false}.
464-
*/
465-
466-
private boolean shouldUseDocValuesSparseIndex(
451+
private static boolean shouldUseDocValuesSkipper(
452+
final boolean hasDocValues,
467453
final IndexSortConfig indexSortConfig,
468454
final IndexMode indexMode,
469455
final String fullFieldName
470456
) {
471-
if (indexed.isSet() && indexed.getValue()) {
472-
return false;
473-
}
474-
return indexed.isConfigured() == false
475-
&& hasDocValues.getValue()
457+
return hasDocValues
476458
&& IndexMode.LOGSDB.equals(indexMode)
477459
&& HOST_NAME.equals(fullFieldName)
478-
&& (indexSortConfig != null && indexSortConfig.hasPrimarySortOnField(HOST_NAME));
460+
&& indexSortConfigByHostName(indexSortConfig);
461+
}
462+
463+
private static boolean indexSortConfigByHostName(final IndexSortConfig indexSortConfig) {
464+
return indexSortConfig != null && indexSortConfig.hasIndexSort() && indexSortConfig.hasSortOnField(HOST_NAME);
479465
}
480466
}
481467

@@ -492,7 +478,7 @@ public static final class KeywordFieldType extends StringFieldType {
492478
private final boolean isSyntheticSource;
493479
private final IndexMode indexMode;
494480
private final IndexSortConfig indexSortConfig;
495-
private final boolean hasDocValuesSparseIndex;
481+
private final boolean hasDocValuesSkipper;
496482

497483
public KeywordFieldType(
498484
String name,
@@ -520,7 +506,7 @@ public KeywordFieldType(
520506
this.isSyntheticSource = isSyntheticSource;
521507
this.indexMode = builder.indexMode;
522508
this.indexSortConfig = builder.indexSortConfig;
523-
this.hasDocValuesSparseIndex = DocValuesSkipIndexType.NONE.equals(fieldType.docValuesSkipIndexType()) == false;
509+
this.hasDocValuesSkipper = DocValuesSkipIndexType.NONE.equals(fieldType.docValuesSkipIndexType()) == false;
524510
}
525511

526512
public KeywordFieldType(String name, boolean isIndexed, boolean hasDocValues, Map<String, String> meta) {
@@ -534,7 +520,7 @@ public KeywordFieldType(String name, boolean isIndexed, boolean hasDocValues, Ma
534520
this.isSyntheticSource = false;
535521
this.indexMode = IndexMode.STANDARD;
536522
this.indexSortConfig = null;
537-
this.hasDocValuesSparseIndex = false;
523+
this.hasDocValuesSkipper = false;
538524
}
539525

540526
public KeywordFieldType(String name) {
@@ -559,7 +545,7 @@ public KeywordFieldType(String name, FieldType fieldType) {
559545
this.isSyntheticSource = false;
560546
this.indexMode = IndexMode.STANDARD;
561547
this.indexSortConfig = null;
562-
this.hasDocValuesSparseIndex = DocValuesSkipIndexType.NONE.equals(fieldType.docValuesSkipIndexType()) == false;
548+
this.hasDocValuesSkipper = DocValuesSkipIndexType.NONE.equals(fieldType.docValuesSkipIndexType()) == false;
563549
}
564550

565551
public KeywordFieldType(String name, NamedAnalyzer analyzer) {
@@ -573,7 +559,7 @@ public KeywordFieldType(String name, NamedAnalyzer analyzer) {
573559
this.isSyntheticSource = false;
574560
this.indexMode = IndexMode.STANDARD;
575561
this.indexSortConfig = null;
576-
this.hasDocValuesSparseIndex = false;
562+
this.hasDocValuesSkipper = false;
577563
}
578564

579565
@Override
@@ -1021,8 +1007,8 @@ public IndexSortConfig getIndexSortConfig() {
10211007
return indexSortConfig;
10221008
}
10231009

1024-
public boolean hasDocValuesSparseIndex() {
1025-
return hasDocValuesSparseIndex;
1010+
public boolean hasDocValuesSkipper() {
1011+
return hasDocValuesSkipper;
10261012
}
10271013
}
10281014

@@ -1041,13 +1027,15 @@ public boolean hasDocValuesSparseIndex() {
10411027
private final int ignoreAboveDefault;
10421028
private final IndexMode indexMode;
10431029
private final IndexSortConfig indexSortConfig;
1030+
private final boolean useDocValuesSkipper;
10441031

10451032
private KeywordFieldMapper(
10461033
String simpleName,
10471034
FieldType fieldType,
10481035
KeywordFieldType mappedFieldType,
10491036
BuilderParams builderParams,
10501037
boolean isSyntheticSource,
1038+
boolean useDocValuesSkipper,
10511039
Builder builder
10521040
) {
10531041
super(simpleName, mappedFieldType, builderParams);
@@ -1066,6 +1054,7 @@ private KeywordFieldMapper(
10661054
this.ignoreAboveDefault = builder.ignoreAboveDefault;
10671055
this.indexMode = builder.indexMode;
10681056
this.indexSortConfig = builder.indexSortConfig;
1057+
this.useDocValuesSkipper = useDocValuesSkipper;
10691058
}
10701059

10711060
@Override
@@ -1183,9 +1172,16 @@ public Map<String, NamedAnalyzer> indexAnalyzers() {
11831172

11841173
@Override
11851174
public FieldMapper.Builder getMergeBuilder() {
1186-
return new Builder(leafName(), indexAnalyzers, scriptCompiler, ignoreAboveDefault, indexCreatedVersion, indexMode, indexSortConfig)
1187-
.dimension(fieldType().isDimension())
1188-
.init(this);
1175+
return new Builder(
1176+
leafName(),
1177+
indexAnalyzers,
1178+
scriptCompiler,
1179+
ignoreAboveDefault,
1180+
indexCreatedVersion,
1181+
indexMode,
1182+
indexSortConfig,
1183+
useDocValuesSkipper
1184+
).dimension(fieldType().isDimension()).init(this);
11891185
}
11901186

11911187
@Override

0 commit comments

Comments
 (0)