Skip to content

Commit a11e6f5

Browse files
Breaking change for single data node setting (#73737)
In #55805, we added a setting to allow single data node clusters to respect the high watermark. In #73733 we added the related deprecations. This commit ensures the only valid value for the setting is true and adds deprecations if the setting is set. The setting will be removed in a future release. Co-authored-by: David Turner <[email protected]>
1 parent 7a3a45a commit a11e6f5

File tree

7 files changed

+113
-157
lines changed

7 files changed

+113
-157
lines changed

docs/reference/migration/migrate_8_0/settings.asciidoc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,3 +280,22 @@ per index data path settings.
280280

281281
*Impact* +
282282
Discontinue use of the deprecated settings.
283+
284+
[[single-data-node-watermark-setting]]
285+
.Single data node watermark setting only accept true and is deprecated
286+
[%collapsible]
287+
====
288+
*Details* +
289+
In 7.14, setting `cluster.routing.allocation.disk.watermark.enable_for_single_data_node`
290+
to false was deprecated. Starting in 8.0, the only legal value will be
291+
true. In a future release, the setting will be removed completely, with same
292+
behavior as if the setting was `true`.
293+
294+
If the old behavior is desired for a single data node cluster, disk based
295+
allocation can be disabled by setting
296+
`cluster.routing.allocation.disk.threshold_enabled: false`
297+
298+
*Impact* +
299+
Discontinue use of the deprecated setting.
300+
====
301+

docs/reference/modules/cluster/disk_allocator.asciidoc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,10 @@ Controls the high watermark. It defaults to `90%`, meaning that {es} will attemp
8484

8585
`cluster.routing.allocation.disk.watermark.enable_for_single_data_node`::
8686
(<<static-cluster-setting,Static>>)
87-
For a single data node, the default is to disregard disk watermarks when
88-
making an allocation decision. This is deprecated behavior and will be
89-
changed in 8.0. This setting can be set to `true` to enable the
90-
disk watermarks for a single data node cluster (will become default in 8.0).
87+
In earlier releases, the default behaviour was to disregard disk watermarks for a single
88+
data node cluster when making an allocation decision. This is deprecated behavior
89+
since 7.14 and has been removed in 8.0. The only valid value for this setting
90+
is now `true`. The setting will be removed in a future release.
9191

9292
[[cluster-routing-flood-stage]]
9393
// tag::cluster-routing-flood-stage-tag[]

server/src/main/java/org/elasticsearch/cluster/routing/allocation/decider/DiskThresholdDecider.java

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@
2626
import org.elasticsearch.cluster.routing.allocation.RoutingAllocation;
2727
import org.elasticsearch.common.Strings;
2828
import org.elasticsearch.common.collect.ImmutableOpenMap;
29+
import org.elasticsearch.common.logging.DeprecationLogger;
2930
import org.elasticsearch.common.settings.ClusterSettings;
3031
import org.elasticsearch.common.settings.Setting;
3132
import org.elasticsearch.common.settings.Settings;
33+
import org.elasticsearch.common.settings.SettingsException;
3234
import org.elasticsearch.common.unit.ByteSizeValue;
3335
import org.elasticsearch.index.Index;
3436
import org.elasticsearch.index.shard.ShardId;
@@ -66,23 +68,35 @@
6668
public class DiskThresholdDecider extends AllocationDecider {
6769

6870
private static final Logger logger = LogManager.getLogger(DiskThresholdDecider.class);
71+
private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(DiskThresholdDecider.class);
6972

7073
public static final String NAME = "disk_threshold";
7174

7275
public static final Setting<Boolean> ENABLE_FOR_SINGLE_DATA_NODE =
73-
Setting.boolSetting("cluster.routing.allocation.disk.watermark.enable_for_single_data_node", false, Setting.Property.NodeScope);
76+
Setting.boolSetting("cluster.routing.allocation.disk.watermark.enable_for_single_data_node", true,
77+
new Setting.Validator<>() {
78+
@Override
79+
public void validate(Boolean value) {
80+
if (value == Boolean.FALSE) {
81+
throw new SettingsException("setting [{}=false] is not allowed, only true is valid",
82+
ENABLE_FOR_SINGLE_DATA_NODE.getKey());
83+
}
84+
}
85+
},
86+
Setting.Property.NodeScope, Setting.Property.Deprecated);
7487

7588
public static final Setting<Boolean> SETTING_IGNORE_DISK_WATERMARKS =
7689
Setting.boolSetting("index.routing.allocation.disk.watermark.ignore", false,
7790
Setting.Property.IndexScope, Setting.Property.PrivateIndex);
7891

7992
private final DiskThresholdSettings diskThresholdSettings;
80-
private final boolean enableForSingleDataNode;
8193

8294
public DiskThresholdDecider(Settings settings, ClusterSettings clusterSettings) {
8395
this.diskThresholdSettings = new DiskThresholdSettings(settings, clusterSettings);
8496
assert Version.CURRENT.major < 9 : "remove enable_for_single_data_node in 9";
85-
this.enableForSingleDataNode = ENABLE_FOR_SINGLE_DATA_NODE.get(settings);
97+
// get deprecation warnings.
98+
boolean enabledForSingleDataNode = ENABLE_FOR_SINGLE_DATA_NODE.get(settings);
99+
assert enabledForSingleDataNode;
86100
}
87101

88102
/**
@@ -430,9 +444,6 @@ DiskUsage averageUsage(RoutingNode node, ImmutableOpenMap<String, DiskUsage> usa
430444

431445
private static final Decision YES_DISABLED = Decision.single(Decision.Type.YES, NAME, "the disk threshold decider is disabled");
432446

433-
private static final Decision YES_SINGLE_DATA_NODE =
434-
Decision.single(Decision.Type.YES, NAME, "there is only a single data node present");
435-
436447
private static final Decision YES_USAGES_UNAVAILABLE = Decision.single(Decision.Type.YES, NAME, "disk usages are unavailable");
437448

438449
private Decision earlyTerminate(RoutingAllocation allocation, ImmutableOpenMap<String, DiskUsage> usages) {
@@ -441,12 +452,6 @@ private Decision earlyTerminate(RoutingAllocation allocation, ImmutableOpenMap<S
441452
return YES_DISABLED;
442453
}
443454

444-
// Allow allocation regardless if only a single data node is available
445-
if (enableForSingleDataNode == false && allocation.nodes().getDataNodes().size() <= 1) {
446-
logger.trace("only a single data node is present, allowing allocation");
447-
return YES_SINGLE_DATA_NODE;
448-
}
449-
450455
// Fail open if there are no disk usages available
451456
if (usages.isEmpty()) {
452457
logger.trace("unable to determine disk usages for disk-aware allocation, allowing allocation");

server/src/test/java/org/elasticsearch/cluster/routing/allocation/decider/DiskThresholdDeciderTests.java

Lines changed: 26 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.elasticsearch.common.UUIDs;
4545
import org.elasticsearch.common.collect.ImmutableOpenMap;
4646
import org.elasticsearch.common.settings.ClusterSettings;
47+
import org.elasticsearch.common.settings.Setting;
4748
import org.elasticsearch.common.settings.Settings;
4849
import org.elasticsearch.index.Index;
4950
import org.elasticsearch.index.shard.ShardId;
@@ -925,141 +926,15 @@ Settings.EMPTY, new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLU
925926
assertThat(result.routingTable().index("test").getShards().get(1).primaryShard().relocatingNodeId(), equalTo("node2"));
926927
}
927928

928-
public void testForSingleDataNode() {
929-
// remove test in 9.0
930-
Settings diskSettings = Settings.builder()
931-
.put(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_THRESHOLD_ENABLED_SETTING.getKey(), true)
932-
.put(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_LOW_DISK_WATERMARK_SETTING.getKey(), "60%")
933-
.put(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_HIGH_DISK_WATERMARK_SETTING.getKey(), "70%").build();
934-
935-
ImmutableOpenMap.Builder<String, DiskUsage> usagesBuilder = ImmutableOpenMap.builder();
936-
usagesBuilder.put("node1", new DiskUsage("node1", "n1", "/dev/null", 100, 100)); // 0% used
937-
usagesBuilder.put("node2", new DiskUsage("node2", "n2", "/dev/null", 100, 20)); // 80% used
938-
usagesBuilder.put("node3", new DiskUsage("node3", "n3", "/dev/null", 100, 100)); // 0% used
939-
ImmutableOpenMap<String, DiskUsage> usages = usagesBuilder.build();
940-
941-
// We have an index with 1 primary shards each taking 40 bytes. Each node has 100 bytes available
942-
ImmutableOpenMap.Builder<String, Long> shardSizes = ImmutableOpenMap.builder();
943-
shardSizes.put("[test][0][p]", 40L);
944-
shardSizes.put("[test][1][p]", 40L);
945-
final ClusterInfo clusterInfo = new DevNullClusterInfo(usages, usages, shardSizes.build());
946-
947-
DiskThresholdDecider diskThresholdDecider = makeDecider(diskSettings);
948-
Metadata metadata = Metadata.builder()
949-
.put(IndexMetadata.builder("test").settings(settings(Version.CURRENT)).numberOfShards(2).numberOfReplicas(0))
950-
.build();
951-
952-
RoutingTable initialRoutingTable = RoutingTable.builder()
953-
.addAsNew(metadata.index("test"))
954-
.build();
955-
956-
logger.info("--> adding one master node, one data node");
957-
DiscoveryNode discoveryNode1 = new DiscoveryNode("", "node1", buildNewFakeTransportAddress(), emptyMap(),
958-
singleton(DiscoveryNodeRole.MASTER_ROLE), Version.CURRENT);
959-
DiscoveryNode discoveryNode2 = new DiscoveryNode("", "node2", buildNewFakeTransportAddress(), emptyMap(),
960-
singleton(DiscoveryNodeRole.DATA_ROLE), Version.CURRENT);
961-
962-
DiscoveryNodes discoveryNodes = DiscoveryNodes.builder().add(discoveryNode1).add(discoveryNode2).build();
963-
ClusterState baseClusterState = ClusterState.builder(ClusterName.CLUSTER_NAME_SETTING.getDefault(Settings.EMPTY))
964-
.metadata(metadata)
965-
.routingTable(initialRoutingTable)
966-
.nodes(discoveryNodes)
967-
.build();
968-
969-
// Two shards consumes 80% of disk space in data node, but we have only one data node, shards should remain.
970-
ShardRouting firstRouting = TestShardRouting.newShardRouting("test", 0, "node2", null, true, ShardRoutingState.STARTED);
971-
ShardRouting secondRouting = TestShardRouting.newShardRouting("test", 1, "node2", null, true, ShardRoutingState.STARTED);
972-
RoutingNode firstRoutingNode = new RoutingNode("node2", discoveryNode2, firstRouting, secondRouting);
973-
974-
RoutingTable.Builder builder = RoutingTable.builder().add(
975-
IndexRoutingTable.builder(firstRouting.index())
976-
.addIndexShard(new IndexShardRoutingTable.Builder(firstRouting.shardId())
977-
.addShard(firstRouting)
978-
.build()
979-
)
980-
.addIndexShard(new IndexShardRoutingTable.Builder(secondRouting.shardId())
981-
.addShard(secondRouting)
982-
.build()
983-
)
984-
);
985-
ClusterState clusterState = ClusterState.builder(baseClusterState).routingTable(builder.build()).build();
986-
RoutingAllocation routingAllocation = new RoutingAllocation(null, new RoutingNodes(clusterState), clusterState, clusterInfo,
987-
null, System.nanoTime());
988-
routingAllocation.debugDecision(true);
989-
Decision decision = diskThresholdDecider.canRemain(firstRouting, firstRoutingNode, routingAllocation);
990-
991-
// Two shards should start happily
992-
assertThat(decision.type(), equalTo(Decision.Type.YES));
993-
assertThat(decision.getExplanation(), containsString("there is only a single data node present"));
994-
ClusterInfoService cis = () -> {
995-
logger.info("--> calling fake getClusterInfo");
996-
return clusterInfo;
997-
};
998-
999-
AllocationDeciders deciders = new AllocationDeciders(new HashSet<>(Arrays.asList(
1000-
new SameShardAllocationDecider(
1001-
Settings.EMPTY, new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS)
1002-
),
1003-
diskThresholdDecider
1004-
)));
1005-
1006-
AllocationService strategy = new AllocationService(deciders, new TestGatewayAllocator(),
1007-
new BalancedShardsAllocator(Settings.EMPTY), cis, EmptySnapshotsInfoService.INSTANCE);
1008-
ClusterState result = strategy.reroute(clusterState, "reroute");
1009-
1010-
assertThat(result.routingTable().index("test").getShards().get(0).primaryShard().state(), equalTo(STARTED));
1011-
assertThat(result.routingTable().index("test").getShards().get(0).primaryShard().currentNodeId(), equalTo("node2"));
1012-
assertThat(result.routingTable().index("test").getShards().get(0).primaryShard().relocatingNodeId(), nullValue());
1013-
assertThat(result.routingTable().index("test").getShards().get(1).primaryShard().state(), equalTo(STARTED));
1014-
assertThat(result.routingTable().index("test").getShards().get(1).primaryShard().currentNodeId(), equalTo("node2"));
1015-
assertThat(result.routingTable().index("test").getShards().get(1).primaryShard().relocatingNodeId(), nullValue());
1016-
1017-
// Add another datanode, it should relocate.
1018-
logger.info("--> adding node3");
1019-
DiscoveryNode discoveryNode3 = new DiscoveryNode("", "node3", buildNewFakeTransportAddress(), emptyMap(),
1020-
singleton(DiscoveryNodeRole.DATA_ROLE), Version.CURRENT);
1021-
ClusterState updateClusterState = ClusterState.builder(clusterState).nodes(DiscoveryNodes.builder(clusterState.nodes())
1022-
.add(discoveryNode3)).build();
1023-
1024-
firstRouting = TestShardRouting.newShardRouting("test", 0, "node2", null, true, ShardRoutingState.STARTED);
1025-
secondRouting = TestShardRouting.newShardRouting("test", 1, "node2", "node3", true, ShardRoutingState.RELOCATING);
1026-
firstRoutingNode = new RoutingNode("node2", discoveryNode2, firstRouting, secondRouting);
1027-
builder = RoutingTable.builder().add(
1028-
IndexRoutingTable.builder(firstRouting.index())
1029-
.addIndexShard(new IndexShardRoutingTable.Builder(firstRouting.shardId())
1030-
.addShard(firstRouting)
1031-
.build()
1032-
)
1033-
.addIndexShard(new IndexShardRoutingTable.Builder(secondRouting.shardId())
1034-
.addShard(secondRouting)
1035-
.build()
1036-
)
1037-
);
1038-
1039-
clusterState = ClusterState.builder(updateClusterState).routingTable(builder.build()).build();
1040-
routingAllocation = new RoutingAllocation(null, new RoutingNodes(clusterState), clusterState, clusterInfo, null,
1041-
System.nanoTime());
1042-
routingAllocation.debugDecision(true);
1043-
decision = diskThresholdDecider.canRemain(firstRouting, firstRoutingNode, routingAllocation);
1044-
assertThat(decision.type(), equalTo(Decision.Type.YES));
1045-
assertThat(((Decision.Single) decision).getExplanation(), containsString(
1046-
"there is enough disk on this node for the shard to remain, free: [60b]"));
1047-
1048-
result = strategy.reroute(clusterState, "reroute");
1049-
assertThat(result.routingTable().index("test").getShards().get(0).primaryShard().state(), equalTo(STARTED));
1050-
assertThat(result.routingTable().index("test").getShards().get(0).primaryShard().currentNodeId(), equalTo("node2"));
1051-
assertThat(result.routingTable().index("test").getShards().get(0).primaryShard().relocatingNodeId(), nullValue());
1052-
assertThat(result.routingTable().index("test").getShards().get(1).primaryShard().state(), equalTo(RELOCATING));
1053-
assertThat(result.routingTable().index("test").getShards().get(1).primaryShard().currentNodeId(), equalTo("node2"));
1054-
assertThat(result.routingTable().index("test").getShards().get(1).primaryShard().relocatingNodeId(), equalTo("node3"));
1055-
}
1056-
1057929
public void testWatermarksEnabledForSingleDataNode() {
1058-
Settings diskSettings = Settings.builder()
1059-
.put(DiskThresholdDecider.ENABLE_FOR_SINGLE_DATA_NODE.getKey(), true)
930+
Settings.Builder builder = Settings.builder()
1060931
.put(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_THRESHOLD_ENABLED_SETTING.getKey(), true)
1061932
.put(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_LOW_DISK_WATERMARK_SETTING.getKey(), "60%")
1062-
.put(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_HIGH_DISK_WATERMARK_SETTING.getKey(), "70%").build();
933+
.put(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_HIGH_DISK_WATERMARK_SETTING.getKey(), "70%");
934+
if (randomBoolean()) {
935+
builder.put(DiskThresholdDecider.ENABLE_FOR_SINGLE_DATA_NODE.getKey(), true);
936+
}
937+
Settings diskSettings = builder.build();
1063938

1064939
ImmutableOpenMap.Builder<String, DiskUsage> usagesBuilder = ImmutableOpenMap.builder();
1065940
usagesBuilder.put("data", new DiskUsage("data", "data", "/dev/null", 100, 20)); // 80% used
@@ -1131,6 +1006,25 @@ Settings.EMPTY, new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLU
11311006
"the shard cannot remain on this node because it is above the high watermark cluster setting" +
11321007
" [cluster.routing.allocation.disk.watermark.high=70%] and there is less than the required [30.0%] free disk on node," +
11331008
" actual free: [20.0%]"));
1009+
1010+
if (DiskThresholdDecider.ENABLE_FOR_SINGLE_DATA_NODE.exists(diskSettings)) {
1011+
assertSettingDeprecationsAndWarnings(new Setting<?>[] { DiskThresholdDecider.ENABLE_FOR_SINGLE_DATA_NODE });
1012+
}
1013+
}
1014+
1015+
public void testSingleDataNodeDeprecationWarning() {
1016+
Settings settings = Settings.builder()
1017+
.put(DiskThresholdDecider.ENABLE_FOR_SINGLE_DATA_NODE.getKey(), false)
1018+
.build();
1019+
1020+
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
1021+
() -> new DiskThresholdDecider(settings, new ClusterSettings(settings, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS)));
1022+
1023+
assertThat(e.getCause().getMessage(),
1024+
equalTo("setting [cluster.routing.allocation.disk.watermark.enable_for_single_data_node=false] is not allowed," +
1025+
" only true is valid"));
1026+
1027+
assertSettingDeprecationsAndWarnings(new Setting<?>[]{ DiskThresholdDecider.ENABLE_FOR_SINGLE_DATA_NODE });
11341028
}
11351029

11361030
public void testDiskThresholdWithSnapshotShardSizes() {

x-pack/plugin/deprecation/src/main/java/org/elasticsearch/xpack/deprecation/DeprecationChecks.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ private DeprecationChecks() {
3333
Collections.emptyList();
3434

3535
static List<BiFunction<Settings, PluginsAndModules, DeprecationIssue>> NODE_SETTINGS_CHECKS = List.of(
36-
NodeDeprecationChecks::checkSharedDataPathSetting
36+
NodeDeprecationChecks::checkSharedDataPathSetting,
37+
NodeDeprecationChecks::checkSingleDataNodeWatermarkSetting
3738
);
3839

3940
static List<Function<IndexMetadata, DeprecationIssue>> INDEX_SETTINGS_CHECKS = List.of(

x-pack/plugin/deprecation/src/main/java/org/elasticsearch/xpack/deprecation/NodeDeprecationChecks.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package org.elasticsearch.xpack.deprecation;
99

1010
import org.elasticsearch.action.admin.cluster.node.info.PluginsAndModules;
11+
import org.elasticsearch.cluster.routing.allocation.decider.DiskThresholdDecider;
1112
import org.elasticsearch.common.settings.Setting;
1213
import org.elasticsearch.common.settings.Settings;
1314
import org.elasticsearch.env.Environment;
@@ -125,4 +126,18 @@ static DeprecationIssue checkSharedDataPathSetting(final Settings settings, fina
125126
}
126127
return null;
127128
}
129+
130+
static DeprecationIssue checkSingleDataNodeWatermarkSetting(final Settings settings, final PluginsAndModules pluginsAndModules) {
131+
if (DiskThresholdDecider.ENABLE_FOR_SINGLE_DATA_NODE.exists(settings)) {
132+
String key = DiskThresholdDecider.ENABLE_FOR_SINGLE_DATA_NODE.getKey();
133+
return new DeprecationIssue(DeprecationIssue.Level.CRITICAL,
134+
String.format(Locale.ROOT, "setting [%s] is deprecated and will not be available in a future version", key),
135+
"https://www.elastic.co/guide/en/elasticsearch/reference/7.14/" +
136+
"breaking-changes-7.14.html#deprecate-single-data-node-watermark",
137+
String.format(Locale.ROOT, "found [%s] configured. Discontinue use of this setting.", key)
138+
);
139+
}
140+
141+
return null;
142+
}
128143
}

0 commit comments

Comments
 (0)