-
Notifications
You must be signed in to change notification settings - Fork 25.6k
New setting to prevent automatically importing dangling indices #49174
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 13 commits
91819a0
fedf771
982be6f
865831e
32d3646
e377772
6393357
7950e86
ce3b5f4
77b4f09
3a453e9
54db504
44834b8
6bf3e9b
71bcbde
d156385
f4f3340
7cd372a
a9dfc48
85f7f57
2a40971
77a7649
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -52,9 +52,22 @@ NOTE: These settings only take effect on a full cluster restart. | |
| [[modules-gateway-dangling-indices]] | ||
| === Dangling indices | ||
|
|
||
| When a node joins the cluster, any shards stored in its local data | ||
| directory which do not already exist in the cluster will be imported into the | ||
| cluster. This functionality is intended as a best effort to help users who | ||
| lose all master nodes. If a new master node is started which is unaware of | ||
| the other indices in the cluster, adding the old nodes will cause the old | ||
| indices to be imported, instead of being deleted. | ||
| When a node joins the cluster, it will search for any shards that are | ||
| stored in its local data directory and do not already exist in the | ||
| cluster. If the static setting `gateway.auto_import_dangling_indices` is | ||
|
||
| `true` (the default is `false`), then those shards will be imported into | ||
| the cluster. This functionality is intended as a best effort to help users | ||
| who lose all master nodes. If a new master node is started which is unaware | ||
|
||
| of the other indices in the cluster, adding the old nodes will cause the | ||
| old indices to be imported, instead of being deleted. | ||
|
|
||
| Enabling `gateway.auto_import_dangling_indices` should only be done if | ||
| absolutely necessary, after understanding the possible consequences (this is not an exhaustive list): | ||
|
||
|
|
||
| * A deleted index might suddenly reappear when a node joins the cluster. | ||
| * You might delete an index and see the immediate creation of another index | ||
| with the same name, containing stale mappings and old data. | ||
| * New documents could be written to the index before anyone realises that | ||
| it has been recovered | ||
| * {es} may not be able to find copies of all of the shards of the index, | ||
| resulting in a red cluster state. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,7 @@ | |
| import org.elasticsearch.cluster.metadata.MetaData; | ||
| import org.elasticsearch.cluster.service.ClusterService; | ||
| import org.elasticsearch.common.inject.Inject; | ||
| import org.elasticsearch.common.settings.Setting; | ||
| import org.elasticsearch.common.util.concurrent.ConcurrentCollections; | ||
| import org.elasticsearch.env.NodeEnvironment; | ||
| import org.elasticsearch.index.Index; | ||
|
|
@@ -55,19 +56,33 @@ public class DanglingIndicesState implements ClusterStateListener { | |
|
|
||
| private static final Logger logger = LogManager.getLogger(DanglingIndicesState.class); | ||
|
|
||
| public static final Setting<Boolean> AUTO_IMPORT_DANGLING_INDICES_SETTING = Setting.boolSetting( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's deprecate this setting right away, so that we can remove it in 8.0 if we want |
||
| "gateway.auto_import_dangling_indices", | ||
| false, | ||
|
||
| Setting.Property.NodeScope | ||
| ); | ||
|
|
||
| private final NodeEnvironment nodeEnv; | ||
| private final MetaStateService metaStateService; | ||
| private final LocalAllocateDangledIndices allocateDangledIndices; | ||
|
|
||
| private final Map<Index, IndexMetaData> danglingIndices = ConcurrentCollections.newConcurrentMap(); | ||
|
|
||
| private boolean allocateDanglingIndices; | ||
|
||
|
|
||
| @Inject | ||
| public DanglingIndicesState(NodeEnvironment nodeEnv, MetaStateService metaStateService, | ||
| LocalAllocateDangledIndices allocateDangledIndices, ClusterService clusterService) { | ||
| this.nodeEnv = nodeEnv; | ||
| this.metaStateService = metaStateService; | ||
| this.allocateDangledIndices = allocateDangledIndices; | ||
| clusterService.addListener(this); | ||
|
||
|
|
||
| this.allocateDanglingIndices = AUTO_IMPORT_DANGLING_INDICES_SETTING.get(clusterService.getSettings()); | ||
| } | ||
|
|
||
| public void setAllocateDanglingIndicesSetting(boolean allocateDanglingIndices) { | ||
|
||
| this.allocateDanglingIndices = allocateDanglingIndices; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -171,10 +186,15 @@ private IndexMetaData stripAliases(IndexMetaData indexMetaData) { | |
| * Allocates the provided list of the dangled indices by sending them to the master node | ||
| * for allocation. | ||
| */ | ||
| private void allocateDanglingIndices() { | ||
| void allocateDanglingIndices() { | ||
| if (this.allocateDanglingIndices == false) { | ||
|
||
| return; | ||
| } | ||
|
|
||
| if (danglingIndices.isEmpty()) { | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| allocateDangledIndices.allocateDangled(Collections.unmodifiableCollection(new ArrayList<>(danglingIndices.values())), | ||
| new ActionListener<>() { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,6 +24,7 @@ | |
| import org.elasticsearch.cluster.metadata.IndexMetaData; | ||
| import org.elasticsearch.cluster.metadata.MetaData; | ||
| import org.elasticsearch.cluster.service.ClusterService; | ||
| import org.elasticsearch.common.settings.ClusterSettings; | ||
| import org.elasticsearch.common.settings.Settings; | ||
| import org.elasticsearch.env.NodeEnvironment; | ||
| import org.elasticsearch.index.Index; | ||
|
|
@@ -35,51 +36,73 @@ | |
| import java.nio.file.StandardCopyOption; | ||
| import java.util.Map; | ||
|
|
||
| import static org.elasticsearch.gateway.DanglingIndicesState.AUTO_IMPORT_DANGLING_INDICES_SETTING; | ||
| import static org.hamcrest.Matchers.equalTo; | ||
| import static org.mockito.Matchers.any; | ||
| import static org.mockito.Mockito.mock; | ||
| import static org.mockito.Mockito.never; | ||
| import static org.mockito.Mockito.verify; | ||
| import static org.mockito.Mockito.when; | ||
|
|
||
| public class DanglingIndicesStateTests extends ESTestCase { | ||
|
|
||
| private static Settings indexSettings = Settings.builder() | ||
| .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) | ||
| .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) | ||
| .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) | ||
| .build(); | ||
| .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) | ||
|
||
| .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) | ||
| .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) | ||
| .build(); | ||
|
|
||
| public void testCleanupWhenEmpty() throws Exception { | ||
| try (NodeEnvironment env = newNodeEnvironment()) { | ||
| MetaStateService metaStateService = new MetaStateService(env, xContentRegistry()); | ||
| DanglingIndicesState danglingState = createDanglingIndicesState(env, metaStateService); | ||
|
|
||
| // Given an empty state | ||
|
||
| assertTrue(danglingState.getDanglingIndices().isEmpty()); | ||
|
|
||
| // When passed an empty metadata | ||
| MetaData metaData = MetaData.builder().build(); | ||
| danglingState.cleanupAllocatedDangledIndices(metaData); | ||
|
|
||
| // Then the state remains empty | ||
| assertTrue(danglingState.getDanglingIndices().isEmpty()); | ||
| } | ||
| } | ||
|
|
||
| public void testDanglingIndicesDiscovery() throws Exception { | ||
| try (NodeEnvironment env = newNodeEnvironment()) { | ||
| MetaStateService metaStateService = new MetaStateService(env, xContentRegistry()); | ||
| DanglingIndicesState danglingState = createDanglingIndicesState(env, metaStateService); | ||
|
|
||
| // Given an empty state | ||
| assertTrue(danglingState.getDanglingIndices().isEmpty()); | ||
|
|
||
| // When passed a metdata with an unknown index | ||
|
||
| MetaData metaData = MetaData.builder().build(); | ||
| final Settings.Builder settings = Settings.builder().put(indexSettings).put(IndexMetaData.SETTING_INDEX_UUID, "test1UUID"); | ||
| IndexMetaData dangledIndex = IndexMetaData.builder("test1").settings(settings).build(); | ||
| metaStateService.writeIndex("test_write", dangledIndex); | ||
| Map<Index, IndexMetaData> newDanglingIndices = danglingState.findNewDanglingIndices(metaData); | ||
|
|
||
| // Then that index is considered dangling | ||
| assertTrue(newDanglingIndices.containsKey(dangledIndex.getIndex())); | ||
|
|
||
| // And when passed another metadata with that index | ||
| metaData = MetaData.builder().put(dangledIndex, false).build(); | ||
| newDanglingIndices = danglingState.findNewDanglingIndices(metaData); | ||
|
|
||
| // Then then index is not considered to be a new dangling index for a second time | ||
| assertFalse(newDanglingIndices.containsKey(dangledIndex.getIndex())); | ||
| } | ||
| } | ||
|
|
||
| public void testInvalidIndexFolder() throws Exception { | ||
| try (NodeEnvironment env = newNodeEnvironment()) { | ||
| // Given an empty state | ||
| MetaStateService metaStateService = new MetaStateService(env, xContentRegistry()); | ||
| DanglingIndicesState danglingState = createDanglingIndicesState(env, metaStateService); | ||
|
|
||
| // When passed settings for an index whose folder does not exist | ||
| MetaData metaData = MetaData.builder().build(); | ||
| final String uuid = "test1UUID"; | ||
| final Settings.Builder settings = Settings.builder().put(indexSettings).put(IndexMetaData.SETTING_INDEX_UUID, uuid); | ||
|
|
@@ -90,6 +113,8 @@ public void testInvalidIndexFolder() throws Exception { | |
| Files.move(path, path.resolveSibling("invalidUUID"), StandardCopyOption.ATOMIC_MOVE); | ||
| } | ||
| } | ||
|
|
||
| // Then an exception is thrown describing the problem | ||
| try { | ||
| danglingState.findNewDanglingIndices(metaData); | ||
| fail("no exception thrown for invalid folder name"); | ||
|
|
@@ -145,25 +170,31 @@ public void testDanglingProcessing() throws Exception { | |
|
|
||
| public void testDanglingIndicesNotImportedWhenTombstonePresent() throws Exception { | ||
| try (NodeEnvironment env = newNodeEnvironment()) { | ||
| // Given an empty state | ||
| MetaStateService metaStateService = new MetaStateService(env, xContentRegistry()); | ||
| DanglingIndicesState danglingState = createDanglingIndicesState(env, metaStateService); | ||
|
|
||
| // When passed a dangling index | ||
| final Settings.Builder settings = Settings.builder().put(indexSettings).put(IndexMetaData.SETTING_INDEX_UUID, "test1UUID"); | ||
| IndexMetaData dangledIndex = IndexMetaData.builder("test1").settings(settings).build(); | ||
| metaStateService.writeIndex("test_write", dangledIndex); | ||
|
|
||
| // And there is a tombstone for that index | ||
| final IndexGraveyard graveyard = IndexGraveyard.builder().addTombstone(dangledIndex.getIndex()).build(); | ||
| final MetaData metaData = MetaData.builder().indexGraveyard(graveyard).build(); | ||
| assertThat(danglingState.findNewDanglingIndices(metaData).size(), equalTo(0)); | ||
|
|
||
| // Then that index is not imported | ||
| assertThat(danglingState.findNewDanglingIndices(metaData).size(), equalTo(0)); | ||
| } | ||
| } | ||
|
|
||
| public void testDanglingIndicesStripAliases() throws Exception { | ||
| try (NodeEnvironment env = newNodeEnvironment()) { | ||
| // Given an empty state | ||
| MetaStateService metaStateService = new MetaStateService(env, xContentRegistry()); | ||
| DanglingIndicesState danglingState = createDanglingIndicesState(env, metaStateService); | ||
|
|
||
| // When passed an index that has an alias | ||
| final Settings.Builder settings = Settings.builder().put(indexSettings).put(IndexMetaData.SETTING_INDEX_UUID, "test1UUID"); | ||
| IndexMetaData dangledIndex = IndexMetaData.builder("test1") | ||
| .settings(settings) | ||
|
|
@@ -174,14 +205,86 @@ public void testDanglingIndicesStripAliases() throws Exception { | |
|
|
||
| final MetaData metaData = MetaData.builder().build(); | ||
| Map<Index, IndexMetaData> newDanglingIndices = danglingState.findNewDanglingIndices(metaData); | ||
|
|
||
| // Then the index is identifying as dangling | ||
| assertThat(newDanglingIndices.size(), equalTo(1)); | ||
| Map.Entry<Index, IndexMetaData> entry = newDanglingIndices.entrySet().iterator().next(); | ||
| assertThat(entry.getKey().getName(), equalTo("test1")); | ||
|
|
||
| // And the alias is removed | ||
| assertThat(entry.getValue().getAliases().size(), equalTo(0)); | ||
| } | ||
| } | ||
|
|
||
| public void testDanglingIndicesAreNotAllocatedWhenDisabled() throws Exception { | ||
| try (NodeEnvironment env = newNodeEnvironment()) { | ||
| MetaStateService metaStateService = new MetaStateService(env, xContentRegistry()); | ||
| LocalAllocateDangledIndices localAllocateDangledIndices = mock(LocalAllocateDangledIndices.class); | ||
| DanglingIndicesState danglingState = createDanglingIndicesState(env, metaStateService, localAllocateDangledIndices); | ||
|
|
||
| assertTrue(danglingState.getDanglingIndices().isEmpty()); | ||
|
|
||
| // Given a metadata that does not enable allocation of dangling indices | ||
|
||
| MetaData metaData = MetaData.builder().build(); | ||
|
|
||
| final Settings.Builder settings = Settings.builder().put(indexSettings).put(IndexMetaData.SETTING_INDEX_UUID, "test1UUID"); | ||
| IndexMetaData dangledIndex = IndexMetaData.builder("test1").settings(settings).build(); | ||
| metaStateService.writeIndex("test_write", dangledIndex); | ||
|
|
||
| danglingState.findNewAndAddDanglingIndices(metaData); | ||
|
|
||
| // When calling the allocate method | ||
| danglingState.allocateDanglingIndices(); | ||
|
|
||
| // Then allocation is not attempted | ||
| verify(localAllocateDangledIndices, never()).allocateDangled(any(), any()); | ||
| } | ||
| } | ||
|
|
||
| public void testDanglingIndicesAreAllocatedWhenEnabled() throws Exception { | ||
| try (NodeEnvironment env = newNodeEnvironment()) { | ||
| MetaStateService metaStateService = new MetaStateService(env, xContentRegistry()); | ||
| LocalAllocateDangledIndices localAllocateDangledIndices = mock(LocalAllocateDangledIndices.class); | ||
| DanglingIndicesState danglingState = createDanglingIndicesState(env, metaStateService, localAllocateDangledIndices); | ||
|
|
||
| assertTrue(danglingState.getDanglingIndices().isEmpty()); | ||
|
|
||
| // Given a state where automatic allocation is enabled | ||
| danglingState.setAllocateDanglingIndicesSetting(true); | ||
|
||
|
|
||
| MetaData metaData = MetaData.builder().build(); | ||
|
|
||
| final Settings.Builder settings = Settings.builder().put(indexSettings).put(IndexMetaData.SETTING_INDEX_UUID, "test1UUID"); | ||
| IndexMetaData dangledIndex = IndexMetaData.builder("test1").settings(settings).build(); | ||
| metaStateService.writeIndex("test_write", dangledIndex); | ||
|
|
||
| danglingState.findNewAndAddDanglingIndices(metaData); | ||
|
|
||
| // When calling the allocate method | ||
| danglingState.allocateDanglingIndices(); | ||
|
|
||
| // Ensure that allocation is attempted | ||
| verify(localAllocateDangledIndices).allocateDangled(any(), any()); | ||
| } | ||
| } | ||
|
|
||
| private DanglingIndicesState createDanglingIndicesState(NodeEnvironment env, MetaStateService metaStateService) { | ||
| return new DanglingIndicesState(env, metaStateService, null, mock(ClusterService.class)); | ||
| return createDanglingIndicesState(env, metaStateService, null); | ||
| } | ||
|
|
||
| private DanglingIndicesState createDanglingIndicesState( | ||
| NodeEnvironment env, | ||
| MetaStateService metaStateService, | ||
| LocalAllocateDangledIndices indexAllocator | ||
| ) { | ||
| final Settings allocateSettings = Settings.builder().put(AUTO_IMPORT_DANGLING_INDICES_SETTING.getKey(), false).build(); | ||
|
|
||
| final ClusterService clusterServiceMock = mock(ClusterService.class); | ||
| when(clusterServiceMock.getClusterSettings()).thenReturn( | ||
| new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS) | ||
| ); | ||
| when(clusterServiceMock.getSettings()).thenReturn(allocateSettings); | ||
|
|
||
| return new DanglingIndicesState(env, metaStateService, indexAllocator, clusterServiceMock); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given my previous comment about preserving existing behaviour, I think we should leave the docs alone for now. I don't think we should put much effort into improving the docs for today's behaviour, and we'll need to make more changes here when we add these APIs.