Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
5 changes: 5 additions & 0 deletions docs/changelog/137096.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 137096
summary: Fix mapping conflicts in clone/split/shrink APIs
area: Indices APIs
type: bug
issues: []
Comment on lines +2 to +5
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should we make changelog more elaborate/descriptive? cc @mattc58

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, and probably needs to be a release highlight since this might warrant a special release. But that can wait until we determine the impact and precise triggering conditions of this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alright, then I'm merging this as-is. We can come back and update the changelog later if we want to.

Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.elasticsearch.index.seqno.SeqNoStats;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.index.IndexVersionUtils;
import org.elasticsearch.xcontent.ObjectPath;
import org.elasticsearch.xcontent.XContentType;

import java.util.List;
Expand Down Expand Up @@ -203,4 +204,49 @@ public void testResizeChangeIndexSorts() {
});
assertThat(error.getMessage(), containsString("can't override index sort when resizing an index"));
}

/**
* Test that cloning a logsdb index with a non-default timestamp mapping doesn't result in any mapping conflicts.
*/
public void testCloneLogsdbIndexWithNonDefaultTimestamp() {
// Create a logsdb index with a date_nanos @timestamp field
final var settings = indexSettings(1, randomInt(internalCluster().numDataNodes() - 1)).put("index.mode", "logsdb")
.put("index.blocks.write", true);
prepareCreate("source").setSettings(settings).setMapping("@timestamp", "type=date_nanos").get();
ensureGreen();

// Clone the index
indicesAdmin().prepareResizeIndex("source", "target").setResizeType(ResizeType.CLONE).get();

// Verify that the target index has the correct @timestamp mapping
final var targetMappings = indicesAdmin().prepareGetMappings(TEST_REQUEST_TIMEOUT, "target").get();
assertThat(
ObjectPath.eval("[email protected]", targetMappings.mappings().get("target").getSourceAsMap()),
equalTo("date_nanos")
);
}

/**
* Test that cloning a time series index with a non-default timestamp mapping doesn't result in any mapping conflicts.
*/
public void testCloneTimeSeriesIndexWithNonDefaultTimestamp() {
// Create a time series index with a date_nanos @timestamp field
final var settings = indexSettings(1, randomInt(internalCluster().numDataNodes() - 1)).put("index.mode", "time_series")
.put("index.routing_path", "sensor_id")
.put("index.blocks.write", true);
prepareCreate("source").setSettings(settings)
.setMapping("@timestamp", "type=date_nanos", "sensor_id", "type=keyword,time_series_dimension=true")
.get();
ensureGreen();

// Clone the index
indicesAdmin().prepareResizeIndex("source", "target").setResizeType(ResizeType.CLONE).get();

// Verify that the target index has the correct @timestamp mapping
final var targetMappings = indicesAdmin().prepareGetMappings(TEST_REQUEST_TIMEOUT, "target").get();
assertThat(
ObjectPath.eval("[email protected]", targetMappings.mappings().get("target").getSourceAsMap()),
equalTo("date_nanos")
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import org.elasticsearch.indices.IndicesService;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.index.IndexVersionUtils;
import org.elasticsearch.xcontent.ObjectPath;
import org.elasticsearch.xcontent.XContentType;

import java.util.Arrays;
Expand Down Expand Up @@ -614,6 +615,59 @@ public void testShrinkThenSplitWithFailedNode() throws Exception {
assertNoResizeSourceIndexSettings("splitagain");
}

/**
* Tests that shrinking a logsdb index with a non-default timestamp mapping doesn't result in any mapping conflicts.
*/
public void testShrinkLogsdbIndexWithNonDefaultTimestamp() {
// Create a logsdb index with a date_nanos @timestamp field
final var settings = indexSettings(2, 0).put("index.mode", "logsdb")
.put("index.blocks.write", true)
.put("index.routing.allocation.require._name", internalCluster().getRandomDataNodeName());
prepareCreate("source").setSettings(settings).setMapping("@timestamp", "type=date_nanos").get();
ensureGreen();

// Shrink the index
indicesAdmin().prepareResizeIndex("source", "target")
.setResizeType(ResizeType.SHRINK)
.setSettings(Settings.builder().put("index.number_of_shards", 1).build())
.get();

// Verify that the target index has the correct @timestamp mapping
final var targetMappings = indicesAdmin().prepareGetMappings(TEST_REQUEST_TIMEOUT, "target").get();
assertThat(
ObjectPath.eval("[email protected]", targetMappings.mappings().get("target").getSourceAsMap()),
equalTo("date_nanos")
);
}

/**
* Tests that shrinking a time series index with a non-default timestamp mapping doesn't result in any mapping conflicts.
*/
public void testShrinkTimeSeriesIndexWithNonDefaultTimestamp() {
// Create a time series index with a date_nanos @timestamp field
final var settings = indexSettings(2, 0).put("index.mode", "time_series")
.put("index.routing_path", "sensor_id")
.put("index.routing.allocation.require._name", internalCluster().getRandomDataNodeName())
.put("index.blocks.write", true);
prepareCreate("source").setSettings(settings)
.setMapping("@timestamp", "type=date_nanos", "sensor_id", "type=keyword,time_series_dimension=true")
.get();
ensureGreen();

// Shrink the index
indicesAdmin().prepareResizeIndex("source", "target")
.setResizeType(ResizeType.SHRINK)
.setSettings(Settings.builder().put("index.number_of_shards", 1).build())
.get();

// Verify that the target index has the correct @timestamp mapping
final var targetMappings = indicesAdmin().prepareGetMappings(TEST_REQUEST_TIMEOUT, "target").get();
assertThat(
ObjectPath.eval("[email protected]", targetMappings.mappings().get("target").getSourceAsMap()),
equalTo("date_nanos")
);
}

static void assertNoResizeSourceIndexSettings(final String index) {
ClusterStateResponse clusterStateResponse = clusterAdmin().prepareState(TEST_REQUEST_TIMEOUT)
.clear()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.elasticsearch.indices.IndicesService;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.index.IndexVersionUtils;
import org.elasticsearch.xcontent.ObjectPath;
import org.elasticsearch.xcontent.XContentType;

import java.io.IOException;
Expand Down Expand Up @@ -496,4 +497,29 @@ public void testCreateSplitWithIndexSort() throws Exception {
assertSortedSegments("target", expectedIndexSort);
assertNoResizeSourceIndexSettings("target");
}

/**
* Tests that splitting a logsdb index with a non-default timestamp mapping doesn't result in any mapping conflicts.
* N.B.: we don't test time_series indices as split is not supported for them.
*/
public void testSplitLogsdbIndexWithNonDefaultTimestamp() {
// Create a logsdb index with a date_nanos @timestamp field
final var settings = indexSettings(1, randomInt(internalCluster().numDataNodes() - 1)).put("index.mode", "logsdb")
.put("index.blocks.write", true);
prepareCreate("source").setSettings(settings).setMapping("@timestamp", "type=date_nanos").get();
ensureGreen();

// Split the index
indicesAdmin().prepareResizeIndex("source", "target")
.setResizeType(ResizeType.SPLIT)
.setSettings(Settings.builder().put("index.number_of_shards", 2).build())
.get();

// Verify that the target index has the correct @timestamp mapping
final var targetMappings = indicesAdmin().prepareGetMappings(TEST_REQUEST_TIMEOUT, "target").get();
assertThat(
ObjectPath.eval("[email protected]", targetMappings.mappings().get("target").getSourceAsMap()),
equalTo("date_nanos")
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -543,11 +543,15 @@ private ClusterState applyCreateIndexWithTemporaryService(
assert indicesService.hasIndex(temporaryIndexMeta.getIndex()) == false
: Strings.format("Index [%s] already exists", temporaryIndexMeta.getIndex().getName());
return indicesService.<ClusterState, Exception>withTempIndexService(temporaryIndexMeta, indexService -> {
try {
updateIndexMappingsAndBuildSortOrder(indexService, request, mappings, sourceMetadata);
} catch (Exception e) {
logger.log(silent ? Level.DEBUG : Level.INFO, "failed on parsing mappings on index creation [{}]", request.index(), e);
throw e;
// If we're creating the index from an existing index, we should not provide any mappings, as the new index shards will take
// care of copying the mappings from the source index during recovery. Providing mappings here would cause conflicts.
if (sourceMetadata == null) {
try {
updateIndexMappingsAndBuildSortOrder(indexService, request, mappings);
} catch (Exception e) {
logger.log(silent ? Level.DEBUG : Level.INFO, "failed on parsing mappings on index creation [{}]", request.index(), e);
throw e;
}
}

final List<AliasMetadata> aliases = aliasSupplier.apply(indexService);
Expand Down Expand Up @@ -1550,8 +1554,7 @@ private static IndexMetadata.Builder createIndexMetadataBuilder(
private static void updateIndexMappingsAndBuildSortOrder(
IndexService indexService,
CreateIndexClusterStateUpdateRequest request,
List<CompressedXContent> mappings,
@Nullable IndexMetadata sourceMetadata
List<CompressedXContent> mappings
) throws IOException {
MapperService mapperService = indexService.mapperService();
IndexMode indexMode = indexService.getIndexSettings() != null ? indexService.getIndexSettings().getMode() : IndexMode.STANDARD;
Expand All @@ -1565,13 +1568,11 @@ private static void updateIndexMappingsAndBuildSortOrder(

indexMode.validateTimestampFieldMapping(request.dataStreamName() != null, mapperService.mappingLookup());

if (sourceMetadata == null) {
// now that the mapping is merged we can validate the index sort.
// we cannot validate for index shrinking since the mapping is empty
// at this point. The validation will take place later in the process
// (when all shards are copied in a single place).
indexService.getIndexSortSupplier().get();
}
// now that the mapping is merged we can validate the index sort.
// we cannot validate for index shrinking since the mapping is empty
// at this point. The validation will take place later in the process
// (when all shards are copied in a single place).
indexService.getIndexSortSupplier().get();
}

private static void validateActiveShardCount(ActiveShardCount waitForActiveShards, IndexMetadata indexMetadata) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2114,6 +2114,13 @@ public String getRandomNodeName() {
return getNodeNameThat(Predicates.always());
}

/**
* @return the name of a random data node in a cluster
*/
public String getRandomDataNodeName() {
return getNodeNameThat(DiscoveryNode::canContainData);
}

/**
* @return the name of a random node in a cluster that match the {@code predicate}
*/
Expand Down