/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.blur.manager.indexserver;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.apache.blur.log.Log;
import org.apache.blur.log.LogFactory;
public class MasterBasedLeveler {
private static final Log LOG = LogFactory.getLog(MasterBasedLeveler.class);
public static int level(int totalShards, int totalShardServers, Map<String, Integer> onlineServerShardCount,
Map<String, String> newLayoutMap, String table, Random random) {
List<Entry<String, Integer>> onlineServerShardCountList = new ArrayList<Map.Entry<String, Integer>>(
onlineServerShardCount.entrySet());
Collections.sort(onlineServerShardCountList, new Comparator<Entry<String, Integer>>() {
@Override
public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) {
int value1 = o1.getValue();
int value2 = o2.getValue();
if (value1 == value2) {
return 0;
}
return value1 < value2 ? -1 : 1;
}
});
float opt = totalShards / (float) totalShardServers;
Set<String> overAllocatedSet = new HashSet<String>();
Set<String> underAllocatedSet = new HashSet<String>();
LOG.debug("Optimum server shard count [{0}] for table [{1}]", opt, table);
for (Entry<String, Integer> e : onlineServerShardCountList) {
int countInt = e.getValue();
float count = countInt;
String server = e.getKey();
if (isNotInOptBalance(opt, count)) {
if (count > opt) {
LOG.debug("Level server [{0}] over allocated at [{1}]", server, e.getValue());
overAllocatedSet.add(server);
} else {
LOG.debug("Level server [{0}] under allocated at [{1}]", server, e.getValue());
underAllocatedSet.add(server);
}
}
}
Map<String, SortedSet<String>> serverToShards = new HashMap<String, SortedSet<String>>();
for (Entry<String, String> e : newLayoutMap.entrySet()) {
String server = e.getValue();
SortedSet<String> shards = serverToShards.get(server);
if (shards == null) {
shards = new TreeSet<String>();
serverToShards.put(server, shards);
}
String shard = e.getKey();
shards.add(shard);
}
int moves = 0;
while (!underAllocatedSet.isEmpty() && !overAllocatedSet.isEmpty()) {
String overAllocatedServer = getFirst(overAllocatedSet);
String underAllocatedServer = getFirst(underAllocatedSet);
LOG.debug("Over allocated server [{0}] under allocated server [{1}]", overAllocatedServer, underAllocatedServer);
moveSingleShard(overAllocatedServer, underAllocatedServer, opt, overAllocatedSet, underAllocatedSet,
newLayoutMap, onlineServerShardCount, serverToShards, table);
moves++;
}
if (overAllocatedSet.size() > 0) {
int count = (int) opt;
LOG.debug("There are still [{0}] over allocated servers for table [{1}]", overAllocatedSet.size(), table);
while (!overAllocatedSet.isEmpty()) {
String overAllocatedServer = getFirst(overAllocatedSet);
String underAllocatedServer = findServerWithCount(onlineServerShardCount, count, table, random);
LOG.debug("Over allocated server [{0}] under allocated server [{1}]", overAllocatedServer, underAllocatedServer);
moveSingleShard(overAllocatedServer, underAllocatedServer, opt, overAllocatedSet, underAllocatedSet,
newLayoutMap, onlineServerShardCount, serverToShards, table);
moves++;
}
}
if (underAllocatedSet.size() > 0) {
int count = (int) Math.ceil(opt);
LOG.info("There are still [{0}] under allocated servers for table [{1}]", underAllocatedSet.size(), table);
while (!underAllocatedSet.isEmpty()) {
String overAllocatedServer = findServerWithCount(onlineServerShardCount, count, table, random);
String underAllocatedServer = getFirst(underAllocatedSet);
LOG.debug("Over allocated server [{0}] under allocated server [{1}]", overAllocatedServer, underAllocatedServer);
moveSingleShard(overAllocatedServer, underAllocatedServer, opt, overAllocatedSet, underAllocatedSet,
newLayoutMap, onlineServerShardCount, serverToShards, table);
moves++;
}
}
return moves;
}
private static String findServerWithCount(Map<String, Integer> onlineServerShardCount, int count, String table,
Random random) {
LOG.debug("Looking for server with shard count [{0}] for table [{1}]", count, table);
List<Entry<String, Integer>> list = new ArrayList<Entry<String, Integer>>(onlineServerShardCount.entrySet());
Collections.shuffle(list, random);
for (Entry<String, Integer> e : list) {
int serverCount = e.getValue();
if (serverCount == count) {
return e.getKey();
}
}
throw new RuntimeException("This should never happen");
}
private static boolean isNotInOptBalance(float opt, float count) {
return Math.abs(count - opt) >= 1.0f;
}
private static String getFirst(Set<String> set) {
return set.iterator().next();
}
private static void moveSingleShard(String srcServer, String distServer, float opt,
Set<String> overAllocatedServerSet, Set<String> underAllocatedServerSet, Map<String, String> newLayoutMap,
Map<String, Integer> onlineServerShardCount, Map<String, SortedSet<String>> serverToShards, String table) {
SortedSet<String> srcShards = serverToShards.get(srcServer);
if (srcShards == null) {
srcShards = new TreeSet<String>();
serverToShards.put(srcServer, srcShards);
}
SortedSet<String> distShards = serverToShards.get(distServer);
if (distShards == null) {
distShards = new TreeSet<String>();
serverToShards.put(distServer, distShards);
}
LOG.debug("Source server shard list for table [{0}] is [{1}]", table, srcShards);
LOG.debug("Destination server shard list for table [{0}] is [{1}]", table, distShards);
String srcShard = getFirst(srcShards);
LOG.debug("Moving shard [{0}] from [{1}] to [{2}] for table [{3}]", srcShard, srcServer, distServer, table);
srcShards.remove(srcShard);
distShards.add(srcShard);
if (!isNotInOptBalance(opt, srcShards.size())) {
LOG.debug("Source server [{0}] is in balance with size [{1}] optimum size [{2}]", srcServer, srcShards.size(),
opt);
overAllocatedServerSet.remove(srcServer);
underAllocatedServerSet.remove(srcServer);
}
if (!isNotInOptBalance(opt, distShards.size())) {
LOG.debug("Source server [{0}] is in balance with size [{1}] optimum size [{2}]", distServer, distShards.size(),
opt);
overAllocatedServerSet.remove(distServer);
underAllocatedServerSet.remove(distServer);
}
newLayoutMap.put(srcShard, distServer);
decr(onlineServerShardCount, srcServer);
incr(onlineServerShardCount, distServer);
}
private static void incr(Map<String, Integer> map, String key) {
Integer i = map.get(key);
if (i == null) {
map.put(key, 1);
} else {
map.put(key, i + 1);
}
}
private static void decr(Map<String, Integer> map, String key) {
Integer i = map.get(key);
if (i == null) {
map.put(key, 0);
} else {
int value = i - 1;
if (value < 0) {
value = 0;
}
map.put(key, value);
}
}
}