/* * Licensed to ElasticSearch and Shay Banon under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. ElasticSearch licenses this * file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.elasticsearch.cluster.routing.allocation.allocator; import gnu.trove.map.hash.TObjectIntHashMap; import org.elasticsearch.cluster.routing.MutableShardRouting; import org.elasticsearch.cluster.routing.RoutingNode; import org.elasticsearch.cluster.routing.RoutingNodes; import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.cluster.routing.allocation.FailedRerouteAllocation; import org.elasticsearch.cluster.routing.allocation.RoutingAllocation; import org.elasticsearch.cluster.routing.allocation.StartedRerouteAllocation; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.inject.Inject; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.List; import static org.elasticsearch.cluster.routing.ShardRoutingState.INITIALIZING; import static org.elasticsearch.cluster.routing.ShardRoutingState.STARTED; /** */ public class EvenShardsCountAllocator extends AbstractComponent implements ShardsAllocator { @Inject public EvenShardsCountAllocator(Settings settings) { super(settings); } @Override public void applyStartedShards(StartedRerouteAllocation allocation) { } @Override public void applyFailedShards(FailedRerouteAllocation allocation) { } @Override public boolean allocateUnassigned(RoutingAllocation allocation) { boolean changed = false; RoutingNodes routingNodes = allocation.routingNodes(); RoutingNode[] nodes = sortedNodesLeastToHigh(allocation); Iterator<MutableShardRouting> unassignedIterator = routingNodes.unassigned().iterator(); int lastNode = 0; while (unassignedIterator.hasNext()) { MutableShardRouting shard = unassignedIterator.next(); // do the allocation, finding the least "busy" node for (int i = 0; i < nodes.length; i++) { RoutingNode node = nodes[lastNode]; lastNode++; if (lastNode == nodes.length) { lastNode = 0; } if (allocation.deciders().canAllocate(shard, node, allocation).allocate()) { int numberOfShardsToAllocate = routingNodes.requiredAverageNumberOfShardsPerNode() - node.shards().size(); if (numberOfShardsToAllocate <= 0) { continue; } changed = true; node.add(shard); unassignedIterator.remove(); break; } } } // allocate all the unassigned shards above the average per node. for (Iterator<MutableShardRouting> it = routingNodes.unassigned().iterator(); it.hasNext(); ) { MutableShardRouting shard = it.next(); // go over the nodes and try and allocate the remaining ones for (RoutingNode routingNode : sortedNodesLeastToHigh(allocation)) { if (allocation.deciders().canAllocate(shard, routingNode, allocation).allocate()) { changed = true; routingNode.add(shard); it.remove(); break; } } } return changed; } @Override public boolean rebalance(RoutingAllocation allocation) { boolean changed = false; RoutingNode[] sortedNodesLeastToHigh = sortedNodesLeastToHigh(allocation); if (sortedNodesLeastToHigh.length == 0) { return false; } int lowIndex = 0; int highIndex = sortedNodesLeastToHigh.length - 1; boolean relocationPerformed; do { relocationPerformed = false; while (lowIndex != highIndex) { RoutingNode lowRoutingNode = sortedNodesLeastToHigh[lowIndex]; RoutingNode highRoutingNode = sortedNodesLeastToHigh[highIndex]; int averageNumOfShards = allocation.routingNodes().requiredAverageNumberOfShardsPerNode(); // only active shards can be removed so must count only active ones. if (highRoutingNode.numberOfOwningShards() <= averageNumOfShards) { highIndex--; continue; } if (lowRoutingNode.shards().size() >= averageNumOfShards) { lowIndex++; continue; } boolean relocated = false; List<MutableShardRouting> startedShards = highRoutingNode.shardsWithState(STARTED); for (MutableShardRouting startedShard : startedShards) { if (!allocation.deciders().canRebalance(startedShard, allocation)) { continue; } if (allocation.deciders().canAllocate(startedShard, lowRoutingNode, allocation).allocate()) { changed = true; lowRoutingNode.add(new MutableShardRouting(startedShard.index(), startedShard.id(), lowRoutingNode.nodeId(), startedShard.currentNodeId(), startedShard.primary(), INITIALIZING, startedShard.version() + 1)); startedShard.relocate(lowRoutingNode.nodeId()); relocated = true; relocationPerformed = true; break; } } if (!relocated) { highIndex--; } } } while (relocationPerformed); return changed; } @Override public boolean move(MutableShardRouting shardRouting, RoutingNode node, RoutingAllocation allocation) { if (!shardRouting.started()) { return false; } boolean changed = false; RoutingNode[] sortedNodesLeastToHigh = sortedNodesLeastToHigh(allocation); if (sortedNodesLeastToHigh.length == 0) { return false; } for (RoutingNode nodeToCheck : sortedNodesLeastToHigh) { // check if its the node we are moving from, no sense to check on it if (nodeToCheck.nodeId().equals(node.nodeId())) { continue; } if (allocation.deciders().canAllocate(shardRouting, nodeToCheck, allocation).allocate()) { nodeToCheck.add(new MutableShardRouting(shardRouting.index(), shardRouting.id(), nodeToCheck.nodeId(), shardRouting.currentNodeId(), shardRouting.primary(), INITIALIZING, shardRouting.version() + 1)); shardRouting.relocate(nodeToCheck.nodeId()); changed = true; break; } } return changed; } private RoutingNode[] sortedNodesLeastToHigh(RoutingAllocation allocation) { // create count per node id, taking into account relocations final TObjectIntHashMap<String> nodeCounts = new TObjectIntHashMap<String>(); for (RoutingNode node : allocation.routingNodes()) { for (int i = 0; i < node.shards().size(); i++) { ShardRouting shardRouting = node.shards().get(i); String nodeId = shardRouting.relocating() ? shardRouting.relocatingNodeId() : shardRouting.currentNodeId(); nodeCounts.adjustOrPutValue(nodeId, 1, 1); } } RoutingNode[] nodes = allocation.routingNodes().nodesToShards().values().toArray(new RoutingNode[allocation.routingNodes().nodesToShards().values().size()]); Arrays.sort(nodes, new Comparator<RoutingNode>() { @Override public int compare(RoutingNode o1, RoutingNode o2) { return nodeCounts.get(o1.nodeId()) - nodeCounts.get(o2.nodeId()); } }); return nodes; } }