Skip to content
5 changes: 5 additions & 0 deletions docs/changelog/141525.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
area: TSDB
issues: []
pr: 141525
summary: Rolling upgrade test for synthetic id
type: enhancement
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public void testTimeRanges() throws Exception {
templateSettings.put("index.routing_path", "metricset");
}
if (IndexSettings.TSDB_SYNTHETIC_ID_FEATURE_FLAG && randomBoolean()) {
templateSettings.put(IndexSettings.USE_SYNTHETIC_ID.getKey(), true);
templateSettings.put(IndexSettings.SYNTHETIC_ID.getKey(), true);
}
var mapping = new CompressedXContent(randomBoolean() ? MAPPING_TEMPLATE : MAPPING_TEMPLATE.replace("date", "date_nanos"));

Expand Down Expand Up @@ -335,7 +335,7 @@ public void testTsdbTemplatesNoKeywordFieldType() throws Exception {
settingsBuilder.put("index.routing_path", "metricset");
}
if (IndexSettings.TSDB_SYNTHETIC_ID_FEATURE_FLAG && randomBoolean()) {
settingsBuilder.put(IndexSettings.USE_SYNTHETIC_ID.getKey(), true);
settingsBuilder.put(IndexSettings.SYNTHETIC_ID.getKey(), true);
}
request.indexTemplate(
ComposableIndexTemplate.builder()
Expand Down Expand Up @@ -386,7 +386,7 @@ public void testSkippingShards() throws Exception {
{
var templateSettings = Settings.builder().put("index.mode", "time_series").put("index.routing_path", "metricset");
if (IndexSettings.TSDB_SYNTHETIC_ID_FEATURE_FLAG && randomBoolean()) {
templateSettings.put(IndexSettings.USE_SYNTHETIC_ID.getKey(), true);
templateSettings.put(IndexSettings.SYNTHETIC_ID.getKey(), true);
}
var request = new TransportPutComposableIndexTemplateAction.Request("id1");
request.indexTemplate(
Expand Down Expand Up @@ -590,7 +590,7 @@ public void testReindexing() throws Exception {
String reindexedDataStreamName = "my-reindexed-ds";
var templateSettings = Settings.builder().put("index.mode", "time_series");
if (IndexSettings.TSDB_SYNTHETIC_ID_FEATURE_FLAG && randomBoolean()) {
templateSettings.put(IndexSettings.USE_SYNTHETIC_ID.getKey(), true);
templateSettings.put(IndexSettings.SYNTHETIC_ID.getKey(), true);
}
var putTemplateRequest = new TransportPutComposableIndexTemplateAction.Request("id");
putTemplateRequest.indexTemplate(
Expand Down Expand Up @@ -649,7 +649,7 @@ public void testAddDimensionToMapping() throws Exception {
.put("index.mode", "time_series")
.put("index.dimensions_tsid_strategy_enabled", indexDimensionsTsidStrategyEnabled);
if (IndexSettings.TSDB_SYNTHETIC_ID_FEATURE_FLAG && randomBoolean()) {
templateSettings.put(IndexSettings.USE_SYNTHETIC_ID.getKey(), true);
templateSettings.put(IndexSettings.SYNTHETIC_ID.getKey(), true);
}
putTemplateRequest.indexTemplate(
ComposableIndexTemplate.builder()
Expand Down Expand Up @@ -733,7 +733,7 @@ public void testDynamicStringDimensions() throws Exception {
String dataStreamName = "my-ds";
var templateSettings = Settings.builder().put("index.mode", "time_series");
if (IndexSettings.TSDB_SYNTHETIC_ID_FEATURE_FLAG && randomBoolean()) {
templateSettings.put(IndexSettings.USE_SYNTHETIC_ID.getKey(), true);
templateSettings.put(IndexSettings.SYNTHETIC_ID.getKey(), true);
}
var putTemplateRequest = new TransportPutComposableIndexTemplateAction.Request("id");
putTemplateRequest.indexTemplate(
Expand Down Expand Up @@ -799,7 +799,7 @@ public void testDynamicDimensions() throws Exception {
String dataStreamName = "my-ds";
var templateSettings = Settings.builder().put("index.mode", "time_series");
if (IndexSettings.TSDB_SYNTHETIC_ID_FEATURE_FLAG && randomBoolean()) {
templateSettings.put(IndexSettings.USE_SYNTHETIC_ID.getKey(), true);
templateSettings.put(IndexSettings.SYNTHETIC_ID.getKey(), true);
}
var putTemplateRequest = new TransportPutComposableIndexTemplateAction.Request("id");
putTemplateRequest.indexTemplate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ protected Settings nodeSettings() {
public void testIndexingGettingAndSearching() throws Exception {
var templateSettings = indexSettings(randomIntBetween(2, 10), 0).put("index.mode", "time_series");
if (IndexSettings.TSDB_SYNTHETIC_ID_FEATURE_FLAG && randomBoolean()) {
templateSettings.put(IndexSettings.USE_SYNTHETIC_ID.getKey(), true);
templateSettings.put(IndexSettings.SYNTHETIC_ID.getKey(), true);
}

var request = new TransportPutComposableIndexTemplateAction.Request("id");
Expand Down Expand Up @@ -226,7 +226,7 @@ public void testIndexingGettingAndSearchingShrunkIndex() throws Exception {
String dataStreamName = "k8s";
var templateSettings = indexSettings(8, 0).put("index.mode", "time_series");
if (IndexSettings.TSDB_SYNTHETIC_ID_FEATURE_FLAG && randomBoolean()) {
templateSettings.put(IndexSettings.USE_SYNTHETIC_ID.getKey(), true);
templateSettings.put(IndexSettings.SYNTHETIC_ID.getKey(), true);
}

var request = new TransportPutComposableIndexTemplateAction.Request("id");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,15 @@ public void testInvalidIndexMode() {
() -> createIndex(
indexName,
indexSettings(1, 0).put(IndexSettings.MODE.getKey(), randomNonTsdbIndexMode)
.put(IndexSettings.USE_SYNTHETIC_ID.getKey(), true)
.put(IndexSettings.SYNTHETIC_ID.getKey(), true)
.build()
)
);
assertThat(
exception.getMessage(),
containsString(
"The setting ["
+ IndexSettings.USE_SYNTHETIC_ID.getKey()
+ IndexSettings.SYNTHETIC_ID.getKey()
+ "] is only permitted when [index.mode] is set to [TIME_SERIES]. Current mode: ["
+ randomNonTsdbIndexMode.getName().toUpperCase(Locale.ROOT)
+ "]."
Expand All @@ -165,7 +165,7 @@ public void testInvalidCodec() {
indexName,
indexSettings(1, 0).put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES)
.put("index.routing_path", "hostname")
.put(IndexSettings.USE_SYNTHETIC_ID.getKey(), true)
.put(IndexSettings.SYNTHETIC_ID.getKey(), true)
.put(EngineConfig.INDEX_CODEC_SETTING.getKey(), randomNonDefaultCodec)
.build()
)
Expand All @@ -174,7 +174,7 @@ public void testInvalidCodec() {
exception.getMessage(),
containsString(
"The setting ["
+ IndexSettings.USE_SYNTHETIC_ID.getKey()
+ IndexSettings.SYNTHETIC_ID.getKey()
+ "] is only permitted when [index.codec] is set to [default]. Current mode: ["
+ randomNonDefaultCodec
+ "]."
Expand Down Expand Up @@ -1211,7 +1211,7 @@ private static void putDataStreamTemplate(String indexPattern, int primaries, in
private static void putDataStreamTemplate(String indexPattern, int primaries, int replicas, Settings extraSettings) throws IOException {
final var settings = indexSettings(primaries, replicas).put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES.getName())
.put(IndexSettings.INDEX_REFRESH_INTERVAL_SETTING.getKey(), -1)
.put(IndexSettings.USE_SYNTHETIC_ID.getKey(), true);
.put(IndexSettings.SYNTHETIC_ID.getKey(), true);
if (randomBoolean()) {
settings.put(IndexSettings.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), SourceFieldMapper.Mode.SYNTHETIC);
settings.put(IndexSettings.RECOVERY_USE_SYNTHETIC_SOURCE_SETTING.getKey(), randomBoolean());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import java.util.Set;

import static org.elasticsearch.cluster.metadata.DataStreamTestHelper.backingIndexEqualTo;
import static org.elasticsearch.index.IndexSettings.USE_SYNTHETIC_ID;
import static org.elasticsearch.index.IndexSettings.SYNTHETIC_ID;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
Expand Down Expand Up @@ -225,17 +225,17 @@ private static String getTemplate() {
idMode = switch (randomInt(2)) {
case 0 -> null;
case 1 -> """
"use_synthetic_id": "false"
"synthetic_id": "false"
""";
case 2 -> """
"use_synthetic_id": "true"
"synthetic_id": "true"
""";
default -> throw new AssertionError("Unknown mode");
};
} else {
assertFalse(
"Setting is enabled by default and must now be tested on non-snapshot build too ",
USE_SYNTHETIC_ID.getDefault(Settings.EMPTY)
SYNTHETIC_ID.getDefault(Settings.EMPTY)
);
}
if (sourceMode == null && idMode == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
* 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.upgrades;

import com.carrotsearch.randomizedtesting.annotations.Name;

import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.ResponseException;
import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.IndexMode;
import org.elasticsearch.index.IndexSettings;
import org.elasticsearch.index.IndexVersion;
import org.elasticsearch.index.IndexVersions;
import org.elasticsearch.test.rest.ObjectPath;
import org.hamcrest.Matchers;

import java.io.IOException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Locale;
import java.util.Map;
import java.util.StringJoiner;

public class TSDBSyntheticIdUpgradeIT extends AbstractRollingUpgradeTestCase {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we test against the live commits in the serverless environments for test suites in this module. I think we should have that additional test coverage. Many logsdb en tsdb tests are now in x-pack/plugin/logsdb/qa/rolling-upgrade module. This test against stateless versions like the current module , but this module is also mirrored in serverless project.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I would like to address this in a later PR because it will probably take quite a bit of time for me to dig into this (so much to learn), if you are ok with that? Do you have any further pointers to where I can read more about how we test against live commits in serverless?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is good with me. I think it is a matter of moving this test suite to the x-pack/plugin/logsdb/qa/rolling-upgrade module and adjusting how the tests upgrades nodes. See other test suites in that module.

private static final int DOC_COUNT = 10;

public TSDBSyntheticIdUpgradeIT(@Name("upgradedNodes") int upgradedNodes) {
super(upgradedNodes);
}

public void testRollingUpgrade() throws IOException {
IndexVersion oldClusterIndexVersion = getOldClusterIndexVersion();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we need to guard this test with assumeTrue("Test should only run with feature flag", IndexSettings.TSDB_SYNTHETIC_ID_FEATURE_FLAG);?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The feature flag(s) that control if the setting can be used or not is in the nodes different processes and might not align with what we see in the test-process. So I don't think we should check it here.


if (hasSupportForSyntheticId(oldClusterIndexVersion)) {
if (isOldCluster()) {
assertWriteIndex("old-cluster-index");
assertIndexRead("old-cluster-index");
}

if (isFirstMixedCluster()) {
assertWriteIndex("first-mixed-cluster-index");
assertIndexRead("old-cluster-index");
assertIndexRead("first-mixed-cluster-index");
}

if (isFirstMixedCluster() == false && isMixedCluster()) {
assertWriteIndex("second-mixed-cluster-index");
assertIndexRead("old-cluster-index");
assertIndexRead("first-mixed-cluster-index");
assertIndexRead("second-mixed-cluster-index");
}

if (isUpgradedCluster()) {
assertWriteIndex("upgraded-cluster-index");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we check that once we've upgraded the cluster the disk usage for the inverted index is 0?

assertIndexRead("old-cluster-index");
assertIndexRead("first-mixed-cluster-index");
assertIndexRead("second-mixed-cluster-index");
assertIndexRead("upgraded-cluster-index");
}
} else {

if (isOldCluster()) {
assertNoWriteIndex("old-cluster-index");
}

if (isMixedCluster()) {
assertNoWriteIndex("mixed-cluster-index");
}

if (isUpgradedCluster()) {
assertWriteIndex("upgraded-cluster-index");
assertIndexRead("upgraded-cluster-index");
}
}
}

private static void assertWriteIndex(String indexName) throws IOException {
assertIndexCanBeCreated(indexName);
assertCanAddDocuments(indexName);
}

private static void assertIndexRead(String indexName) throws IOException {
assertTrue("Expected index [" + indexName + "] to exist, but did not", indexExists(indexName));
Map<String, Object> indexSettingsAsMap = getIndexSettingsAsMap(indexName);
assertThat(indexSettingsAsMap.get(IndexSettings.SYNTHETIC_ID.getKey()), Matchers.equalTo("true"));
assertDocCount(client(), indexName, DOC_COUNT);
assertThat(invertedIndexSize(indexName), Matchers.equalTo(0));
}

private static int invertedIndexSize(String indexName) throws IOException {
var diskUsage = new Request("POST", "/" + indexName + "/_disk_usage?run_expensive_tasks=true");
Response response = client().performRequest(diskUsage);
ObjectPath objectPath = ObjectPath.createFromResponse(response);
return objectPath.evaluate(indexName + ".all_fields.inverted_index.total_in_bytes");
}

private static void assertIndexCanBeCreated(String indexName) throws IOException {
CreateIndexResponse response = null;
try {
response = createSyntheticIdIndex(indexName);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we assert that the index settings include the synthetic id setting?

assertTrue("Expected index [" + indexName + "] to be created successfully, but was not", response.isAcknowledged());
assertTrue(
"Expected shards of index [" + indexName + "] to be created successfully, but was not",
response.isShardsAcknowledged()
);
} finally {
if (response != null) {
response.decRef();
}
}
}

private static void assertCanAddDocuments(String indexName) throws IOException {
StringJoiner joiner = new StringJoiner("\n", "", "\n");
Instant now = Instant.now();
for (int i = 0; i < DOC_COUNT; i++) {
addDocument(joiner, now.plus(i, ChronoUnit.SECONDS));
}
var request = new Request("PUT", "/" + indexName + "/_bulk");
request.setJsonEntity(joiner.toString());
request.addParameter("refresh", "true");
Response response = client().performRequest(request);
assertOK(response);
}

private static void addDocument(StringJoiner joiner, Instant timestamp) {
joiner.add("{\"create\": {}}");
joiner.add(String.format(Locale.ROOT, """
{"@timestamp": "%s", "hostname": "host", "metric": {"field": "cpu-load", "value": %d}}
""", timestamp, randomByte()));
}

private static void assertNoWriteIndex(String indexName) {
String setting = IndexSettings.SYNTHETIC_ID.getKey();
String unknownSetting = "unknown setting [" + setting + "]";
String versionTooLow = "The setting [" + setting + "] is set to [true] but index metadata has a different value [false]";

ResponseException e = assertThrows(ResponseException.class, () -> createSyntheticIdIndex(indexName));
assertThat(e.getMessage(), Matchers.either(Matchers.containsString(unknownSetting)).or(Matchers.containsString(versionTooLow)));
assertThat(e.getMessage(), Matchers.containsString("illegal_argument_exception"));
}

private static CreateIndexResponse createSyntheticIdIndex(String indexName) throws IOException {
Settings settings = Settings.builder()
.put(IndexSettings.SYNTHETIC_ID.getKey(), true)
.put(IndexSettings.MODE.getKey(), IndexMode.TIME_SERIES)
.put(IndexMetadata.INDEX_ROUTING_PATH.getKey(), "hostname")
.build();
final var mapping = """
{
"properties": {
"@timestamp": {
"type": "date"
},
"hostname": {
"type": "keyword",
"time_series_dimension": true
},
"metric": {
"properties": {
"field": {
"type": "keyword",
"time_series_dimension": true
},
"value": {
"type": "integer",
"time_series_metric": "counter"
}
}
}
}
}
""";
return createIndex(indexName, settings, mapping);
}

private boolean hasSupportForSyntheticId(IndexVersion indexVersion) {
return indexVersion.onOrAfter(IndexVersions.TIME_SERIES_USE_SYNTHETIC_ID_94);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2537,8 +2537,8 @@ IndexMetadata build(boolean repair) {
boolean useTimeSeriesSyntheticId = false;
if (isTsdb
&& IndexSettings.TSDB_SYNTHETIC_ID_FEATURE_FLAG
&& indexCreatedVersion.onOrAfter(IndexVersions.TIME_SERIES_USE_SYNTHETIC_ID)) {
var setting = settings.get(IndexSettings.USE_SYNTHETIC_ID.getKey());
&& indexCreatedVersion.onOrAfter(IndexVersions.TIME_SERIES_USE_SYNTHETIC_ID_94)) {
var setting = settings.get(IndexSettings.SYNTHETIC_ID.getKey());
if (setting != null && setting.equalsIgnoreCase(Boolean.TRUE.toString())) {
assert IndexSettings.TSDB_SYNTHETIC_ID_FEATURE_FLAG;
useTimeSeriesSyntheticId = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ public final class IndexScopedSettings extends AbstractScopedSettings {
);

if (IndexSettings.TSDB_SYNTHETIC_ID_FEATURE_FLAG) {
settings.add(IndexSettings.USE_SYNTHETIC_ID);
settings.add(IndexSettings.SYNTHETIC_ID);
}
settings.add(IndexSettings.INDEX_MAPPING_EXCLUDE_SOURCE_VECTORS_SETTING);
BUILT_IN_INDEX_SETTINGS = Collections.unmodifiableSet(settings);
Expand Down
Loading