Skip to content

Commit b636be3

Browse files
authored
Enable synthetic_id by default under feature flag (#144026)
* Enable synthetic_id by default if settings are valid Newly created time_series indices use synthetic id by default. The new IndexVersion, TIME_SERIES_USE_SYNTHETIC_ID_DEFAULT, prevent old indices (with older index version) from changing default behavior. `index.mapping.synthetic_id` defaults to true if - Feature flag TSDB_SYNTHETIC_ID_FEATURE_FLAG is enabled - index mode is time_series - index version is TIME_SERIES_USE_SYNTHETIC_ID_DEFAULT or later - codec is default or unset and setting is still allowed to be explicitly set if index version is TIME_SERIES_USE_SYNTHETIC_ID_94 or later. Only the default behavior is controlled by TIME_SERIES_USE_SYNTHETIC_ID_DEFAULT. This default logic is duplicated by IndexMetadata to make sure metadata always agree with Settings. MODE setting moved above SYNTHETIC_ID to allow access. * NodeFeature index.time_series_synthetic_id_default Making sure to add this node feature in the same PR as adding the new index version, `TIME_SERIES_USE_SYNTHETIC_ID_DEFAULT` to make sure the node feature can be used as a shortcut / replacement for checking version in tests. * Skip tsdb/25_id... and delete/70_tsdb in yamlRestCompatTest The compat tests run with a copy of the yaml file from the BWC branch, which doesn't contain the skip for index.time_series_synthetic_id. This means the hardcoded ids in the test doesn't match with actual default behavior anymore, so we skip those tests in the compatibility run. This is safe because the behavior is not changed for existing indices that don't use synthetic id. Only new indices will have the changed behavior, so there is no compatibility issue. To make this work we nedd to make it possible to skip compatibility tests with overloaded names. If the name of the test is also a name of an operation such as `get` or `delete` in below examples, then the build would fail with ``` Execution failed for task ':rest-api-spec:yamlRestCompatTestTransform'. > class com.fasterxml.jackson.databind.node.ObjectNode cannot be cast to class com.fasterxml.jackson.databind.node.ArrayNode ``` Instead of throwing an (assert) exception if the node to transform (skip) is of the wrong type, we now just ignore it instead. Example of test that could not be skipped before, but that can be skipped now: ``` get: - do: get: index: id_generation_test id: cZZNs7B9sSWsyrL5AAABeRnSA5M ``` --- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> and Cursor
1 parent 256a390 commit b636be3

File tree

8 files changed

+179
-39
lines changed

8 files changed

+179
-39
lines changed

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/test/rest/transform/skip/Skip.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,14 @@ private void addSkip(ArrayNode skipParent) {
8787
@Override
8888
public void transformTest(ObjectNode parent) {
8989
if (testName.isBlank() == false) {
90-
assert parent.get(testName) instanceof ArrayNode;
91-
addSkip((ArrayNode) parent.get(testName));
90+
JsonNode value = parent.get(testName);
91+
// Only apply skip to test documents where the key is the test name and value is the steps array.
92+
// Do not apply to nested keys with the same name (e.g. "do: get: { ... }" request body).
93+
// This makes it possible to skip tests where the test name is an overloaded term such as
94+
// task.skipTest("tsdb/25_id_generation/delete",...)
95+
if (value instanceof ArrayNode) {
96+
addSkip((ArrayNode) value);
97+
}
9298
}
9399
}
94100

modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/TSDBSyntheticIdsIT.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.elasticsearch.action.admin.indices.diskusage.TransportAnalyzeIndexDiskUsageAction;
2525
import org.elasticsearch.action.admin.indices.get.GetIndexResponse;
2626
import org.elasticsearch.action.admin.indices.rollover.RolloverResponse;
27+
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
2728
import org.elasticsearch.action.admin.indices.template.put.TransportPutComposableIndexTemplateAction;
2829
import org.elasticsearch.action.bulk.BulkItemResponse;
2930
import org.elasticsearch.action.support.WriteRequest;
@@ -1297,6 +1298,70 @@ public void testMerge() throws Exception {
12971298
assertShardsHaveNoIdStoredFieldValuesOnDisk(indices);
12981299
}
12991300

1301+
public void testDefaultSetting() throws Exception {
1302+
assumeTrue("Test should only run with feature flag", IndexSettings.TSDB_SYNTHETIC_ID_FEATURE_FLAG);
1303+
1304+
String indexName = randomIndexName();
1305+
1306+
// Don't set IndexSettings.SYNTHETIC_ID to test default behavior.
1307+
// Use default codec so the SYNTHETIC_ID default is true
1308+
// (codec will be randomised by ESIntegTestCase.randomIndexTemplate if not explicitly set)
1309+
Settings.Builder settingsBuilder = Settings.builder()
1310+
.put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES)
1311+
.put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "hostname")
1312+
.put(EngineConfig.INDEX_CODEC_SETTING.getKey(), CodecService.DEFAULT_CODEC);
1313+
final var mapping = """
1314+
{
1315+
"properties": {
1316+
"@timestamp": {
1317+
"type": "date"
1318+
},
1319+
"hostname": {
1320+
"type": "keyword",
1321+
"time_series_dimension": true
1322+
},
1323+
"metric": {
1324+
"properties": {
1325+
"field": {
1326+
"type": "keyword"
1327+
},
1328+
"value": {
1329+
"type": "integer",
1330+
"time_series_metric": "counter"
1331+
}
1332+
}
1333+
}
1334+
}
1335+
}
1336+
""";
1337+
assertAcked(client().admin().indices().prepareCreate(indexName).setSettings(settingsBuilder).setMapping(mapping).get());
1338+
1339+
var timestamp = Instant.now();
1340+
createDocuments(
1341+
indexName,
1342+
document(timestamp, "vm-dev01", "cpu-load", 0),
1343+
document(timestamp.plus(1, ChronoUnit.SECONDS), "vm-dev02", "cpu-load", 1)
1344+
);
1345+
ensureGreen(indexName);
1346+
flushAndRefresh(indexName);
1347+
1348+
GetSettingsResponse getSettingsResponse = client().admin().indices().prepareGetSettings(TEST_REQUEST_TIMEOUT, indexName).get();
1349+
String versionSetting = getSettingsResponse.getSetting(indexName, IndexMetadata.SETTING_INDEX_VERSION_CREATED.getKey());
1350+
IndexVersion version = IndexVersion.fromId(Integer.parseInt(versionSetting));
1351+
assertTrue(version.onOrAfter(IndexVersions.TIME_SERIES_USE_SYNTHETIC_ID_DEFAULT));
1352+
String syntheticIdSetting = getSettingsResponse.getSetting(indexName, IndexSettings.SYNTHETIC_ID.getKey());
1353+
assertThat(syntheticIdSetting, Matchers.nullValue());
1354+
1355+
var diskUsage = diskUsage(indexName);
1356+
var diskUsageIdField = AnalyzeIndexDiskUsageTestUtils.getPerFieldDiskUsage(diskUsage, IdFieldMapper.NAME);
1357+
assertThat("_id field should not have postings on disk", diskUsageIdField.getInvertedIndexBytes(), equalTo(0L));
1358+
assertThat("_id field should have bloom filter usage", diskUsageIdField.getBloomFilterBytes(), greaterThan(0L));
1359+
1360+
var indices = new HashSet<String>();
1361+
indices.add(indexName);
1362+
assertShardsHaveNoIdStoredFieldValuesOnDisk(indices);
1363+
}
1364+
13001365
/**
13011366
* This test verifies that index with synthetic id cannot be created
13021367
* if index version is too low. Imagine a mixed cluster where node A has

rest-api-spec/build.gradle

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,16 @@ tasks.named("yamlRestCompatTestTransform").configure ({ task ->
158158
"get/100_synthetic_source/fields with ignore_malformed",
159159
"Malformed values are now stored in binary doc values which sort differently than stored fields"
160160
)
161+
task.skipTest("delete/70_tsdb/basic tsdb delete", "ids have changed after introduction of synthetic id")
162+
task.skipTest("tsdb/25_id_generation/routing_path matches object", "ids have changed after introduction of synthetic id")
163+
task.skipTest("tsdb/25_id_generation/ids query", "ids have changed after introduction of synthetic id")
164+
task.skipTest("tsdb/25_id_generation/create operation on top of old document fails over bulk", "ids have changed after introduction of synthetic id")
165+
task.skipTest("tsdb/25_id_generation/delete over _bulk", "ids have changed after introduction of synthetic id")
166+
task.skipTest("tsdb/25_id_generation/delete", "ids have changed after introduction of synthetic id")
167+
task.skipTest("tsdb/25_id_generation/index a new document on top of an old one", "ids have changed after introduction of synthetic id")
168+
task.skipTest("tsdb/25_id_generation/generates a consistent id", "ids have changed after introduction of synthetic id")
169+
task.skipTest("tsdb/25_id_generation/create operation on top of old document fails", "ids have changed after introduction of synthetic id")
170+
task.skipTest("tsdb/25_id_generation/get", "ids have changed after introduction of synthetic id")
171+
task.skipTest("tsdb/25_id_generation/routing_path matches deep object", "ids have changed after introduction of synthetic id")
172+
task.skipTest("tsdb/25_id_generation/index a new document on top of an old one over bulk", "ids have changed after introduction of synthetic id")
161173
})

server/src/main/java/org/elasticsearch/cluster/metadata/IndexMetadata.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
import org.elasticsearch.index.IndexSettings;
4848
import org.elasticsearch.index.IndexVersion;
4949
import org.elasticsearch.index.IndexVersions;
50+
import org.elasticsearch.index.codec.CodecService;
51+
import org.elasticsearch.index.engine.EngineConfig;
5052
import org.elasticsearch.index.mapper.DateFieldMapper;
5153
import org.elasticsearch.index.mapper.MapperService;
5254
import org.elasticsearch.index.search.QueryParserHelper;
@@ -2574,15 +2576,7 @@ IndexMetadata build(boolean repair) {
25742576
String indexModeString = settings.get(IndexSettings.MODE.getKey());
25752577
final IndexMode indexMode = indexModeString != null ? IndexMode.fromString(indexModeString.toLowerCase(Locale.ROOT)) : null;
25762578
final boolean isTsdb = indexMode == IndexMode.TIME_SERIES;
2577-
boolean useTimeSeriesSyntheticId = false;
2578-
if (isTsdb
2579-
&& IndexSettings.TSDB_SYNTHETIC_ID_FEATURE_FLAG
2580-
&& indexCreatedVersion.onOrAfter(IndexVersions.TIME_SERIES_USE_SYNTHETIC_ID_94)) {
2581-
var setting = settings.get(IndexSettings.SYNTHETIC_ID.getKey());
2582-
if (setting != null && setting.equalsIgnoreCase(Boolean.TRUE.toString())) {
2583-
useTimeSeriesSyntheticId = true;
2584-
}
2585-
}
2579+
boolean useTimeSeriesSyntheticId = shouldUseTimeSeriesSyntheticId(isTsdb, indexCreatedVersion, settings);
25862580
final boolean sequenceNumbersDisabled = IndexSettings.DISABLE_SEQUENCE_NUMBERS_FEATURE_FLAG
25872581
&& indexCreatedVersion.onOrAfter(IndexVersions.DISABLE_SEQUENCE_NUMBERS)
25882582
&& settings.getAsBoolean(IndexSettings.DISABLE_SEQUENCE_NUMBERS.getKey(), false);
@@ -2642,6 +2636,18 @@ IndexMetadata build(boolean repair) {
26422636
);
26432637
}
26442638

2639+
private static boolean shouldUseTimeSeriesSyntheticId(boolean isTsdb, IndexVersion version, Settings settings) {
2640+
String codecSetting = settings.get(EngineConfig.INDEX_CODEC_SETTING.getKey());
2641+
if (IndexSettings.TSDB_SYNTHETIC_ID_FEATURE_FLAG
2642+
&& isTsdb
2643+
&& version.onOrAfter(IndexVersions.TIME_SERIES_USE_SYNTHETIC_ID_94)
2644+
&& (codecSetting == null || codecSetting.equalsIgnoreCase(CodecService.DEFAULT_CODEC))) {
2645+
boolean defaultValue = version.onOrAfter(IndexVersions.TIME_SERIES_USE_SYNTHETIC_ID_DEFAULT);
2646+
return settings.getAsBoolean(IndexSettings.SYNTHETIC_ID.getKey(), defaultValue);
2647+
}
2648+
return false;
2649+
}
2650+
26452651
@SuppressWarnings("unchecked")
26462652
public static void toXContent(IndexMetadata indexMetadata, XContentBuilder builder, ToXContent.Params params) throws IOException {
26472653
Metadata.XContentContext context = Metadata.XContentContext.valueOf(

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public Set<NodeFeature> getFeatures() {
2525
public static final NodeFeature LOGSDB_NO_HOST_NAME_FIELD = new NodeFeature("index.logsdb_no_host_name_field");
2626

2727
public static final NodeFeature TIME_SERIES_SYNTHETIC_ID = new NodeFeature("index.time_series_synthetic_id");
28+
public static final NodeFeature TIME_SERIES_SYNTHETIC_ID_DEFAULT = new NodeFeature("index.time_series_synthetic_id_default");
2829

2930
public static final NodeFeature TIME_SERIES_NO_SEQNO = new NodeFeature("index.time_series_no_seqno");
3031

@@ -51,6 +52,7 @@ public Set<NodeFeature> getTestFeatures() {
5152
return Set.of(
5253
LOGSDB_NO_HOST_NAME_FIELD,
5354
TIME_SERIES_SYNTHETIC_ID,
55+
TIME_SERIES_SYNTHETIC_ID_DEFAULT,
5456
TIME_SERIES_NO_SEQNO,
5557
SYNONYMS_SET_LENIENT_ON_NON_EXISTING,
5658
THROW_EXCEPTION_FOR_UNKNOWN_TOKEN_IN_REST_INDEX_PUT_ALIAS_ACTION,

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

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -681,8 +681,43 @@ public boolean isES87TSDBCodecEnabled() {
681681
Property.Final
682682
);
683683

684+
/**
685+
* The {@link IndexMode "mode"} of the index.
686+
*/
687+
public static final Setting<IndexMode> MODE = Setting.enumSetting(
688+
IndexMode.class,
689+
"index.mode",
690+
IndexMode.STANDARD,
691+
new Setting.Validator<>() {
692+
@Override
693+
public void validate(IndexMode value) {}
694+
695+
@Override
696+
public void validate(IndexMode value, Map<Setting<?>, Object> settings) {
697+
value.validateWithOtherSettings(settings);
698+
}
699+
700+
@Override
701+
public Iterator<Setting<?>> settings() {
702+
return IndexMode.VALIDATE_WITH_SETTINGS.iterator();
703+
}
704+
},
705+
Property.IndexScope,
706+
Property.Final,
707+
Property.ServerlessPublic
708+
);
709+
684710
public static final boolean TSDB_SYNTHETIC_ID_FEATURE_FLAG = new FeatureFlag("tsdb_synthetic_id").isEnabled();
685-
public static final Setting<Boolean> SYNTHETIC_ID = Setting.boolSetting("index.mapping.synthetic_id", false, new Setting.Validator<>() {
711+
public static final Setting<Boolean> SYNTHETIC_ID = Setting.boolSetting("index.mapping.synthetic_id", settings -> {
712+
IndexVersion indexVersion = SETTING_INDEX_VERSION_CREATED.get(settings);
713+
IndexMode indexMode = MODE.get(settings);
714+
String codec = INDEX_CODEC_SETTING.get(settings);
715+
boolean onByDefault = indexVersion.onOrAfter(IndexVersions.TIME_SERIES_USE_SYNTHETIC_ID_DEFAULT);
716+
return TSDB_SYNTHETIC_ID_FEATURE_FLAG
717+
&& IndexMode.TIME_SERIES.equals(indexMode)
718+
&& CodecService.DEFAULT_CODEC.equalsIgnoreCase(codec)
719+
&& onByDefault ? Boolean.TRUE.toString() : Boolean.FALSE.toString();
720+
}, new Setting.Validator<>() {
686721
@Override
687722
public void validate(Boolean enabled) {
688723
if (enabled) {
@@ -717,7 +752,7 @@ public void validate(Boolean enabled, Map<Setting<?>, Object> settings) {
717752
}
718753

719754
var codecName = (String) settings.get(INDEX_CODEC_SETTING);
720-
if (codecName.equals(CodecService.DEFAULT_CODEC) == false) {
755+
if (codecName.equalsIgnoreCase(CodecService.DEFAULT_CODEC) == false) {
721756
throw new IllegalArgumentException(
722757
String.format(
723758
Locale.ROOT,
@@ -757,32 +792,6 @@ public Iterator<Setting<?>> settings() {
757792
}
758793
}, Property.IndexScope, Property.Final);
759794

760-
/**
761-
* The {@link IndexMode "mode"} of the index.
762-
*/
763-
public static final Setting<IndexMode> MODE = Setting.enumSetting(
764-
IndexMode.class,
765-
"index.mode",
766-
IndexMode.STANDARD,
767-
new Setting.Validator<>() {
768-
@Override
769-
public void validate(IndexMode value) {}
770-
771-
@Override
772-
public void validate(IndexMode value, Map<Setting<?>, Object> settings) {
773-
value.validateWithOtherSettings(settings);
774-
}
775-
776-
@Override
777-
public Iterator<Setting<?>> settings() {
778-
return IndexMode.VALIDATE_WITH_SETTINGS.iterator();
779-
}
780-
},
781-
Property.IndexScope,
782-
Property.Final,
783-
Property.ServerlessPublic
784-
);
785-
786795
public static final Setting<Boolean> USE_DOC_VALUES_SKIPPER = Setting.boolSetting("index.mapping.use_doc_values_skipper", s -> {
787796
IndexVersion iv = SETTING_INDEX_VERSION_CREATED.get(s);
788797
if (MODE.get(s) == IndexMode.TIME_SERIES) {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ private static Version parseUnchecked(String version) {
237237
public static final IndexVersion UPGRADE_DISKBBQ_ES940 = def(9_076_00_0, Version.LUCENE_10_4_0);
238238
public static final IndexVersion FLATTENED_FIELD_NO_ROOT_DOC_VALUES = def(9_077_0_00, Version.LUCENE_10_4_0);
239239
public static final IndexVersion IGNORED_SOURCE_AS_DOC_VALUES = def(9_078_0_00, Version.LUCENE_10_4_0);
240+
public static final IndexVersion TIME_SERIES_USE_SYNTHETIC_ID_DEFAULT = def(9_079_0_00, Version.LUCENE_10_4_0);
240241

241242
/*
242243
* STOP! READ THIS FIRST! No, really,

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -978,6 +978,45 @@ public void testSyntheticIdCorrectSettings() {
978978
assertTrue(indexMetadata.useTimeSeriesSyntheticId());
979979
}
980980

981+
public void testSyntheticIdDefaultValueTrue() {
982+
assumeTrue("Test should only run with feature flag", IndexSettings.TSDB_SYNTHETIC_ID_FEATURE_FLAG);
983+
IndexVersion version = IndexVersionUtils.randomVersionBetween(
984+
IndexVersions.TIME_SERIES_USE_SYNTHETIC_ID_DEFAULT,
985+
IndexVersion.current()
986+
);
987+
IndexMode mode = IndexMode.TIME_SERIES;
988+
String codec = CodecService.DEFAULT_CODEC;
989+
990+
Settings settings = Settings.builder()
991+
.put(EngineConfig.INDEX_CODEC_SETTING.getKey(), codec)
992+
.put(IndexSettings.MODE.getKey(), mode)
993+
.put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "some-routing")
994+
.build();
995+
IndexMetadata indexMetadata = newIndexMeta("some-index", settings, version);
996+
997+
IndexSettings indexSettings = new IndexSettings(indexMetadata, Settings.EMPTY);
998+
assertTrue(indexSettings.useTimeSeriesSyntheticId());
999+
assertTrue(indexMetadata.useTimeSeriesSyntheticId());
1000+
}
1001+
1002+
public void testSyntheticIdDefaultValueFalse() {
1003+
assumeTrue("Test should only run with feature flag", IndexSettings.TSDB_SYNTHETIC_ID_FEATURE_FLAG);
1004+
IndexVersion version = IndexVersionUtils.getPreviousVersion(IndexVersions.TIME_SERIES_USE_SYNTHETIC_ID_DEFAULT);
1005+
IndexMode mode = IndexMode.TIME_SERIES;
1006+
String codec = CodecService.DEFAULT_CODEC;
1007+
1008+
Settings settings = Settings.builder()
1009+
.put(EngineConfig.INDEX_CODEC_SETTING.getKey(), codec)
1010+
.put(IndexSettings.MODE.getKey(), mode)
1011+
.put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "some-routing")
1012+
.build();
1013+
IndexMetadata indexMetadata = newIndexMeta("some-index", settings, version);
1014+
1015+
IndexSettings indexSettings = new IndexSettings(indexMetadata, Settings.EMPTY);
1016+
assertFalse(indexSettings.useTimeSeriesSyntheticId());
1017+
assertFalse(indexMetadata.useTimeSeriesSyntheticId());
1018+
}
1019+
9811020
public void testSyntheticIdBadVersion() {
9821021
assumeTrue("Test should only run with feature flag", IndexSettings.TSDB_SYNTHETIC_ID_FEATURE_FLAG);
9831022
IndexVersion badVersion = IndexVersionUtils.getPreviousVersion(IndexVersions.TIME_SERIES_USE_SYNTHETIC_ID_94);

0 commit comments

Comments
 (0)