Skip to content

Commit 2e21a11

Browse files
authored
Short-circuit rebalancing when disabled (elastic#40966)
Today if `cluster.routing.rebalance.enable: none` then rebalancing is disabled, but we still execute `balanceByWeights()` and perform some rather expensive calculations before discovering that we cannot rebalance any shards. In a large cluster this can make cluster state updates occur rather slowly. With this change we check earlier whether rebalancing is globally disabled and, if so, avoid the rebalancing process entirely. Relates elastic#40942 which was reverted because of egregiously faulty tests.
1 parent 529eda3 commit 2e21a11

File tree

2 files changed

+260
-3
lines changed

2 files changed

+260
-3
lines changed

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

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,21 @@ public EnableAllocationDecider(Settings settings, ClusterSettings clusterSetting
8686
clusterSettings.addSettingsUpdateConsumer(CLUSTER_ROUTING_REBALANCE_ENABLE_SETTING, this::setEnableRebalance);
8787
}
8888

89-
public void setEnableRebalance(Rebalance enableRebalance) {
89+
private void setEnableRebalance(Rebalance enableRebalance) {
9090
this.enableRebalance = enableRebalance;
9191
}
9292

93-
public void setEnableAllocation(Allocation enableAllocation) {
93+
private void setEnableAllocation(Allocation enableAllocation) {
9494
this.enableAllocation = enableAllocation;
9595
}
9696

9797
@Override
9898
public Decision canAllocate(ShardRouting shardRouting, RoutingNode node, RoutingAllocation allocation) {
99+
return canAllocate(shardRouting, allocation);
100+
}
101+
102+
@Override
103+
public Decision canAllocate(ShardRouting shardRouting, RoutingAllocation allocation) {
99104
if (allocation.ignoreDisable()) {
100105
return allocation.decision(Decision.YES, NAME,
101106
"explicitly ignoring any disabling of allocation due to manual allocation commands via the reroute API");
@@ -136,10 +141,29 @@ public Decision canAllocate(ShardRouting shardRouting, RoutingNode node, Routing
136141
}
137142
}
138143

144+
@Override
145+
public Decision canRebalance(RoutingAllocation allocation) {
146+
if (allocation.ignoreDisable()) {
147+
return allocation.decision(Decision.YES, NAME, "allocation is explicitly ignoring any disabling of rebalancing");
148+
}
149+
150+
if (enableRebalance == Rebalance.NONE) {
151+
for (IndexMetaData indexMetaData : allocation.metaData()) {
152+
if (INDEX_ROUTING_REBALANCE_ENABLE_SETTING.exists(indexMetaData.getSettings())
153+
&& INDEX_ROUTING_REBALANCE_ENABLE_SETTING.get(indexMetaData.getSettings()) != Rebalance.NONE) {
154+
return allocation.decision(Decision.YES, NAME, "rebalancing is permitted on one or more indices");
155+
}
156+
}
157+
return allocation.decision(Decision.NO, NAME, "no rebalancing is allowed due to %s", setting(enableRebalance, false));
158+
}
159+
160+
return allocation.decision(Decision.YES, NAME, "rebalancing is not globally disabled");
161+
}
162+
139163
@Override
140164
public Decision canRebalance(ShardRouting shardRouting, RoutingAllocation allocation) {
141165
if (allocation.ignoreDisable()) {
142-
return allocation.decision(Decision.YES, NAME, "allocation is explicitly ignoring any disabling of relocation");
166+
return allocation.decision(Decision.YES, NAME, "allocation is explicitly ignoring any disabling of rebalancing");
143167
}
144168

145169
Settings indexSettings = allocation.metaData().getIndexSafe(shardRouting.index()).getSettings();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
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+
package org.elasticsearch.cluster.routing.allocation.decider;
20+
21+
import org.elasticsearch.Version;
22+
import org.elasticsearch.cluster.ClusterModule;
23+
import org.elasticsearch.cluster.ClusterName;
24+
import org.elasticsearch.cluster.ClusterState;
25+
import org.elasticsearch.cluster.ESAllocationTestCase;
26+
import org.elasticsearch.cluster.EmptyClusterInfoService;
27+
import org.elasticsearch.cluster.metadata.IndexMetaData;
28+
import org.elasticsearch.cluster.metadata.MetaData;
29+
import org.elasticsearch.cluster.node.DiscoveryNodes;
30+
import org.elasticsearch.cluster.routing.RoutingNode;
31+
import org.elasticsearch.cluster.routing.RoutingTable;
32+
import org.elasticsearch.cluster.routing.ShardRouting;
33+
import org.elasticsearch.cluster.routing.ShardRoutingState;
34+
import org.elasticsearch.cluster.routing.allocation.AllocationService;
35+
import org.elasticsearch.cluster.routing.allocation.RoutingAllocation;
36+
import org.elasticsearch.cluster.routing.allocation.allocator.BalancedShardsAllocator;
37+
import org.elasticsearch.common.settings.ClusterSettings;
38+
import org.elasticsearch.common.settings.Settings;
39+
import org.elasticsearch.plugins.ClusterPlugin;
40+
import org.elasticsearch.test.gateway.TestGatewayAllocator;
41+
42+
import java.util.ArrayList;
43+
import java.util.Collection;
44+
import java.util.Collections;
45+
import java.util.List;
46+
47+
import static org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider.CLUSTER_ROUTING_ALLOCATION_ENABLE_SETTING;
48+
import static org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider.CLUSTER_ROUTING_REBALANCE_ENABLE_SETTING;
49+
import static org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider.INDEX_ROUTING_REBALANCE_ENABLE_SETTING;
50+
import static org.hamcrest.Matchers.equalTo;
51+
import static org.hamcrest.Matchers.greaterThan;
52+
53+
public class EnableAllocationShortCircuitTests extends ESAllocationTestCase {
54+
55+
private static ClusterState createClusterStateWithAllShardsAssigned() {
56+
AllocationService allocationService = createAllocationService(Settings.EMPTY);
57+
58+
final int numberOfNodes = randomIntBetween(1, 5);
59+
final DiscoveryNodes.Builder discoveryNodesBuilder = DiscoveryNodes.builder();
60+
for (int i = 0; i < numberOfNodes; i++) {
61+
discoveryNodesBuilder.add(newNode("node" + i));
62+
}
63+
64+
final MetaData.Builder metadataBuilder = MetaData.builder();
65+
final RoutingTable.Builder routingTableBuilder = RoutingTable.builder();
66+
for (int i = randomIntBetween(1, 10); i >= 0; i--) {
67+
final IndexMetaData indexMetaData = IndexMetaData.builder("test" + i).settings(settings(Version.CURRENT))
68+
.numberOfShards(1).numberOfReplicas(randomIntBetween(0, numberOfNodes - 1)).build();
69+
metadataBuilder.put(indexMetaData, true);
70+
routingTableBuilder.addAsNew(indexMetaData);
71+
}
72+
73+
ClusterState clusterState = ClusterState.builder(ClusterName.CLUSTER_NAME_SETTING.get(Settings.EMPTY))
74+
.nodes(discoveryNodesBuilder).metaData(metadataBuilder).routingTable(routingTableBuilder.build()).build();
75+
76+
while (clusterState.getRoutingNodes().hasUnassignedShards()
77+
|| clusterState.getRoutingNodes().shardsWithState(ShardRoutingState.INITIALIZING).isEmpty() == false) {
78+
clusterState = allocationService.applyStartedShards(clusterState,
79+
clusterState.getRoutingNodes().shardsWithState(ShardRoutingState.INITIALIZING));
80+
clusterState = allocationService.reroute(clusterState, "reroute");
81+
}
82+
83+
return clusterState;
84+
}
85+
86+
public void testRebalancingAttemptedIfPermitted() {
87+
ClusterState clusterState = createClusterStateWithAllShardsAssigned();
88+
89+
final RebalanceShortCircuitPlugin plugin = new RebalanceShortCircuitPlugin();
90+
AllocationService allocationService = createAllocationService(Settings.builder()
91+
.put(CLUSTER_ROUTING_REBALANCE_ENABLE_SETTING.getKey(),
92+
randomFrom(EnableAllocationDecider.Rebalance.ALL,
93+
EnableAllocationDecider.Rebalance.PRIMARIES,
94+
EnableAllocationDecider.Rebalance.REPLICAS).name()),
95+
plugin);
96+
allocationService.reroute(clusterState, "reroute").routingTable();
97+
assertThat(plugin.rebalanceAttempts, greaterThan(0));
98+
}
99+
100+
public void testRebalancingSkippedIfDisabled() {
101+
ClusterState clusterState = createClusterStateWithAllShardsAssigned();
102+
103+
final RebalanceShortCircuitPlugin plugin = new RebalanceShortCircuitPlugin();
104+
AllocationService allocationService = createAllocationService(Settings.builder()
105+
.put(CLUSTER_ROUTING_REBALANCE_ENABLE_SETTING.getKey(), EnableAllocationDecider.Allocation.NONE.name()),
106+
plugin);
107+
allocationService.reroute(clusterState, "reroute").routingTable();
108+
assertThat(plugin.rebalanceAttempts, equalTo(0));
109+
}
110+
111+
public void testRebalancingSkippedIfDisabledIncludingOnSpecificIndices() {
112+
ClusterState clusterState = createClusterStateWithAllShardsAssigned();
113+
final IndexMetaData indexMetaData = randomFrom(clusterState.metaData().indices().values().toArray(IndexMetaData.class));
114+
clusterState = ClusterState.builder(clusterState).metaData(MetaData.builder(clusterState.metaData())
115+
.put(IndexMetaData.builder(indexMetaData).settings(Settings.builder().put(indexMetaData.getSettings())
116+
.put(INDEX_ROUTING_REBALANCE_ENABLE_SETTING.getKey(), EnableAllocationDecider.Rebalance.NONE.name()))).build()).build();
117+
118+
final RebalanceShortCircuitPlugin plugin = new RebalanceShortCircuitPlugin();
119+
AllocationService allocationService = createAllocationService(Settings.builder()
120+
.put(CLUSTER_ROUTING_REBALANCE_ENABLE_SETTING.getKey(), EnableAllocationDecider.Rebalance.NONE.name()),
121+
plugin);
122+
allocationService.reroute(clusterState, "reroute").routingTable();
123+
assertThat(plugin.rebalanceAttempts, equalTo(0));
124+
}
125+
126+
public void testRebalancingAttemptedIfDisabledButOverridenOnSpecificIndices() {
127+
ClusterState clusterState = createClusterStateWithAllShardsAssigned();
128+
final IndexMetaData indexMetaData = randomFrom(clusterState.metaData().indices().values().toArray(IndexMetaData.class));
129+
clusterState = ClusterState.builder(clusterState).metaData(MetaData.builder(clusterState.metaData())
130+
.put(IndexMetaData.builder(indexMetaData).settings(Settings.builder().put(indexMetaData.getSettings())
131+
.put(INDEX_ROUTING_REBALANCE_ENABLE_SETTING.getKey(),
132+
randomFrom(EnableAllocationDecider.Rebalance.ALL,
133+
EnableAllocationDecider.Rebalance.PRIMARIES,
134+
EnableAllocationDecider.Rebalance.REPLICAS).name()))).build()).build();
135+
136+
final RebalanceShortCircuitPlugin plugin = new RebalanceShortCircuitPlugin();
137+
AllocationService allocationService = createAllocationService(Settings.builder()
138+
.put(CLUSTER_ROUTING_REBALANCE_ENABLE_SETTING.getKey(), EnableAllocationDecider.Rebalance.NONE.name()),
139+
plugin);
140+
allocationService.reroute(clusterState, "reroute").routingTable();
141+
assertThat(plugin.rebalanceAttempts, greaterThan(0));
142+
}
143+
144+
public void testAllocationSkippedIfDisabled() {
145+
final AllocateShortCircuitPlugin plugin = new AllocateShortCircuitPlugin();
146+
AllocationService allocationService = createAllocationService(Settings.builder()
147+
.put(CLUSTER_ROUTING_ALLOCATION_ENABLE_SETTING.getKey(), EnableAllocationDecider.Allocation.NONE.name()),
148+
plugin);
149+
150+
MetaData metaData = MetaData.builder()
151+
.put(IndexMetaData.builder("test").settings(settings(Version.CURRENT)).numberOfShards(1).numberOfReplicas(0))
152+
.build();
153+
154+
RoutingTable routingTable = RoutingTable.builder()
155+
.addAsNew(metaData.index("test"))
156+
.build();
157+
158+
ClusterState clusterState = ClusterState.builder(ClusterName.CLUSTER_NAME_SETTING.getDefault(Settings.EMPTY))
159+
.metaData(metaData).routingTable(routingTable).nodes(DiscoveryNodes.builder().add(newNode("node1"))).build();
160+
161+
allocationService.reroute(clusterState, "reroute").routingTable();
162+
assertThat(plugin.canAllocateAttempts, equalTo(0));
163+
}
164+
165+
private static AllocationService createAllocationService(Settings.Builder settings, ClusterPlugin plugin) {
166+
final ClusterSettings emptyClusterSettings = new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS);
167+
List<AllocationDecider> deciders = new ArrayList<>(ClusterModule.createAllocationDeciders(settings.build(), emptyClusterSettings,
168+
Collections.singletonList(plugin)));
169+
return new MockAllocationService(
170+
new AllocationDeciders(deciders),
171+
new TestGatewayAllocator(), new BalancedShardsAllocator(Settings.EMPTY), EmptyClusterInfoService.INSTANCE);
172+
}
173+
174+
private static class RebalanceShortCircuitPlugin implements ClusterPlugin {
175+
int rebalanceAttempts;
176+
177+
@Override
178+
public Collection<AllocationDecider> createAllocationDeciders(Settings settings, ClusterSettings clusterSettings) {
179+
return Collections.singletonList(new RebalanceShortCircuitAllocationDecider());
180+
}
181+
182+
private class RebalanceShortCircuitAllocationDecider extends AllocationDecider {
183+
184+
@Override
185+
public Decision canRebalance(ShardRouting shardRouting, RoutingAllocation allocation) {
186+
rebalanceAttempts++;
187+
return super.canRebalance(shardRouting, allocation);
188+
}
189+
190+
@Override
191+
public Decision canRebalance(RoutingAllocation allocation) {
192+
rebalanceAttempts++;
193+
return super.canRebalance(allocation);
194+
}
195+
}
196+
}
197+
198+
private static class AllocateShortCircuitPlugin implements ClusterPlugin {
199+
int canAllocateAttempts;
200+
201+
@Override
202+
public Collection<AllocationDecider> createAllocationDeciders(Settings settings, ClusterSettings clusterSettings) {
203+
return Collections.singletonList(new AllocateShortCircuitAllocationDecider());
204+
}
205+
206+
private class AllocateShortCircuitAllocationDecider extends AllocationDecider {
207+
208+
@Override
209+
public Decision canAllocate(ShardRouting shardRouting, RoutingNode node, RoutingAllocation allocation) {
210+
canAllocateAttempts++;
211+
return super.canAllocate(shardRouting, node, allocation);
212+
}
213+
214+
@Override
215+
public Decision canAllocate(ShardRouting shardRouting, RoutingAllocation allocation) {
216+
canAllocateAttempts++;
217+
return super.canAllocate(shardRouting, allocation);
218+
}
219+
220+
@Override
221+
public Decision canAllocate(IndexMetaData indexMetaData, RoutingNode node, RoutingAllocation allocation) {
222+
canAllocateAttempts++;
223+
return super.canAllocate(indexMetaData, node, allocation);
224+
}
225+
226+
@Override
227+
public Decision canAllocate(RoutingNode node, RoutingAllocation allocation) {
228+
canAllocateAttempts++;
229+
return super.canAllocate(node, allocation);
230+
}
231+
}
232+
}
233+
}

0 commit comments

Comments
 (0)