Skip to content

Commit f78e6ef

Browse files
committed
Short-circuit rebalancing when disabled (#40942)
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.
1 parent af87463 commit f78e6ef

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.Allocation.ALL,
93+
EnableAllocationDecider.Allocation.NEW_PRIMARIES,
94+
EnableAllocationDecider.Allocation.PRIMARIES).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.Allocation.ALL,
133+
EnableAllocationDecider.Allocation.NEW_PRIMARIES,
134+
EnableAllocationDecider.Allocation.PRIMARIES).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)