Skip to content

Commit 7a6f463

Browse files
committed
Remove per-index metadata without assigned shards
Today on master-eligible nodes we maintain per-index metadata files for every index. However, we also keep this metadata in the `LucenePersistedState`, and only use the per-index metadata files for importing dangling indices. However there is no point in importing a dangling index without any shard data, so we do not need to maintain these extra files any more. This commit removes per-index metadata files from nodes which do not hold any shards of those indices. Relates elastic#48701
1 parent c3a49dc commit 7a6f463

File tree

5 files changed

+107
-73
lines changed

5 files changed

+107
-73
lines changed

server/src/main/java/org/elasticsearch/gateway/IncrementalClusterStateWriter.java

+7-32
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import org.elasticsearch.index.Index;
3434

3535
import java.util.ArrayList;
36-
import java.util.Collections;
3736
import java.util.HashMap;
3837
import java.util.HashSet;
3938
import java.util.List;
@@ -53,9 +52,7 @@ public class IncrementalClusterStateWriter {
5352

5453
private final MetaStateService metaStateService;
5554

56-
// On master-eligible nodes we call updateClusterState under the Coordinator's mutex; on master-ineligible data nodes we call
57-
// updateClusterState on the (unique) cluster applier thread; on other nodes we never call updateClusterState. In all cases there's
58-
// no need to synchronize access to these fields.
55+
// We call updateClusterState on the (unique) cluster applier thread so there's no need to synchronize access to these fields.
5956
private Manifest previousManifest;
6057
private ClusterState previousClusterState;
6158
private final LongSupplier relativeTimeMillisSupplier;
@@ -89,10 +86,6 @@ Manifest getPreviousManifest() {
8986
return previousManifest;
9087
}
9188

92-
ClusterState getPreviousClusterState() {
93-
return previousClusterState;
94-
}
95-
9689
void setIncrementalWrite(boolean incrementalWrite) {
9790
this.incrementalWrite = incrementalWrite;
9891
}
@@ -206,38 +199,20 @@ static List<IndexMetaDataAction> resolveIndexMetaDataActions(Map<Index, Long> pr
206199
return actions;
207200
}
208201

209-
private static Set<Index> getRelevantIndicesOnDataOnlyNode(ClusterState state) {
210-
RoutingNode newRoutingNode = state.getRoutingNodes().node(state.nodes().getLocalNodeId());
202+
// exposed for tests
203+
static Set<Index> getRelevantIndices(ClusterState state) {
204+
assert state.nodes().getLocalNode().isDataNode();
205+
final RoutingNode newRoutingNode = state.getRoutingNodes().node(state.nodes().getLocalNodeId());
211206
if (newRoutingNode == null) {
212207
throw new IllegalStateException("cluster state does not contain this node - cannot write index meta state");
213208
}
214-
Set<Index> indices = new HashSet<>();
215-
for (ShardRouting routing : newRoutingNode) {
209+
final Set<Index> indices = new HashSet<>();
210+
for (final ShardRouting routing : newRoutingNode) {
216211
indices.add(routing.index());
217212
}
218213
return indices;
219214
}
220215

221-
private static Set<Index> getRelevantIndicesForMasterEligibleNode(ClusterState state) {
222-
Set<Index> relevantIndices = new HashSet<>();
223-
// we have to iterate over the metadata to make sure we also capture closed indices
224-
for (IndexMetaData indexMetaData : state.metaData()) {
225-
relevantIndices.add(indexMetaData.getIndex());
226-
}
227-
return relevantIndices;
228-
}
229-
230-
// exposed for tests
231-
static Set<Index> getRelevantIndices(ClusterState state) {
232-
if (state.nodes().getLocalNode().isMasterNode()) {
233-
return getRelevantIndicesForMasterEligibleNode(state);
234-
} else if (state.nodes().getLocalNode().isDataNode()) {
235-
return getRelevantIndicesOnDataOnlyNode(state);
236-
} else {
237-
return Collections.emptySet();
238-
}
239-
}
240-
241216
/**
242217
* Action to perform with index metadata.
243218
*/

server/src/main/java/org/elasticsearch/indices/IndicesService.java

+4-15
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,7 @@ public void deleteUnassignedIndex(String reason, IndexMetaData metaData, Cluster
753753
throw new IllegalStateException("Can't delete unassigned index store for [" + indexName + "] - it's still part of " +
754754
"the cluster state [" + index.getIndexUUID() + "] [" + metaData.getIndexUUID() + "]");
755755
}
756-
deleteIndexStore(reason, metaData, clusterState);
756+
deleteIndexStore(reason, metaData);
757757
} catch (Exception e) {
758758
logger.warn(() -> new ParameterizedMessage("[{}] failed to delete unassigned index (reason [{}])",
759759
metaData.getIndex(), reason), e);
@@ -767,7 +767,7 @@ public void deleteUnassignedIndex(String reason, IndexMetaData metaData, Cluster
767767
*
768768
* Package private for testing
769769
*/
770-
void deleteIndexStore(String reason, IndexMetaData metaData, ClusterState clusterState) throws IOException {
770+
void deleteIndexStore(String reason, IndexMetaData metaData) throws IOException {
771771
if (nodeEnv.hasNodeFile()) {
772772
synchronized (this) {
773773
Index index = metaData.getIndex();
@@ -776,15 +776,6 @@ void deleteIndexStore(String reason, IndexMetaData metaData, ClusterState cluste
776776
throw new IllegalStateException("Can't delete index store for [" + index.getName() +
777777
"] - it's still part of the indices service [" + localUUid + "] [" + metaData.getIndexUUID() + "]");
778778
}
779-
780-
if (clusterState.metaData().hasIndex(index.getName()) && (clusterState.nodes().getLocalNode().isMasterNode() == true)) {
781-
// we do not delete the store if it is a master eligible node and the index is still in the cluster state
782-
// because we want to keep the meta data for indices around even if no shards are left here
783-
final IndexMetaData idxMeta = clusterState.metaData().index(index.getName());
784-
throw new IllegalStateException("Can't delete index store for [" + index.getName() + "] - it's still part of the " +
785-
"cluster state [" + idxMeta.getIndexUUID() + "] [" + metaData.getIndexUUID() + "], " +
786-
"we are master eligible, so will keep the index metadata even if no shards are left.");
787-
}
788779
}
789780
final IndexSettings indexSettings = buildIndexSettings(metaData);
790781
deleteIndexStore(reason, indexSettings.getIndex(), indexSettings);
@@ -862,13 +853,11 @@ public void deleteShardStore(String reason, ShardId shardId, ClusterState cluste
862853
nodeEnv.deleteShardDirectorySafe(shardId, indexSettings);
863854
logger.debug("{} deleted shard reason [{}]", shardId, reason);
864855

865-
// master nodes keep the index meta data, even if having no shards..
866-
if (clusterState.nodes().getLocalNode().isMasterNode() == false &&
867-
canDeleteIndexContents(shardId.getIndex(), indexSettings)) {
856+
if (canDeleteIndexContents(shardId.getIndex(), indexSettings)) {
868857
if (nodeEnv.findAllShardIds(shardId.getIndex()).isEmpty()) {
869858
try {
870859
// note that deleteIndexStore have more safety checks and may throw an exception if index was concurrently created.
871-
deleteIndexStore("no longer used", metaData, clusterState);
860+
deleteIndexStore("no longer used", metaData);
872861
} catch (Exception e) {
873862
// wrap the exception to indicate we already deleted the shard
874863
throw new ElasticsearchException("failed to delete unused index after deleting its last shard (" + shardId + ")", e);

server/src/test/java/org/elasticsearch/gateway/GatewayIndexStateIT.java

+86-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import org.elasticsearch.test.InternalTestCluster.RestartCallback;
5757

5858
import java.io.IOException;
59+
import java.nio.file.Files;
5960
import java.nio.file.Path;
6061
import java.util.List;
6162
import java.util.Map;
@@ -533,7 +534,7 @@ public void testHalfDeletedIndexImport() throws Exception {
533534
// // term in the coordination metadata
534535
// .coordinationMetaData(CoordinationMetaData.builder(metaData.coordinationMetaData()).term(0L).build())
535536
// // add a tombstone but do not delete the index metadata from disk
536-
// .putCustom(IndexGraveyard.TYPE, IndexGraveyard.builder().addTombstone(metaData.index("test").getIndex()).build()).build());
537+
// .putCustom(IndexGraveyard.TYPE, IndexGraveyard.builder().addTombstone(metaData.index("test").getIndex()).build()).build());
537538
// for (final Path path : paths) {
538539
// try (Stream<Path> stateFiles = Files.list(path.resolve(MetaDataStateFormat.STATE_DIR_NAME))) {
539540
// for (final Path manifestPath : stateFiles
@@ -549,6 +550,90 @@ public void testHalfDeletedIndexImport() throws Exception {
549550
assertBusy(() -> assertThat(internalCluster().getInstance(NodeEnvironment.class).availableIndexFolders(), empty()));
550551
}
551552

553+
public void testOnlyWritesIndexMetaDataFilesOnDataNodes() throws Exception {
554+
final String masterNode = internalCluster().startMasterOnlyNode();
555+
final String dataNode = internalCluster().startDataOnlyNode();
556+
final String mixedNode = internalCluster().startNode();
557+
558+
createIndex("test", Settings.builder()
559+
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, between(1, 3))
560+
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 1)
561+
.build());
562+
ensureGreen("test");
563+
564+
final String indexUUID = client().admin().indices().prepareStats("test").get().getIndex("test").getUuid();
565+
566+
final Path[] masterPaths = internalCluster().getInstance(NodeEnvironment.class, masterNode).nodeDataPaths();
567+
final Path[] dataPaths = internalCluster().getInstance(NodeEnvironment.class, dataNode).nodeDataPaths();
568+
final Path[] mixedPaths = internalCluster().getInstance(NodeEnvironment.class, mixedNode).nodeDataPaths();
569+
570+
for (final Path path : masterPaths) {
571+
assertFalse("master: " + path, Files.exists(path.resolve(NodeEnvironment.INDICES_FOLDER)));
572+
}
573+
for (final Path path : dataPaths) {
574+
assertTrue("data: " + path, Files.exists(path.resolve(NodeEnvironment.INDICES_FOLDER).resolve(indexUUID)));
575+
}
576+
for (final Path path : mixedPaths) {
577+
assertTrue("mixed: " + path, Files.exists(path.resolve(NodeEnvironment.INDICES_FOLDER).resolve(indexUUID)));
578+
}
579+
580+
logger.info("--> remove shards from data node, to check the index folder is cleaned up");
581+
assertAcked(client().admin().indices().prepareUpdateSettings("test").setSettings(Settings.builder()
582+
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)
583+
.put(IndexMetaData.INDEX_ROUTING_EXCLUDE_GROUP_PREFIX + "._name", dataNode)));
584+
assertFalse(client().admin().cluster().prepareHealth("test").setWaitForGreenStatus()
585+
.setWaitForNoInitializingShards(true).setWaitForEvents(Priority.LANGUID).get().isTimedOut());
586+
587+
for (final Path path : masterPaths) {
588+
assertFalse("master: " + path, Files.exists(path.resolve(NodeEnvironment.INDICES_FOLDER)));
589+
}
590+
for (final Path path : mixedPaths) {
591+
assertTrue("mixed: " + path, Files.exists(path.resolve(NodeEnvironment.INDICES_FOLDER).resolve(indexUUID)));
592+
}
593+
assertBusy(() -> {
594+
for (final Path path : dataPaths) {
595+
assertFalse("data: " + path, Files.exists(path.resolve(NodeEnvironment.INDICES_FOLDER).resolve(indexUUID)));
596+
}
597+
});
598+
599+
logger.info("--> remove shards from mixed master/data node, to check the index folder is cleaned up");
600+
assertAcked(client().admin().indices().prepareUpdateSettings("test").setSettings(Settings.builder()
601+
.put(IndexMetaData.INDEX_ROUTING_EXCLUDE_GROUP_PREFIX + "._name", mixedNode)));
602+
assertFalse(client().admin().cluster().prepareHealth("test").setWaitForGreenStatus()
603+
.setWaitForNoInitializingShards(true).setWaitForEvents(Priority.LANGUID).get().isTimedOut());
604+
605+
for (final Path path : masterPaths) {
606+
assertFalse("master: " + path, Files.exists(path.resolve(NodeEnvironment.INDICES_FOLDER)));
607+
}
608+
for (final Path path : dataPaths) {
609+
assertTrue("data: " + path, Files.exists(path.resolve(NodeEnvironment.INDICES_FOLDER).resolve(indexUUID)));
610+
}
611+
assertBusy(() -> {
612+
for (final Path path : mixedPaths) {
613+
assertFalse("mixed: " + path, Files.exists(path.resolve(NodeEnvironment.INDICES_FOLDER).resolve(indexUUID)));
614+
}
615+
});
616+
617+
logger.info("--> delete index and check the index folder is cleaned up on all nodes");
618+
assertAcked(client().admin().indices().prepareUpdateSettings("test").setSettings(Settings.builder()
619+
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 1)
620+
.putNull(IndexMetaData.INDEX_ROUTING_EXCLUDE_GROUP_PREFIX + "._name")));
621+
ensureGreen("test");
622+
assertAcked(client().admin().indices().prepareDelete("test"));
623+
624+
for (final Path path : masterPaths) {
625+
assertFalse("master: " + path, Files.exists(path.resolve(NodeEnvironment.INDICES_FOLDER)));
626+
}
627+
assertBusy(() -> {
628+
for (final Path path : dataPaths) {
629+
assertFalse("data: " + path, Files.exists(path.resolve(NodeEnvironment.INDICES_FOLDER).resolve(indexUUID)));
630+
}
631+
for (final Path path : mixedPaths) {
632+
assertFalse("mixed: " + path, Files.exists(path.resolve(NodeEnvironment.INDICES_FOLDER).resolve(indexUUID)));
633+
}
634+
});
635+
}
636+
552637
private void restartNodesOnBrokenClusterState(ClusterState.Builder clusterStateBuilder) throws Exception {
553638
Map<String, LucenePersistedStateFactory> lucenePersistedStateFactories = Stream.of(internalCluster().getNodeNames())
554639
.collect(Collectors.toMap(Function.identity(),

server/src/test/java/org/elasticsearch/gateway/IncrementalClusterStateWriterTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ private IndexMetaData createIndexMetaData(String name) {
173173
public void testGetRelevantIndicesWithUnassignedShardsOnMasterEligibleNode() {
174174
IndexMetaData indexMetaData = createIndexMetaData("test");
175175
Set<Index> indices = IncrementalClusterStateWriter.getRelevantIndices(clusterStateWithUnassignedIndex(indexMetaData, true));
176-
assertThat(indices.size(), equalTo(1));
176+
assertThat(indices.size(), equalTo(0));
177177
}
178178

179179
public void testGetRelevantIndicesWithUnassignedShardsOnDataOnlyNode() {

server/src/test/java/org/elasticsearch/indices/IndicesServiceTests.java

+9-24
Original file line numberDiff line numberDiff line change
@@ -223,50 +223,35 @@ public void testDeleteIndexStore() throws Exception {
223223
ClusterService clusterService = getInstanceFromNode(ClusterService.class);
224224
IndexMetaData firstMetaData = clusterService.state().metaData().index("test");
225225
assertTrue(test.hasShard(0));
226+
ShardPath firstPath = ShardPath.loadShardPath(logger, getNodeEnvironment(), new ShardId(test.index(), 0), test.getIndexSettings());
226227

227-
try {
228-
indicesService.deleteIndexStore("boom", firstMetaData, clusterService.state());
229-
fail();
230-
} catch (IllegalStateException ex) {
231-
// all good
232-
}
228+
expectThrows(IllegalStateException.class, () -> indicesService.deleteIndexStore("boom", firstMetaData));
229+
assertTrue(firstPath.exists());
233230

234231
GatewayMetaState gwMetaState = getInstanceFromNode(GatewayMetaState.class);
235232
MetaData meta = gwMetaState.getMetaData();
236233
assertNotNull(meta);
237234
assertNotNull(meta.index("test"));
238235
assertAcked(client().admin().indices().prepareDelete("test"));
239236

237+
assertFalse(firstPath.exists());
238+
240239
meta = gwMetaState.getMetaData();
241240
assertNotNull(meta);
242241
assertNull(meta.index("test"));
243242

244-
245243
test = createIndex("test");
246244
client().prepareIndex("test").setId("1").setSource("field", "value").setRefreshPolicy(IMMEDIATE).get();
247245
client().admin().indices().prepareFlush("test").get();
248246
assertHitCount(client().prepareSearch("test").get(), 1);
249247
IndexMetaData secondMetaData = clusterService.state().metaData().index("test");
250248
assertAcked(client().admin().indices().prepareClose("test"));
251-
ShardPath path = ShardPath.loadShardPath(logger, getNodeEnvironment(), new ShardId(test.index(), 0), test.getIndexSettings());
252-
assertTrue(path.exists());
249+
ShardPath secondPath = ShardPath.loadShardPath(logger, getNodeEnvironment(), new ShardId(test.index(), 0), test.getIndexSettings());
250+
assertTrue(secondPath.exists());
253251

254-
try {
255-
indicesService.deleteIndexStore("boom", secondMetaData, clusterService.state());
256-
fail();
257-
} catch (IllegalStateException ex) {
258-
// all good
259-
}
260-
261-
assertTrue(path.exists());
252+
expectThrows(IllegalStateException.class, () -> indicesService.deleteIndexStore("boom", secondMetaData));
253+
assertTrue(secondPath.exists());
262254

263-
// now delete the old one and make sure we resolve against the name
264-
try {
265-
indicesService.deleteIndexStore("boom", firstMetaData, clusterService.state());
266-
fail();
267-
} catch (IllegalStateException ex) {
268-
// all good
269-
}
270255
assertAcked(client().admin().indices().prepareOpen("test"));
271256
ensureGreen("test");
272257
}

0 commit comments

Comments
 (0)