Skip to content

Commit e69c835

Browse files
committed
Allocate newly created indices on data_hot tier nodes (elastic#61342)
This commit adds the functionality to allocate newly created indices on nodes in the "hot" tier by default when they are created. This does not break existing behavior, as nodes with the `data` role are considered to be part of the hot tier. Users that separate their deployments by using the `data_hot` (and `data_warm`, `data_cold`, `data_frozen`) roles will have their data allocated on the hot tier nodes now by default. This change is a little more complicated than changing the default value for `index.routing.allocation.include._tier` from null to "data_hot". Instead, this adds the ability to have a plugin inject a setting into the builder for a newly created index. This has the benefit of allowing this setting to be visible as part of the settings when retrieving the index, for example: ``` // Create an index PUT /eggplant // Get an index GET /eggplant?flat_settings ``` Returns the default settings now of: ```json { "eggplant" : { "aliases" : { }, "mappings" : { }, "settings" : { "index.creation_date" : "1597855465598", "index.number_of_replicas" : "1", "index.number_of_shards" : "1", "index.provided_name" : "eggplant", "index.routing.allocation.include._tier" : "data_hot", "index.uuid" : "6ySG78s9RWGystRipoBFCA", "index.version.created" : "8000099" } } } ``` After the initial setting of this setting, it can be treated like any other index level setting. This new setting is *not* set on a new index if any of the following is true: - The index is created with an `index.routing.allocation.include.<anything>` setting - The index is created with an `index.routing.allocation.exclude.<anything>` setting - The index is created with an `index.routing.allocation.require.<anything>` setting - The index is created with a null `index.routing.allocation.include._tier` value - The index was created from an existing source metadata (shrink, clone, split, etc) Relates to elastic#60848
1 parent 1cb97a2 commit e69c835

File tree

15 files changed

+444
-24
lines changed

15 files changed

+444
-24
lines changed

docs/reference/api-conventions.asciidoc

+8
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ Returns:
391391
"index.creation_date": "1474389951325",
392392
"index.uuid": "n6gzFZTgS664GUfx0Xrpjw",
393393
"index.version.created": ...,
394+
"index.routing.allocation.include._tier" : "data_hot",
394395
"index.provided_name" : "my-index-000001"
395396
}
396397
}
@@ -424,6 +425,13 @@ Returns:
424425
"version": {
425426
"created": ...
426427
},
428+
"routing": {
429+
"allocation": {
430+
"include": {
431+
"_tier": "data_hot"
432+
}
433+
}
434+
},
427435
"provided_name" : "my-index-000001"
428436
}
429437
}

docs/reference/cluster/allocation-explain.asciidoc

+6-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,12 @@ DELETE my-index-000001
113113
[source,console]
114114
--------------------------------------------------
115115
PUT /my-index-000001?master_timeout=1s&timeout=1s
116-
{"settings": {"index.routing.allocation.include._name": "non_existent_node"} }
116+
{
117+
"settings": {
118+
"index.routing.allocation.include._name": "non_existent_node",
119+
"index.routing.allocation.include._tier": null
120+
}
121+
}
117122
118123
GET /_cluster/allocation/explain
119124
{

qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/RecoveryIT.java

+3
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ public void testRelocationWithConcurrentIndexing() throws Exception {
250250
// but the recovering copy will be seen as invalid and the cluster health won't return to GREEN
251251
// before timing out
252252
.put(INDEX_DELAYED_NODE_LEFT_TIMEOUT_SETTING.getKey(), "100ms")
253+
.put("index.routing.allocation.include._tier", "")
253254
.put(SETTING_ALLOCATION_MAX_RETRY.getKey(), "0"); // fail faster
254255
createIndex(index, settings.build());
255256
indexDocs(index, 0, 10);
@@ -266,6 +267,7 @@ public void testRelocationWithConcurrentIndexing() throws Exception {
266267
.put(IndexMetadata.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), 0)
267268
.put(INDEX_ROUTING_ALLOCATION_ENABLE_SETTING.getKey(), (String)null)
268269
.put("index.routing.allocation.include._id", oldNode)
270+
.putNull("index.routing.allocation.include._tier")
269271
);
270272
ensureGreen(index); // wait for the primary to be assigned
271273
ensureNoInitializingShards(); // wait for all other shard activity to finish
@@ -288,6 +290,7 @@ public void testRelocationWithConcurrentIndexing() throws Exception {
288290
updateIndexSettings(index, Settings.builder()
289291
.put(IndexMetadata.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), 2)
290292
.put("index.routing.allocation.include._id", (String)null)
293+
.putNull("index.routing.allocation.include._tier")
291294
);
292295
asyncIndexDocs(index, 60, 45).get();
293296
ensureGreen(index);

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

+74-8
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import org.elasticsearch.index.mapper.MapperService;
7070
import org.elasticsearch.index.mapper.MapperService.MergeReason;
7171
import org.elasticsearch.index.query.QueryShardContext;
72+
import org.elasticsearch.index.shard.IndexSettingProvider;
7273
import org.elasticsearch.indices.IndexCreationException;
7374
import org.elasticsearch.indices.IndicesService;
7475
import org.elasticsearch.indices.InvalidIndexNameException;
@@ -85,6 +86,7 @@
8586
import java.util.Arrays;
8687
import java.util.Collections;
8788
import java.util.HashMap;
89+
import java.util.HashSet;
8890
import java.util.List;
8991
import java.util.Locale;
9092
import java.util.Map;
@@ -130,6 +132,7 @@ public class MetadataCreateIndexService {
130132
private final SystemIndices systemIndices;
131133
private final ShardLimitValidator shardLimitValidator;
132134
private final boolean forbidPrivateIndexSettings;
135+
private final Set<IndexSettingProvider> indexSettingProviders = new HashSet<>();
133136

134137
public MetadataCreateIndexService(
135138
final Settings settings,
@@ -158,6 +161,19 @@ public MetadataCreateIndexService(
158161
this.shardLimitValidator = shardLimitValidator;
159162
}
160163

164+
/**
165+
* Add a provider to be invoked to get additional index settings prior to an index being created
166+
*/
167+
public void addAdditionalIndexSettingProvider(IndexSettingProvider provider) {
168+
if (provider == null) {
169+
throw new IllegalArgumentException("provider must not be null");
170+
}
171+
if (indexSettingProviders.contains(provider)) {
172+
throw new IllegalArgumentException("provider already added");
173+
}
174+
this.indexSettingProviders.add(provider);
175+
}
176+
161177
/**
162178
* Validate the name for an index against some static rules and a cluster state.
163179
*/
@@ -471,7 +487,7 @@ private ClusterState applyCreateIndexRequestWithV1Templates(final ClusterState c
471487

472488
final Settings aggregatedIndexSettings =
473489
aggregateIndexSettings(currentState, request, MetadataIndexTemplateService.resolveSettings(templates),
474-
null, settings, indexScopedSettings, shardLimitValidator);
490+
null, settings, indexScopedSettings, shardLimitValidator, indexSettingProviders);
475491
int routingNumShards = getIndexNumberOfRoutingShards(aggregatedIndexSettings, null);
476492
IndexMetadata tmpImd = buildAndValidateTemporaryIndexMetadata(currentState, aggregatedIndexSettings, request, routingNumShards);
477493

@@ -497,7 +513,7 @@ private ClusterState applyCreateIndexRequestWithV2Template(final ClusterState cu
497513
final Settings aggregatedIndexSettings =
498514
aggregateIndexSettings(currentState, request,
499515
MetadataIndexTemplateService.resolveSettings(currentState.metadata(), templateName),
500-
null, settings, indexScopedSettings, shardLimitValidator);
516+
null, settings, indexScopedSettings, shardLimitValidator, indexSettingProviders);
501517
int routingNumShards = getIndexNumberOfRoutingShards(aggregatedIndexSettings, null);
502518
IndexMetadata tmpImd = buildAndValidateTemporaryIndexMetadata(currentState, aggregatedIndexSettings, request, routingNumShards);
503519

@@ -548,7 +564,7 @@ private ClusterState applyCreateIndexRequestWithExistingMetadata(final ClusterSt
548564
}
549565

550566
final Settings aggregatedIndexSettings = aggregateIndexSettings(currentState, request, Settings.EMPTY,
551-
sourceMetadata, settings, indexScopedSettings, shardLimitValidator);
567+
sourceMetadata, settings, indexScopedSettings, shardLimitValidator, indexSettingProviders);
552568
final int routingNumShards = getIndexNumberOfRoutingShards(aggregatedIndexSettings, sourceMetadata);
553569
IndexMetadata tmpImd = buildAndValidateTemporaryIndexMetadata(currentState, aggregatedIndexSettings, request, routingNumShards);
554570

@@ -635,14 +651,64 @@ static Map<String, Map<String, Object>> parseV1Mappings(Map<String, String> requ
635651
* @return the aggregated settings for the new index
636652
*/
637653
static Settings aggregateIndexSettings(ClusterState currentState, CreateIndexClusterStateUpdateRequest request,
638-
Settings templateSettings, @Nullable IndexMetadata sourceMetadata, Settings settings,
639-
IndexScopedSettings indexScopedSettings, ShardLimitValidator shardLimitValidator) {
640-
Settings.Builder indexSettingsBuilder = Settings.builder();
654+
Settings combinedTemplateSettings, @Nullable IndexMetadata sourceMetadata, Settings settings,
655+
IndexScopedSettings indexScopedSettings, ShardLimitValidator shardLimitValidator,
656+
Set<IndexSettingProvider> indexSettingProviders) {
657+
// Create builders for the template and request settings. We transform these into builders
658+
// because we may want settings to be "removed" from these prior to being set on the new
659+
// index (see more comments below)
660+
final Settings.Builder templateSettings = Settings.builder().put(combinedTemplateSettings);
661+
final Settings.Builder requestSettings = Settings.builder().put(request.settings());
662+
663+
final Settings.Builder indexSettingsBuilder = Settings.builder();
641664
if (sourceMetadata == null) {
642-
indexSettingsBuilder.put(templateSettings);
665+
final Settings.Builder additionalIndexSettings = Settings.builder();
666+
final Settings templateAndRequestSettings = Settings.builder()
667+
.put(combinedTemplateSettings)
668+
.put(request.settings())
669+
.build();
670+
671+
// Loop through all the explicit index setting providers, adding them to the
672+
// additionalIndexSettings map
673+
for (IndexSettingProvider provider : indexSettingProviders) {
674+
additionalIndexSettings.put(provider.getAdditionalIndexSettings(request.index(), templateAndRequestSettings));
675+
}
676+
677+
// For all the explicit settings, we go through the template and request level settings
678+
// and see if either a template or the request has "cancelled out" an explicit default
679+
// setting. For example, if a plugin had as an explicit setting:
680+
// "index.mysetting": "blah
681+
// And either a template or create index request had:
682+
// "index.mysetting": null
683+
// We want to remove the explicit setting not only from the explicitly set settings, but
684+
// also from the template and request settings, so that from the newly create index's
685+
// perspective it is as though the setting has not been set at all (using the default
686+
// value).
687+
for (String explicitSetting : additionalIndexSettings.keys()) {
688+
if (templateSettings.keys().contains(explicitSetting) && templateSettings.get(explicitSetting) == null) {
689+
logger.debug("removing default [{}] setting as it in set to null in a template for [{}] creation",
690+
explicitSetting, request.index());
691+
additionalIndexSettings.remove(explicitSetting);
692+
templateSettings.remove(explicitSetting);
693+
}
694+
if (requestSettings.keys().contains(explicitSetting) && requestSettings.get(explicitSetting) == null) {
695+
logger.debug("removing default [{}] setting as it in set to null in the request for [{}] creation",
696+
explicitSetting, request.index());
697+
additionalIndexSettings.remove(explicitSetting);
698+
requestSettings.remove(explicitSetting);
699+
}
700+
}
701+
702+
// Finally, we actually add the explicit defaults prior to the template settings and the
703+
// request settings, so that the precedence goes:
704+
// Explicit Defaults -> Template -> Request -> Necessary Settings (# of shards, uuid, etc)
705+
indexSettingsBuilder.put(additionalIndexSettings.build());
706+
indexSettingsBuilder.put(templateSettings.build());
643707
}
708+
644709
// now, put the request settings, so they override templates
645-
indexSettingsBuilder.put(request.settings());
710+
indexSettingsBuilder.put(requestSettings.build());
711+
646712
if (indexSettingsBuilder.get(IndexMetadata.SETTING_INDEX_VERSION_CREATED.getKey()) == null) {
647713
final DiscoveryNodes nodes = currentState.nodes();
648714
final Version createdVersion = Version.min(Version.CURRENT, nodes.getSmallestNonClientNodeVersion());

server/src/main/java/org/elasticsearch/cluster/node/DiscoveryNodeFilters.java

+3
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ public boolean match(DiscoveryNode node) {
181181
}
182182
}
183183
}
184+
} else if ("_tier".equals(attr)) {
185+
// Always allow _tier as an attribute, will be handled elsewhere
186+
return true;
184187
} else {
185188
String nodeAttributeValue = node.getAttributes().get(attr);
186189
if (nodeAttributeValue == null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.index.shard;
21+
22+
import org.elasticsearch.common.settings.Settings;
23+
24+
/**
25+
* An {@link IndexSettingProvider} is a provider for index level settings that can be set
26+
* explicitly as a default value (so they show up as "set" for newly created indices)
27+
*/
28+
public interface IndexSettingProvider {
29+
/**
30+
* Returns explicitly set default index {@link Settings} for the given index. This should not
31+
* return null.
32+
*/
33+
default Settings getAdditionalIndexSettings(String indexName, Settings templateAndRequestSettings) {
34+
return Settings.EMPTY;
35+
}
36+
}

server/src/main/java/org/elasticsearch/node/Node.java

+3
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,9 @@ protected Node(final Environment initialEnvironment,
520520
systemIndices,
521521
forbidPrivateIndexSettings
522522
);
523+
pluginsService.filterPlugins(Plugin.class)
524+
.forEach(p -> p.getAdditionalIndexSettingProviders()
525+
.forEach(metadataCreateIndexService::addAdditionalIndexSettingProvider));
523526

524527
final MetadataCreateDataStreamService metadataCreateDataStreamService =
525528
new MetadataCreateDataStreamService(threadPool, clusterService, metadataCreateIndexService);

server/src/main/java/org/elasticsearch/plugins/Plugin.java

+11
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.elasticsearch.env.Environment;
4040
import org.elasticsearch.env.NodeEnvironment;
4141
import org.elasticsearch.index.IndexModule;
42+
import org.elasticsearch.index.shard.IndexSettingProvider;
4243
import org.elasticsearch.repositories.RepositoriesService;
4344
import org.elasticsearch.script.ScriptService;
4445
import org.elasticsearch.threadpool.ExecutorBuilder;
@@ -227,4 +228,14 @@ public Set<DiscoveryNodeRole> getRoles() {
227228
public void close() throws IOException {
228229

229230
}
231+
232+
/**
233+
* An {@link IndexSettingProvider} allows hooking in to parts of an index
234+
* lifecycle to provide explicit default settings for newly created indices. Rather than changing
235+
* the default values for an index-level setting, these act as though the setting has been set
236+
* explicitly, but still allow the setting to be overridden by a template or creation request body.
237+
*/
238+
public Collection<IndexSettingProvider> getAdditionalIndexSettingProviders() {
239+
return Collections.emptyList();
240+
}
230241
}

0 commit comments

Comments
 (0)