View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  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, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.master.balancer;
19  
20  import com.google.common.annotations.VisibleForTesting;
21  import java.util.ArrayDeque;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.Deque;
27  import java.util.HashMap;
28  import java.util.LinkedList;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Map.Entry;
32  import java.util.Random;
33  
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.hadoop.hbase.classification.InterfaceAudience;
37  import org.apache.hadoop.conf.Configuration;
38  import org.apache.hadoop.hbase.ClusterStatus;
39  import org.apache.hadoop.hbase.HBaseInterfaceAudience;
40  import org.apache.hadoop.hbase.HConstants;
41  import org.apache.hadoop.hbase.HRegionInfo;
42  import org.apache.hadoop.hbase.RegionLoad;
43  import org.apache.hadoop.hbase.ServerLoad;
44  import org.apache.hadoop.hbase.ServerName;
45  import org.apache.hadoop.hbase.TableName;
46  import org.apache.hadoop.hbase.master.MasterServices;
47  import org.apache.hadoop.hbase.master.RegionPlan;
48  import org.apache.hadoop.hbase.master.balancer.BaseLoadBalancer.Cluster.Action;
49  import org.apache.hadoop.hbase.master.balancer.BaseLoadBalancer.Cluster.Action.Type;
50  import org.apache.hadoop.hbase.master.balancer.BaseLoadBalancer.Cluster.AssignRegionAction;
51  import org.apache.hadoop.hbase.master.balancer.BaseLoadBalancer.Cluster.LocalityType;
52  import org.apache.hadoop.hbase.master.balancer.BaseLoadBalancer.Cluster.MoveRegionAction;
53  import org.apache.hadoop.hbase.master.balancer.BaseLoadBalancer.Cluster.SwapRegionsAction;
54  import org.apache.hadoop.hbase.util.Bytes;
55  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
56  
57  import com.google.common.base.Optional;
58  
59  /**
60   * <p>This is a best effort load balancer. Given a Cost function F(C) =&gt; x It will
61   * randomly try and mutate the cluster to Cprime. If F(Cprime) &lt; F(C) then the
62   * new cluster state becomes the plan. It includes costs functions to compute the cost of:</p>
63   * <ul>
64   * <li>Region Load</li>
65   * <li>Table Load</li>
66   * <li>Data Locality</li>
67   * <li>Memstore Sizes</li>
68   * <li>Storefile Sizes</li>
69   * </ul>
70   *
71   *
72   * <p>Every cost function returns a number between 0 and 1 inclusive; where 0 is the lowest cost
73   * best solution, and 1 is the highest possible cost and the worst solution.  The computed costs are
74   * scaled by their respective multipliers:</p>
75   *
76   * <ul>
77   *   <li>hbase.master.balancer.stochastic.regionLoadCost</li>
78   *   <li>hbase.master.balancer.stochastic.moveCost</li>
79   *   <li>hbase.master.balancer.stochastic.tableLoadCost</li>
80   *   <li>hbase.master.balancer.stochastic.localityCost</li>
81   *   <li>hbase.master.balancer.stochastic.memstoreSizeCost</li>
82   *   <li>hbase.master.balancer.stochastic.storefileSizeCost</li>
83   * </ul>
84   *
85   * <p>In addition to the above configurations, the balancer can be tuned by the following
86   * configuration values:</p>
87   * <ul>
88   *   <li>hbase.master.balancer.stochastic.maxMoveRegions which
89   *   controls what the max number of regions that can be moved in a single invocation of this
90   *   balancer.</li>
91   *   <li>hbase.master.balancer.stochastic.stepsPerRegion is the coefficient by which the number of
92   *   regions is multiplied to try and get the number of times the balancer will
93   *   mutate all servers.</li>
94   *   <li>hbase.master.balancer.stochastic.maxSteps which controls the maximum number of times that
95   *   the balancer will try and mutate all the servers. The balancer will use the minimum of this
96   *   value and the above computation.</li>
97   * </ul>
98   *
99   * <p>This balancer is best used with hbase.master.loadbalance.bytable set to false
100  * so that the balancer gets the full picture of all loads on the cluster.</p>
101  */
102 @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
103 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="IS2_INCONSISTENT_SYNC",
104   justification="Complaint is about costFunctions not being synchronized; not end of the world")
105 public class StochasticLoadBalancer extends BaseLoadBalancer {
106 
107   protected static final String STEPS_PER_REGION_KEY =
108       "hbase.master.balancer.stochastic.stepsPerRegion";
109   protected static final String MAX_STEPS_KEY =
110       "hbase.master.balancer.stochastic.maxSteps";
111   protected static final String MAX_RUNNING_TIME_KEY =
112       "hbase.master.balancer.stochastic.maxRunningTime";
113   protected static final String KEEP_REGION_LOADS =
114       "hbase.master.balancer.stochastic.numRegionLoadsToRemember";
115   private static final String TABLE_FUNCTION_SEP = "_";
116   protected static final String MIN_COST_NEED_BALANCE_KEY =
117       "hbase.master.balancer.stochastic.minCostNeedBalance";
118 
119   private static final Random RANDOM = new Random(System.currentTimeMillis());
120   private static final Log LOG = LogFactory.getLog(StochasticLoadBalancer.class);
121 
122   Map<String, Deque<RegionLoad>> loads = new HashMap<String, Deque<RegionLoad>>();
123 
124   // values are defaults
125   private int maxSteps = 1000000;
126   private int stepsPerRegion = 800;
127   private long maxRunningTime = 30 * 1000 * 1; // 30 seconds.
128   private int numRegionLoadsToRemember = 15;
129   private float minCostNeedBalance = 0.05f;
130 
131   private CandidateGenerator[] candidateGenerators;
132   private CostFromRegionLoadFunction[] regionLoadFunctions;
133   private CostFunction[] costFunctions; // FindBugs: Wants this protected; IS2_INCONSISTENT_SYNC
134 
135   // to save and report costs to JMX
136   private Double curOverallCost = 0d;
137   private Double[] tempFunctionCosts;
138   private Double[] curFunctionCosts;
139 
140   // Keep locality based picker and cost function to alert them
141   // when new services are offered
142   private LocalityBasedCandidateGenerator localityCandidateGenerator;
143   private ServerLocalityCostFunction localityCost;
144   private RackLocalityCostFunction rackLocalityCost;
145   private RegionReplicaHostCostFunction regionReplicaHostCostFunction;
146   private RegionReplicaRackCostFunction regionReplicaRackCostFunction;
147   private boolean isByTable = false;
148   private TableName tableName = null;
149 
150   /**
151    * The constructor that pass a MetricsStochasticBalancer to BaseLoadBalancer to replace its
152    * default MetricsBalancer
153    */
154   public StochasticLoadBalancer() {
155     super(new MetricsStochasticBalancer());
156   }
157 
158   @Override
159   public void onConfigurationChange(Configuration conf) {
160     setConf(conf);
161   }
162 
163   @Override
164   public synchronized void setConf(Configuration conf) {
165     super.setConf(conf);
166     LOG.info("loading config");
167 
168     maxSteps = conf.getInt(MAX_STEPS_KEY, maxSteps);
169 
170     stepsPerRegion = conf.getInt(STEPS_PER_REGION_KEY, stepsPerRegion);
171     maxRunningTime = conf.getLong(MAX_RUNNING_TIME_KEY, maxRunningTime);
172 
173     numRegionLoadsToRemember = conf.getInt(KEEP_REGION_LOADS, numRegionLoadsToRemember);
174     isByTable = conf.getBoolean(HConstants.HBASE_MASTER_LOADBALANCE_BYTABLE, isByTable);
175 
176     minCostNeedBalance = conf.getFloat(MIN_COST_NEED_BALANCE_KEY, minCostNeedBalance);
177 
178     if (localityCandidateGenerator == null) {
179       localityCandidateGenerator = new LocalityBasedCandidateGenerator(services);
180     }
181     localityCost = new ServerLocalityCostFunction(conf, services);
182     rackLocalityCost = new RackLocalityCostFunction(conf, services);
183 
184     if (candidateGenerators == null) {
185       candidateGenerators = new CandidateGenerator[] {
186           new RandomCandidateGenerator(),
187           new LoadCandidateGenerator(),
188           localityCandidateGenerator,
189           new RegionReplicaRackCandidateGenerator(),
190       };
191     }
192 
193     regionLoadFunctions = new CostFromRegionLoadFunction[] {
194       new ReadRequestCostFunction(conf),
195       new WriteRequestCostFunction(conf),
196       new MemstoreSizeCostFunction(conf),
197       new StoreFileCostFunction(conf)
198     };
199 
200     regionReplicaHostCostFunction = new RegionReplicaHostCostFunction(conf);
201     regionReplicaRackCostFunction = new RegionReplicaRackCostFunction(conf);
202 
203     costFunctions = new CostFunction[]{
204       new RegionCountSkewCostFunction(conf),
205       new PrimaryRegionCountSkewCostFunction(conf),
206       new MoveCostFunction(conf),
207       localityCost,
208       rackLocalityCost,
209       new TableSkewCostFunction(conf),
210       regionReplicaHostCostFunction,
211       regionReplicaRackCostFunction,
212       regionLoadFunctions[0],
213       regionLoadFunctions[1],
214       regionLoadFunctions[2],
215       regionLoadFunctions[3],
216     };
217 
218     curFunctionCosts= new Double[costFunctions.length];
219     tempFunctionCosts= new Double[costFunctions.length];
220 
221   }
222 
223   @Override
224   protected void setSlop(Configuration conf) {
225     this.slop = conf.getFloat("hbase.regions.slop", 0.001F);
226   }
227 
228   @Override
229   public synchronized void setClusterStatus(ClusterStatus st) {
230     super.setClusterStatus(st);
231     updateRegionLoad();
232     for(CostFromRegionLoadFunction cost : regionLoadFunctions) {
233       cost.setClusterStatus(st);
234     }
235 
236     // update metrics size
237     try {
238       // by-table or ensemble mode
239       int tablesCount = isByTable ? services.getTableDescriptors().getAll().size() : 1;
240       int functionsCount = getCostFunctionNames().length;
241 
242       updateMetricsSize(tablesCount * (functionsCount + 1)); // +1 for overall
243     } catch (Exception e) {
244       LOG.error("failed to get the size of all tables", e);
245     }
246   }
247 
248   /**
249    * Update the number of metrics that are reported to JMX
250    */
251   public void updateMetricsSize(int size) {
252     if (metricsBalancer instanceof MetricsStochasticBalancer) {
253         ((MetricsStochasticBalancer) metricsBalancer).updateMetricsSize(size);
254     }
255   }
256 
257   @Override
258   public synchronized void setMasterServices(MasterServices masterServices) {
259     super.setMasterServices(masterServices);
260     this.localityCost.setServices(masterServices);
261     this.rackLocalityCost.setServices(masterServices);
262     this.localityCandidateGenerator.setServices(masterServices);
263   }
264 
265   @Override
266   protected synchronized boolean areSomeRegionReplicasColocated(Cluster c) {
267     regionReplicaHostCostFunction.init(c);
268     if (regionReplicaHostCostFunction.cost() > 0) return true;
269     regionReplicaRackCostFunction.init(c);
270     if (regionReplicaRackCostFunction.cost() > 0) return true;
271     return false;
272   }
273 
274   @Override
275   protected boolean needsBalance(Cluster cluster) {
276     ClusterLoadState cs = new ClusterLoadState(cluster.clusterState);
277     if (cs.getNumServers() < MIN_SERVER_BALANCE) {
278       if (LOG.isDebugEnabled()) {
279         LOG.debug("Not running balancer because only " + cs.getNumServers()
280             + " active regionserver(s)");
281       }
282       return false;
283     }
284     if (areSomeRegionReplicasColocated(cluster)) {
285       return true;
286     }
287 
288     double total = 0.0;
289     float sumMultiplier = 0.0f;
290     for (CostFunction c : costFunctions) {
291       float multiplier = c.getMultiplier();
292       if (multiplier <= 0) {
293         continue;
294       }
295       if (!c.isNeeded()) {
296         LOG.debug(c.getClass().getName() + " indicated that its cost should not be considered");
297         continue;
298       }
299       sumMultiplier += multiplier;
300       total += c.cost() * multiplier;
301     }
302 
303     if (total <= 0 || sumMultiplier <= 0
304         || (sumMultiplier > 0 && (total / sumMultiplier) < minCostNeedBalance)) {
305       LOG.info("Skipping load balancing because balanced cluster; " + "total cost is " + total
306           + ", sum multiplier is " + sumMultiplier + " min cost which need balance is "
307           + minCostNeedBalance);
308       return false;
309     }
310     return true;
311   }
312 
313   @Override
314   public synchronized List<RegionPlan> balanceCluster(TableName tableName, Map<ServerName,
315     List<HRegionInfo>> clusterState) {
316     this.tableName = tableName;
317     return balanceCluster(clusterState);
318   }
319 
320   @VisibleForTesting
321   Cluster.Action nextAction(Cluster cluster) {
322     return candidateGenerators[(RANDOM.nextInt(candidateGenerators.length))]
323             .generate(cluster);
324   }
325 
326   /**
327    * Given the cluster state this will try and approach an optimal balance. This
328    * should always approach the optimal state given enough steps.
329    */
330   @Override
331   public synchronized List<RegionPlan> balanceCluster(Map<ServerName,
332     List<HRegionInfo>> clusterState) {
333     List<RegionPlan> plans = balanceMasterRegions(clusterState);
334     if (plans != null || clusterState == null || clusterState.size() <= 1) {
335       return plans;
336     }
337 
338     if (masterServerName != null && clusterState.containsKey(masterServerName)) {
339       if (clusterState.size() <= 2) {
340         return null;
341       }
342       clusterState = new HashMap<ServerName, List<HRegionInfo>>(clusterState);
343       clusterState.remove(masterServerName);
344     }
345 
346     // On clusters with lots of HFileLinks or lots of reference files,
347     // instantiating the storefile infos can be quite expensive.
348     // Allow turning this feature off if the locality cost is not going to
349     // be used in any computations.
350     RegionLocationFinder finder = null;
351     if ((this.localityCost != null && this.localityCost.getMultiplier() > 0)
352         || (this.rackLocalityCost != null && this.rackLocalityCost.getMultiplier() > 0)) {
353       finder = this.regionFinder;
354     }
355 
356     //The clusterState that is given to this method contains the state
357     //of all the regions in the table(s) (that's true today)
358     // Keep track of servers to iterate through them.
359     Cluster cluster = new Cluster(clusterState, loads, finder, rackManager);
360 
361     long startTime = EnvironmentEdgeManager.currentTime();
362 
363     initCosts(cluster);
364 
365     if (!needsBalance(cluster)) {
366       return null;
367     }
368 
369     double currentCost = computeCost(cluster, Double.MAX_VALUE);
370     curOverallCost = currentCost;
371     for (int i = 0; i < this.curFunctionCosts.length; i++) {
372       curFunctionCosts[i] = tempFunctionCosts[i];
373     }
374     LOG.info("start StochasticLoadBalancer.balancer, initCost=" + currentCost + ", functionCost="
375         + functionCost());
376 
377     double initCost = currentCost;
378     double newCost = currentCost;
379 
380     long computedMaxSteps = Math.min(this.maxSteps,
381         ((long)cluster.numRegions * (long)this.stepsPerRegion * (long)cluster.numServers));
382     // Perform a stochastic walk to see if we can get a good fit.
383     long step;
384 
385     for (step = 0; step < computedMaxSteps; step++) {
386       Cluster.Action action = nextAction(cluster);
387 
388       if (action.type == Type.NULL) {
389         continue;
390       }
391 
392       cluster.doAction(action);
393       updateCostsWithAction(cluster, action);
394 
395       newCost = computeCost(cluster, currentCost);
396 
397       // Should this be kept?
398       if (newCost < currentCost) {
399         currentCost = newCost;
400 
401         // save for JMX
402         curOverallCost = currentCost;
403         for (int i = 0; i < this.curFunctionCosts.length; i++) {
404           curFunctionCosts[i] = tempFunctionCosts[i];
405         }
406       } else {
407         // Put things back the way they were before.
408         // TODO: undo by remembering old values
409         Action undoAction = action.undoAction();
410         cluster.doAction(undoAction);
411         updateCostsWithAction(cluster, undoAction);
412       }
413 
414       if (EnvironmentEdgeManager.currentTime() - startTime >
415           maxRunningTime) {
416         break;
417       }
418     }
419     long endTime = EnvironmentEdgeManager.currentTime();
420 
421     metricsBalancer.balanceCluster(endTime - startTime);
422 
423     // update costs metrics
424     updateStochasticCosts(tableName, curOverallCost, curFunctionCosts);
425     if (initCost > currentCost) {
426       plans = createRegionPlans(cluster);
427       if (LOG.isDebugEnabled()) {
428         LOG.debug("Finished computing new load balance plan.  Computation took "
429             + (endTime - startTime) + "ms to try " + step
430             + " different iterations.  Found a solution that moves "
431             + plans.size() + " regions; Going from a computed cost of "
432             + initCost + " to a new cost of " + currentCost);
433       }
434 
435       return plans;
436     }
437     if (LOG.isDebugEnabled()) {
438       LOG.debug("Could not find a better load balance plan.  Tried "
439           + step + " different configurations in " + (endTime - startTime)
440           + "ms, and did not find anything with a computed cost less than " + initCost);
441     }
442     return null;
443   }
444 
445   /**
446    * update costs to JMX
447    */
448   private void updateStochasticCosts(TableName tableName, Double overall, Double[] subCosts) {
449     if (tableName == null) return;
450 
451     // check if the metricsBalancer is MetricsStochasticBalancer before casting
452     if (metricsBalancer instanceof MetricsStochasticBalancer) {
453       MetricsStochasticBalancer balancer = (MetricsStochasticBalancer) metricsBalancer;
454       // overall cost
455       balancer.updateStochasticCost(tableName.getNameAsString(),
456         "Overall", "Overall cost", overall);
457 
458       // each cost function
459       for (int i = 0; i < costFunctions.length; i++) {
460         CostFunction costFunction = costFunctions[i];
461         String costFunctionName = costFunction.getClass().getSimpleName();
462         Double costPercent = (overall == 0) ? 0 : (subCosts[i] / overall);
463         // TODO: cost function may need a specific description
464         balancer.updateStochasticCost(tableName.getNameAsString(), costFunctionName,
465           "The percent of " + costFunctionName, costPercent);
466       }
467     }
468   }
469 
470   private String functionCost() {
471     StringBuilder builder = new StringBuilder();
472     for (CostFunction c:costFunctions) {
473       builder.append(c.getClass().getSimpleName());
474       builder.append(" : (");
475       builder.append(c.getMultiplier());
476       builder.append(", ");
477       builder.append(c.cost());
478       builder.append("); ");
479     }
480     return builder.toString();
481   }
482 
483   /**
484    * Create all of the RegionPlan's needed to move from the initial cluster state to the desired
485    * state.
486    *
487    * @param cluster The state of the cluster
488    * @return List of RegionPlan's that represent the moves needed to get to desired final state.
489    */
490   private List<RegionPlan> createRegionPlans(Cluster cluster) {
491     List<RegionPlan> plans = new LinkedList<RegionPlan>();
492     for (int regionIndex = 0;
493          regionIndex < cluster.regionIndexToServerIndex.length; regionIndex++) {
494       int initialServerIndex = cluster.initialRegionIndexToServerIndex[regionIndex];
495       int newServerIndex = cluster.regionIndexToServerIndex[regionIndex];
496 
497       if (initialServerIndex != newServerIndex) {
498         HRegionInfo region = cluster.regions[regionIndex];
499         ServerName initialServer = cluster.servers[initialServerIndex];
500         ServerName newServer = cluster.servers[newServerIndex];
501 
502         if (LOG.isTraceEnabled()) {
503           LOG.trace("Moving Region " + region.getEncodedName() + " from server "
504               + initialServer.getHostname() + " to " + newServer.getHostname());
505         }
506         RegionPlan rp = new RegionPlan(region, initialServer, newServer);
507         plans.add(rp);
508       }
509     }
510     return plans;
511   }
512 
513   /**
514    * Store the current region loads.
515    */
516   private synchronized void updateRegionLoad() {
517     // We create a new hashmap so that regions that are no longer there are removed.
518     // However we temporarily need the old loads so we can use them to keep the rolling average.
519     Map<String, Deque<RegionLoad>> oldLoads = loads;
520     loads = new HashMap<String, Deque<RegionLoad>>();
521 
522     for (ServerName sn : clusterStatus.getServers()) {
523       ServerLoad sl = clusterStatus.getLoad(sn);
524       if (sl == null) {
525         continue;
526       }
527       for (Entry<byte[], RegionLoad> entry : sl.getRegionsLoad().entrySet()) {
528         Deque<RegionLoad> rLoads = oldLoads.get(Bytes.toString(entry.getKey()));
529         if (rLoads == null) {
530           // There was nothing there
531           rLoads = new ArrayDeque<RegionLoad>();
532         } else if (rLoads.size() >= numRegionLoadsToRemember) {
533           rLoads.remove();
534         }
535         rLoads.add(entry.getValue());
536         loads.put(Bytes.toString(entry.getKey()), rLoads);
537 
538       }
539     }
540 
541     for(CostFromRegionLoadFunction cost : regionLoadFunctions) {
542       cost.setLoads(loads);
543     }
544   }
545 
546   protected void initCosts(Cluster cluster) {
547     for (CostFunction c:costFunctions) {
548       c.init(cluster);
549     }
550   }
551 
552   protected void updateCostsWithAction(Cluster cluster, Action action) {
553     for (CostFunction c : costFunctions) {
554       c.postAction(action);
555     }
556   }
557 
558   /**
559    * Get the names of the cost functions
560    */
561   public String[] getCostFunctionNames() {
562     if (costFunctions == null) return null;
563     String[] ret = new String[costFunctions.length];
564     for (int i = 0; i < costFunctions.length; i++) {
565       CostFunction c = costFunctions[i];
566       ret[i] = c.getClass().getSimpleName();
567     }
568 
569     return ret;
570   }
571 
572   /**
573    * This is the main cost function.  It will compute a cost associated with a proposed cluster
574    * state.  All different costs will be combined with their multipliers to produce a double cost.
575    *
576    * @param cluster The state of the cluster
577    * @param previousCost the previous cost. This is used as an early out.
578    * @return a double of a cost associated with the proposed cluster state.  This cost is an
579    *         aggregate of all individual cost functions.
580    */
581   protected double computeCost(Cluster cluster, double previousCost) {
582     double total = 0;
583 
584     for (int i = 0; i < costFunctions.length; i++) {
585       CostFunction c = costFunctions[i];
586       this.tempFunctionCosts[i] = 0.0;
587 
588       if (c.getMultiplier() <= 0) {
589         continue;
590       }
591 
592       Float multiplier = c.getMultiplier();
593       Double cost = c.cost();
594 
595       this.tempFunctionCosts[i] = multiplier*cost;
596       total += this.tempFunctionCosts[i];
597 
598       if (total > previousCost) {
599         break;
600       }
601     }
602 
603     return total;
604   }
605 
606   /** Generates a candidate action to be applied to the cluster for cost function search */
607   abstract static class CandidateGenerator {
608     abstract Cluster.Action generate(Cluster cluster);
609 
610     /**
611      * From a list of regions pick a random one. Null can be returned which
612      * {@link StochasticLoadBalancer#balanceCluster(Map)} recognize as signal to try a region move
613      * rather than swap.
614      *
615      * @param cluster        The state of the cluster
616      * @param server         index of the server
617      * @param chanceOfNoSwap Chance that this will decide to try a move rather
618      *                       than a swap.
619      * @return a random {@link HRegionInfo} or null if an asymmetrical move is
620      *         suggested.
621      */
622     protected int pickRandomRegion(Cluster cluster, int server, double chanceOfNoSwap) {
623       // Check to see if this is just a move.
624       if (cluster.regionsPerServer[server].length == 0 || RANDOM.nextFloat() < chanceOfNoSwap) {
625         // signal a move only.
626         return -1;
627       }
628       int rand = RANDOM.nextInt(cluster.regionsPerServer[server].length);
629       return cluster.regionsPerServer[server][rand];
630 
631     }
632     protected int pickRandomServer(Cluster cluster) {
633       if (cluster.numServers < 1) {
634         return -1;
635       }
636 
637       return RANDOM.nextInt(cluster.numServers);
638     }
639 
640     protected int pickRandomRack(Cluster cluster) {
641       if (cluster.numRacks < 1) {
642         return -1;
643       }
644 
645       return RANDOM.nextInt(cluster.numRacks);
646     }
647 
648     protected int pickOtherRandomServer(Cluster cluster, int serverIndex) {
649       if (cluster.numServers < 2) {
650         return -1;
651       }
652       while (true) {
653         int otherServerIndex = pickRandomServer(cluster);
654         if (otherServerIndex != serverIndex) {
655           return otherServerIndex;
656         }
657       }
658     }
659 
660     protected int pickOtherRandomRack(Cluster cluster, int rackIndex) {
661       if (cluster.numRacks < 2) {
662         return -1;
663       }
664       while (true) {
665         int otherRackIndex = pickRandomRack(cluster);
666         if (otherRackIndex != rackIndex) {
667           return otherRackIndex;
668         }
669       }
670     }
671 
672     protected Cluster.Action pickRandomRegions(Cluster cluster,
673                                                        int thisServer,
674                                                        int otherServer) {
675       if (thisServer < 0 || otherServer < 0) {
676         return Cluster.NullAction;
677       }
678 
679       // Decide who is most likely to need another region
680       int thisRegionCount = cluster.getNumRegions(thisServer);
681       int otherRegionCount = cluster.getNumRegions(otherServer);
682 
683       // Assign the chance based upon the above
684       double thisChance = (thisRegionCount > otherRegionCount) ? 0 : 0.5;
685       double otherChance = (thisRegionCount <= otherRegionCount) ? 0 : 0.5;
686 
687       int thisRegion = pickRandomRegion(cluster, thisServer, thisChance);
688       int otherRegion = pickRandomRegion(cluster, otherServer, otherChance);
689 
690       return getAction(thisServer, thisRegion, otherServer, otherRegion);
691     }
692 
693     protected Cluster.Action getAction(int fromServer, int fromRegion,
694         int toServer, int toRegion) {
695       if (fromServer < 0 || toServer < 0) {
696         return Cluster.NullAction;
697       }
698       if (fromRegion > 0 && toRegion > 0) {
699         return new Cluster.SwapRegionsAction(fromServer, fromRegion,
700           toServer, toRegion);
701       } else if (fromRegion > 0) {
702         return new Cluster.MoveRegionAction(fromRegion, fromServer, toServer);
703       } else if (toRegion > 0) {
704         return new Cluster.MoveRegionAction(toRegion, toServer, fromServer);
705       } else {
706         return Cluster.NullAction;
707       }
708     }
709 
710     /**
711      * Returns a random iteration order of indexes of an array with size length
712      */
713     protected List<Integer> getRandomIterationOrder(int length) {
714       ArrayList<Integer> order = new ArrayList<>(length);
715       for (int i = 0; i < length; i++) {
716         order.add(i);
717       }
718       Collections.shuffle(order);
719       return order;
720     }
721   }
722 
723   static class RandomCandidateGenerator extends CandidateGenerator {
724 
725     @Override
726     Cluster.Action generate(Cluster cluster) {
727 
728       int thisServer = pickRandomServer(cluster);
729 
730       // Pick the other server
731       int otherServer = pickOtherRandomServer(cluster, thisServer);
732 
733       return pickRandomRegions(cluster, thisServer, otherServer);
734     }
735   }
736 
737   static class LoadCandidateGenerator extends CandidateGenerator {
738 
739     @Override
740     Cluster.Action generate(Cluster cluster) {
741       cluster.sortServersByRegionCount();
742       int thisServer = pickMostLoadedServer(cluster, -1);
743       int otherServer = pickLeastLoadedServer(cluster, thisServer);
744 
745       return pickRandomRegions(cluster, thisServer, otherServer);
746     }
747 
748     private int pickLeastLoadedServer(final Cluster cluster, int thisServer) {
749       Integer[] servers = cluster.serverIndicesSortedByRegionCount;
750 
751       int index = 0;
752       while (servers[index] == null || servers[index] == thisServer) {
753         index++;
754         if (index == servers.length) {
755           return -1;
756         }
757       }
758       return servers[index];
759     }
760 
761     private int pickMostLoadedServer(final Cluster cluster, int thisServer) {
762       Integer[] servers = cluster.serverIndicesSortedByRegionCount;
763 
764       int index = servers.length - 1;
765       while (servers[index] == null || servers[index] == thisServer) {
766         index--;
767         if (index < 0) {
768           return -1;
769         }
770       }
771       return servers[index];
772     }
773   }
774 
775   static class LocalityBasedCandidateGenerator extends CandidateGenerator {
776 
777     private MasterServices masterServices;
778 
779     LocalityBasedCandidateGenerator(MasterServices masterServices) {
780       this.masterServices = masterServices;
781     }
782 
783     @Override
784     Cluster.Action generate(Cluster cluster) {
785       if (this.masterServices == null) {
786         int thisServer = pickRandomServer(cluster);
787         // Pick the other server
788         int otherServer = pickOtherRandomServer(cluster, thisServer);
789         return pickRandomRegions(cluster, thisServer, otherServer);
790       }
791 
792       // Randomly iterate through regions until you find one that is not on ideal host
793       for (int region : getRandomIterationOrder(cluster.numRegions)) {
794         int currentServer = cluster.regionIndexToServerIndex[region];
795         if (currentServer != cluster.getOrComputeRegionsToMostLocalEntities(LocalityType.SERVER)[region]) {
796           Optional<Action> potential = tryMoveOrSwap(
797               cluster,
798               currentServer,
799               region,
800               cluster.getOrComputeRegionsToMostLocalEntities(LocalityType.SERVER)[region]
801           );
802           if (potential.isPresent()) {
803             return potential.get();
804           }
805         }
806       }
807       return Cluster.NullAction;
808     }
809 
810     /**
811      * Try to generate a move/swap fromRegion between fromServer and toServer such that locality is improved.
812      * Returns empty optional if no move can be found
813      */
814     private Optional<Action> tryMoveOrSwap(Cluster cluster,
815                                            int fromServer,
816                                            int fromRegion,
817                                            int toServer) {
818       // Try move first. We know apriori fromRegion has the highest locality on toServer
819       if (cluster.serverHasTooFewRegions(toServer)) {
820         return Optional.of(getAction(fromServer, fromRegion, toServer, -1));
821       }
822 
823       // Compare locality gain/loss from swapping fromRegion with regions on toServer
824       double fromRegionLocalityDelta =
825           getWeightedLocality(cluster, fromRegion, toServer) - getWeightedLocality(cluster, fromRegion, fromServer);
826       for (int toRegionIndex : getRandomIterationOrder(cluster.regionsPerServer[toServer].length)) {
827         int toRegion = cluster.regionsPerServer[toServer][toRegionIndex];
828         double toRegionLocalityDelta =
829             getWeightedLocality(cluster, toRegion, fromServer) - getWeightedLocality(cluster, toRegion, toServer);
830         // If locality would remain neutral or improve, attempt the swap
831         if (fromRegionLocalityDelta + toRegionLocalityDelta >= 0) {
832           return Optional.of(getAction(fromServer, fromRegion, toServer, toRegion));
833         }
834       }
835 
836       return Optional.absent();
837     }
838 
839     private double getWeightedLocality(Cluster cluster, int region, int server) {
840       return cluster.getOrComputeWeightedLocality(region, server, LocalityType.SERVER);
841     }
842 
843     void setServices(MasterServices services) {
844       this.masterServices = services;
845     }
846   }
847 
848   /**
849    * Generates candidates which moves the replicas out of the region server for
850    * co-hosted region replicas
851    */
852   static class RegionReplicaCandidateGenerator extends CandidateGenerator {
853 
854     RandomCandidateGenerator randomGenerator = new RandomCandidateGenerator();
855 
856     /**
857      * Randomly select one regionIndex out of all region replicas co-hosted in the same group
858      * (a group is a server, host or rack)
859      * @param primariesOfRegionsPerGroup either Cluster.primariesOfRegionsPerServer,
860      * primariesOfRegionsPerHost or primariesOfRegionsPerRack
861      * @param regionsPerGroup either Cluster.regionsPerServer, regionsPerHost or regionsPerRack
862      * @param regionIndexToPrimaryIndex Cluster.regionsIndexToPrimaryIndex
863      * @return a regionIndex for the selected primary or -1 if there is no co-locating
864      */
865     int selectCoHostedRegionPerGroup(int[] primariesOfRegionsPerGroup, int[] regionsPerGroup
866         , int[] regionIndexToPrimaryIndex) {
867       int currentPrimary = -1;
868       int currentPrimaryIndex = -1;
869       int selectedPrimaryIndex = -1;
870       double currentLargestRandom = -1;
871       // primariesOfRegionsPerGroup is a sorted array. Since it contains the primary region
872       // ids for the regions hosted in server, a consecutive repetition means that replicas
873       // are co-hosted
874       for (int j = 0; j <= primariesOfRegionsPerGroup.length; j++) {
875         int primary = j < primariesOfRegionsPerGroup.length
876             ? primariesOfRegionsPerGroup[j] : -1;
877         if (primary != currentPrimary) { // check for whether we see a new primary
878           int numReplicas = j - currentPrimaryIndex;
879           if (numReplicas > 1) { // means consecutive primaries, indicating co-location
880             // decide to select this primary region id or not
881             double currentRandom = RANDOM.nextDouble();
882             // we don't know how many region replicas are co-hosted, we will randomly select one
883             // using reservoir sampling (http://gregable.com/2007/10/reservoir-sampling.html)
884             if (currentRandom > currentLargestRandom) {
885               selectedPrimaryIndex = currentPrimary;
886               currentLargestRandom = currentRandom;
887             }
888           }
889           currentPrimary = primary;
890           currentPrimaryIndex = j;
891         }
892       }
893 
894       // we have found the primary id for the region to move. Now find the actual regionIndex
895       // with the given primary, prefer to move the secondary region.
896       for (int j = 0; j < regionsPerGroup.length; j++) {
897         int regionIndex = regionsPerGroup[j];
898         if (selectedPrimaryIndex == regionIndexToPrimaryIndex[regionIndex]) {
899           // always move the secondary, not the primary
900           if (selectedPrimaryIndex != regionIndex) {
901             return regionIndex;
902           }
903         }
904       }
905       return -1;
906     }
907 
908     @Override
909     Cluster.Action generate(Cluster cluster) {
910       int serverIndex = pickRandomServer(cluster);
911       if (cluster.numServers <= 1 || serverIndex == -1) {
912         return Cluster.NullAction;
913       }
914 
915       int regionIndex = selectCoHostedRegionPerGroup(
916         cluster.primariesOfRegionsPerServer[serverIndex],
917         cluster.regionsPerServer[serverIndex],
918         cluster.regionIndexToPrimaryIndex);
919 
920       // if there are no pairs of region replicas co-hosted, default to random generator
921       if (regionIndex == -1) {
922         // default to randompicker
923         return randomGenerator.generate(cluster);
924       }
925 
926       int toServerIndex = pickOtherRandomServer(cluster, serverIndex);
927       int toRegionIndex = pickRandomRegion(cluster, toServerIndex, 0.9f);
928       return getAction(serverIndex, regionIndex, toServerIndex, toRegionIndex);
929     }
930   }
931 
932   /**
933    * Generates candidates which moves the replicas out of the rack for
934    * co-hosted region replicas in the same rack
935    */
936   static class RegionReplicaRackCandidateGenerator extends RegionReplicaCandidateGenerator {
937     @Override
938     Cluster.Action generate(Cluster cluster) {
939       int rackIndex = pickRandomRack(cluster);
940       if (cluster.numRacks <= 1 || rackIndex == -1) {
941         return super.generate(cluster);
942       }
943 
944       int regionIndex = selectCoHostedRegionPerGroup(
945         cluster.primariesOfRegionsPerRack[rackIndex],
946         cluster.regionsPerRack[rackIndex],
947         cluster.regionIndexToPrimaryIndex);
948 
949       // if there are no pairs of region replicas co-hosted, default to random generator
950       if (regionIndex == -1) {
951         // default to randompicker
952         return randomGenerator.generate(cluster);
953       }
954 
955       int serverIndex = cluster.regionIndexToServerIndex[regionIndex];
956       int toRackIndex = pickOtherRandomRack(cluster, rackIndex);
957 
958       int rand = RANDOM.nextInt(cluster.serversPerRack[toRackIndex].length);
959       int toServerIndex = cluster.serversPerRack[toRackIndex][rand];
960       int toRegionIndex = pickRandomRegion(cluster, toServerIndex, 0.9f);
961       return getAction(serverIndex, regionIndex, toServerIndex, toRegionIndex);
962     }
963   }
964 
965   /**
966    * Base class of StochasticLoadBalancer's Cost Functions.
967    */
968   abstract static class CostFunction {
969 
970     private float multiplier = 0;
971 
972     protected Cluster cluster;
973 
974     CostFunction(Configuration c) {
975     }
976 
977     boolean isNeeded() {
978       return true;
979     }
980     float getMultiplier() {
981       return multiplier;
982     }
983 
984     void setMultiplier(float m) {
985       this.multiplier = m;
986     }
987 
988     /** Called once per LB invocation to give the cost function
989      * to initialize it's state, and perform any costly calculation.
990      */
991     void init(Cluster cluster) {
992       this.cluster = cluster;
993     }
994 
995     /** Called once per cluster Action to give the cost function
996      * an opportunity to update it's state. postAction() is always
997      * called at least once before cost() is called with the cluster
998      * that this action is performed on. */
999     void postAction(Action action) {
1000       switch (action.type) {
1001       case NULL: break;
1002       case ASSIGN_REGION:
1003         AssignRegionAction ar = (AssignRegionAction) action;
1004         regionMoved(ar.region, -1, ar.server);
1005         break;
1006       case MOVE_REGION:
1007         MoveRegionAction mra = (MoveRegionAction) action;
1008         regionMoved(mra.region, mra.fromServer, mra.toServer);
1009         break;
1010       case SWAP_REGIONS:
1011         SwapRegionsAction a = (SwapRegionsAction) action;
1012         regionMoved(a.fromRegion, a.fromServer, a.toServer);
1013         regionMoved(a.toRegion, a.toServer, a.fromServer);
1014         break;
1015       default:
1016         throw new RuntimeException("Uknown action:" + action.type);
1017       }
1018     }
1019 
1020     protected void regionMoved(int region, int oldServer, int newServer) {
1021     }
1022 
1023     abstract double cost();
1024 
1025     /**
1026      * Function to compute a scaled cost using {@link DescriptiveStatistics}. It
1027      * assumes that this is a zero sum set of costs.  It assumes that the worst case
1028      * possible is all of the elements in one region server and the rest having 0.
1029      *
1030      * @param stats the costs
1031      * @return a scaled set of costs.
1032      */
1033     protected double costFromArray(double[] stats) {
1034       double totalCost = 0;
1035       double total = getSum(stats);
1036 
1037       double count = stats.length;
1038       double mean = total/count;
1039 
1040       // Compute max as if all region servers had 0 and one had the sum of all costs.  This must be
1041       // a zero sum cost for this to make sense.
1042       double max = ((count - 1) * mean) + (total - mean);
1043 
1044       // It's possible that there aren't enough regions to go around
1045       double min;
1046       if (count > total) {
1047         min = ((count - total) * mean) + ((1 - mean) * total);
1048       } else {
1049         // Some will have 1 more than everything else.
1050         int numHigh = (int) (total - (Math.floor(mean) * count));
1051         int numLow = (int) (count - numHigh);
1052 
1053         min = (numHigh * (Math.ceil(mean) - mean)) + (numLow * (mean - Math.floor(mean)));
1054 
1055       }
1056       min = Math.max(0, min);
1057       for (int i=0; i<stats.length; i++) {
1058         double n = stats[i];
1059         double diff = Math.abs(mean - n);
1060         totalCost += diff;
1061       }
1062 
1063       double scaled =  scale(min, max, totalCost);
1064       return scaled;
1065     }
1066 
1067     private double getSum(double[] stats) {
1068       double total = 0;
1069       for(double s:stats) {
1070         total += s;
1071       }
1072       return total;
1073     }
1074 
1075     /**
1076      * Scale the value between 0 and 1.
1077      *
1078      * @param min   Min value
1079      * @param max   The Max value
1080      * @param value The value to be scaled.
1081      * @return The scaled value.
1082      */
1083     protected double scale(double min, double max, double value) {
1084       if (max <= min || value <= min) {
1085         return 0;
1086       }
1087       if ((max - min) == 0) return 0;
1088 
1089       return Math.max(0d, Math.min(1d, (value - min) / (max - min)));
1090     }
1091   }
1092 
1093   /**
1094    * Given the starting state of the regions and a potential ending state
1095    * compute cost based upon the number of regions that have moved.
1096    */
1097   static class MoveCostFunction extends CostFunction {
1098     private static final String MOVE_COST_KEY = "hbase.master.balancer.stochastic.moveCost";
1099     private static final String MAX_MOVES_PERCENT_KEY =
1100         "hbase.master.balancer.stochastic.maxMovePercent";
1101     private static final float DEFAULT_MOVE_COST = 7;
1102     private static final int DEFAULT_MAX_MOVES = 600;
1103     private static final float DEFAULT_MAX_MOVE_PERCENT = 0.25f;
1104 
1105     private final float maxMovesPercent;
1106 
1107     MoveCostFunction(Configuration conf) {
1108       super(conf);
1109 
1110       // Move cost multiplier should be the same cost or higher than the rest of the costs to ensure
1111       // that large benefits are need to overcome the cost of a move.
1112       this.setMultiplier(conf.getFloat(MOVE_COST_KEY, DEFAULT_MOVE_COST));
1113       // What percent of the number of regions a single run of the balancer can move.
1114       maxMovesPercent = conf.getFloat(MAX_MOVES_PERCENT_KEY, DEFAULT_MAX_MOVE_PERCENT);
1115     }
1116 
1117     @Override
1118     double cost() {
1119       // Try and size the max number of Moves, but always be prepared to move some.
1120       int maxMoves = Math.max((int) (cluster.numRegions * maxMovesPercent),
1121           DEFAULT_MAX_MOVES);
1122 
1123       double moveCost = cluster.numMovedRegions;
1124 
1125       // Don't let this single balance move more than the max moves.
1126       // This allows better scaling to accurately represent the actual cost of a move.
1127       if (moveCost > maxMoves) {
1128         return 1000000;   // return a number much greater than any of the other cost
1129       }
1130 
1131       return scale(0, Math.min(cluster.numRegions, maxMoves), moveCost);
1132     }
1133   }
1134 
1135   /**
1136    * Compute the cost of a potential cluster state from skew in number of
1137    * regions on a cluster.
1138    */
1139   static class RegionCountSkewCostFunction extends CostFunction {
1140     private static final String REGION_COUNT_SKEW_COST_KEY =
1141         "hbase.master.balancer.stochastic.regionCountCost";
1142     private static final float DEFAULT_REGION_COUNT_SKEW_COST = 500;
1143 
1144     private double[] stats = null;
1145 
1146     RegionCountSkewCostFunction(Configuration conf) {
1147       super(conf);
1148       // Load multiplier should be the greatest as it is the most general way to balance data.
1149       this.setMultiplier(conf.getFloat(REGION_COUNT_SKEW_COST_KEY, DEFAULT_REGION_COUNT_SKEW_COST));
1150     }
1151 
1152     @Override
1153     double cost() {
1154       if (stats == null || stats.length != cluster.numServers) {
1155         stats = new double[cluster.numServers];
1156       }
1157 
1158       for (int i =0; i < cluster.numServers; i++) {
1159         stats[i] = cluster.regionsPerServer[i].length;
1160       }
1161 
1162       return costFromArray(stats);
1163     }
1164   }
1165 
1166   /**
1167    * Compute the cost of a potential cluster state from skew in number of
1168    * primary regions on a cluster.
1169    */
1170   static class PrimaryRegionCountSkewCostFunction extends CostFunction {
1171     private static final String PRIMARY_REGION_COUNT_SKEW_COST_KEY =
1172         "hbase.master.balancer.stochastic.primaryRegionCountCost";
1173     private static final float DEFAULT_PRIMARY_REGION_COUNT_SKEW_COST = 500;
1174 
1175     private double[] stats = null;
1176 
1177     PrimaryRegionCountSkewCostFunction(Configuration conf) {
1178       super(conf);
1179       // Load multiplier should be the greatest as primary regions serve majority of reads/writes.
1180       this.setMultiplier(conf.getFloat(PRIMARY_REGION_COUNT_SKEW_COST_KEY,
1181         DEFAULT_PRIMARY_REGION_COUNT_SKEW_COST));
1182     }
1183 
1184     @Override
1185     double cost() {
1186       if (!cluster.hasRegionReplicas) {
1187         return 0;
1188       }
1189       if (stats == null || stats.length != cluster.numServers) {
1190         stats = new double[cluster.numServers];
1191       }
1192 
1193       for (int i =0; i < cluster.numServers; i++) {
1194         stats[i] = 0;
1195         for (int regionIdx : cluster.regionsPerServer[i]) {
1196           if (regionIdx == cluster.regionIndexToPrimaryIndex[regionIdx]) {
1197             stats[i] ++;
1198           }
1199         }
1200       }
1201 
1202       return costFromArray(stats);
1203     }
1204   }
1205 
1206   /**
1207    * Compute the cost of a potential cluster configuration based upon how evenly
1208    * distributed tables are.
1209    */
1210   static class TableSkewCostFunction extends CostFunction {
1211 
1212     private static final String TABLE_SKEW_COST_KEY =
1213         "hbase.master.balancer.stochastic.tableSkewCost";
1214     private static final float DEFAULT_TABLE_SKEW_COST = 35;
1215 
1216     TableSkewCostFunction(Configuration conf) {
1217       super(conf);
1218       this.setMultiplier(conf.getFloat(TABLE_SKEW_COST_KEY, DEFAULT_TABLE_SKEW_COST));
1219     }
1220 
1221     @Override
1222     double cost() {
1223       double max = cluster.numRegions;
1224       double min = ((double) cluster.numRegions) / cluster.numServers;
1225       double value = 0;
1226 
1227       for (int i = 0; i < cluster.numMaxRegionsPerTable.length; i++) {
1228         value += cluster.numMaxRegionsPerTable[i];
1229       }
1230 
1231       return scale(min, max, value);
1232     }
1233   }
1234 
1235   /**
1236    * Compute a cost of a potential cluster configuration based upon where
1237    * {@link org.apache.hadoop.hbase.regionserver.StoreFile}s are located.
1238    */
1239   static abstract class LocalityBasedCostFunction extends CostFunction {
1240 
1241     private final LocalityType type;
1242 
1243     private double bestLocality; // best case locality across cluster weighted by local data size
1244     private double locality; // current locality across cluster weighted by local data size
1245 
1246     private MasterServices services;
1247 
1248     LocalityBasedCostFunction(Configuration conf,
1249                               MasterServices srv,
1250                               LocalityType type,
1251                               String localityCostKey,
1252                               float defaultLocalityCost) {
1253       super(conf);
1254       this.type = type;
1255       this.setMultiplier(conf.getFloat(localityCostKey, defaultLocalityCost));
1256       this.services = srv;
1257       this.locality = 0.0;
1258       this.bestLocality = 0.0;
1259     }
1260 
1261     /**
1262      * Maps region to the current entity (server or rack) on which it is stored
1263      */
1264     abstract int regionIndexToEntityIndex(int region);
1265 
1266     public void setServices(MasterServices srvc) {
1267       this.services = srvc;
1268     }
1269 
1270     @Override
1271     void init(Cluster cluster) {
1272       super.init(cluster);
1273       locality = 0.0;
1274       bestLocality = 0.0;
1275 
1276       // If no master, no computation will work, so assume 0 cost
1277       if (this.services == null) {
1278         return;
1279       }
1280 
1281       for (int region = 0; region < cluster.numRegions; region++) {
1282         locality += getWeightedLocality(region, regionIndexToEntityIndex(region));
1283         bestLocality += getWeightedLocality(region, getMostLocalEntityForRegion(region));
1284       }
1285 
1286       // We normalize locality to be a score between 0 and 1.0 representing how good it
1287       // is compared to how good it could be. If bestLocality is 0, assume locality is 100
1288       // (and the cost is 0)
1289       locality = bestLocality == 0 ? 1.0 : locality / bestLocality;
1290     }
1291 
1292     @Override
1293     protected void regionMoved(int region, int oldServer, int newServer) {
1294       int oldEntity = type == LocalityType.SERVER ? oldServer : cluster.serverIndexToRackIndex[oldServer];
1295       int newEntity = type == LocalityType.SERVER ? newServer : cluster.serverIndexToRackIndex[newServer];
1296       if (this.services == null) {
1297         return;
1298       }
1299       double localityDelta = getWeightedLocality(region, newEntity) - getWeightedLocality(region, oldEntity);
1300       double normalizedDelta = bestLocality == 0 ? 0.0 : localityDelta / bestLocality;
1301       locality += normalizedDelta;
1302     }
1303 
1304     @Override
1305     double cost() {
1306       return 1 - locality;
1307     }
1308 
1309     private int getMostLocalEntityForRegion(int region) {
1310       return cluster.getOrComputeRegionsToMostLocalEntities(type)[region];
1311     }
1312 
1313     private double getWeightedLocality(int region, int entity) {
1314       return cluster.getOrComputeWeightedLocality(region, entity, type);
1315     }
1316 
1317   }
1318 
1319   static class ServerLocalityCostFunction extends LocalityBasedCostFunction {
1320 
1321     private static final String LOCALITY_COST_KEY = "hbase.master.balancer.stochastic.localityCost";
1322     private static final float DEFAULT_LOCALITY_COST = 25;
1323 
1324     ServerLocalityCostFunction(Configuration conf, MasterServices srv) {
1325       super(
1326           conf,
1327           srv,
1328           LocalityType.SERVER,
1329           LOCALITY_COST_KEY,
1330           DEFAULT_LOCALITY_COST
1331       );
1332     }
1333 
1334     @Override
1335     int regionIndexToEntityIndex(int region) {
1336       return cluster.regionIndexToServerIndex[region];
1337     }
1338   }
1339 
1340   static class RackLocalityCostFunction extends LocalityBasedCostFunction {
1341 
1342     private static final String RACK_LOCALITY_COST_KEY = "hbase.master.balancer.stochastic.rackLocalityCost";
1343     private static final float DEFAULT_RACK_LOCALITY_COST = 15;
1344 
1345     public RackLocalityCostFunction(Configuration conf, MasterServices services) {
1346       super(
1347           conf,
1348           services,
1349           LocalityType.RACK,
1350           RACK_LOCALITY_COST_KEY,
1351           DEFAULT_RACK_LOCALITY_COST
1352       );
1353     }
1354 
1355     @Override
1356     int regionIndexToEntityIndex(int region) {
1357       return cluster.getRackForRegion(region);
1358     }
1359   }
1360 
1361   /**
1362    * Base class the allows writing costs functions from rolling average of some
1363    * number from RegionLoad.
1364    */
1365   abstract static class CostFromRegionLoadFunction extends CostFunction {
1366 
1367     private ClusterStatus clusterStatus = null;
1368     private Map<String, Deque<RegionLoad>> loads = null;
1369     private double[] stats = null;
1370     CostFromRegionLoadFunction(Configuration conf) {
1371       super(conf);
1372     }
1373 
1374     void setClusterStatus(ClusterStatus status) {
1375       this.clusterStatus = status;
1376     }
1377 
1378     void setLoads(Map<String, Deque<RegionLoad>> l) {
1379       this.loads = l;
1380     }
1381 
1382     @Override
1383     double cost() {
1384       if (clusterStatus == null || loads == null) {
1385         return 0;
1386       }
1387 
1388       if (stats == null || stats.length != cluster.numServers) {
1389         stats = new double[cluster.numServers];
1390       }
1391 
1392       for (int i =0; i < stats.length; i++) {
1393         //Cost this server has from RegionLoad
1394         long cost = 0;
1395 
1396         // for every region on this server get the rl
1397         for(int regionIndex:cluster.regionsPerServer[i]) {
1398           Collection<RegionLoad> regionLoadList =  cluster.regionLoads[regionIndex];
1399 
1400           // Now if we found a region load get the type of cost that was requested.
1401           if (regionLoadList != null) {
1402             cost = (long) (cost + getRegionLoadCost(regionLoadList));
1403           }
1404         }
1405 
1406         // Add the total cost to the stats.
1407         stats[i] = cost;
1408       }
1409 
1410       // Now return the scaled cost from data held in the stats object.
1411       return costFromArray(stats);
1412     }
1413 
1414     protected double getRegionLoadCost(Collection<RegionLoad> regionLoadList) {
1415       double cost = 0;
1416 
1417       for (RegionLoad rl : regionLoadList) {
1418         cost += getCostFromRl(rl);
1419       }
1420       return cost / regionLoadList.size();
1421     }
1422 
1423 
1424     protected abstract double getCostFromRl(RegionLoad rl);
1425   }
1426 
1427   /**
1428    * Class to be used for the subset of RegionLoad costs that should be treated as rates.
1429    * We do not compare about the actual rate in requests per second but rather the rate relative
1430    * to the rest of the regions.
1431    */
1432   abstract static class CostFromRegionLoadAsRateFunction extends CostFromRegionLoadFunction {
1433 
1434     CostFromRegionLoadAsRateFunction(Configuration conf) {
1435       super(conf);
1436     }
1437 
1438     @Override
1439     protected double getRegionLoadCost(Collection<RegionLoad> regionLoadList) {
1440       double cost = 0;
1441       double previous = 0;
1442       boolean isFirst = true;
1443       for (RegionLoad rl : regionLoadList) {
1444         double current = getCostFromRl(rl);
1445         if (isFirst) {
1446           isFirst = false;
1447         } else {
1448           cost += current - previous;
1449         }
1450         previous = current;
1451       }
1452       return Math.max(0, cost / (regionLoadList.size() - 1));
1453     }
1454   }
1455 
1456   /**
1457    * Compute the cost of total number of read requests  The more unbalanced the higher the
1458    * computed cost will be.  This uses a rolling average of regionload.
1459    */
1460 
1461   static class ReadRequestCostFunction extends CostFromRegionLoadAsRateFunction {
1462 
1463     private static final String READ_REQUEST_COST_KEY =
1464         "hbase.master.balancer.stochastic.readRequestCost";
1465     private static final float DEFAULT_READ_REQUEST_COST = 5;
1466 
1467     ReadRequestCostFunction(Configuration conf) {
1468       super(conf);
1469       this.setMultiplier(conf.getFloat(READ_REQUEST_COST_KEY, DEFAULT_READ_REQUEST_COST));
1470     }
1471 
1472 
1473     @Override
1474     protected double getCostFromRl(RegionLoad rl) {
1475       return rl.getReadRequestsCount();
1476     }
1477   }
1478 
1479   /**
1480    * Compute the cost of total number of write requests.  The more unbalanced the higher the
1481    * computed cost will be.  This uses a rolling average of regionload.
1482    */
1483   static class WriteRequestCostFunction extends CostFromRegionLoadAsRateFunction {
1484 
1485     private static final String WRITE_REQUEST_COST_KEY =
1486         "hbase.master.balancer.stochastic.writeRequestCost";
1487     private static final float DEFAULT_WRITE_REQUEST_COST = 5;
1488 
1489     WriteRequestCostFunction(Configuration conf) {
1490       super(conf);
1491       this.setMultiplier(conf.getFloat(WRITE_REQUEST_COST_KEY, DEFAULT_WRITE_REQUEST_COST));
1492     }
1493 
1494     @Override
1495     protected double getCostFromRl(RegionLoad rl) {
1496       return rl.getWriteRequestsCount();
1497     }
1498   }
1499 
1500   /**
1501    * A cost function for region replicas. We give a very high cost to hosting
1502    * replicas of the same region in the same host. We do not prevent the case
1503    * though, since if numReplicas > numRegionServers, we still want to keep the
1504    * replica open.
1505    */
1506   static class RegionReplicaHostCostFunction extends CostFunction {
1507     private static final String REGION_REPLICA_HOST_COST_KEY =
1508         "hbase.master.balancer.stochastic.regionReplicaHostCostKey";
1509     private static final float DEFAULT_REGION_REPLICA_HOST_COST_KEY = 100000;
1510 
1511     long maxCost = 0;
1512     long[] costsPerGroup; // group is either server, host or rack
1513     int[][] primariesOfRegionsPerGroup;
1514 
1515     public RegionReplicaHostCostFunction(Configuration conf) {
1516       super(conf);
1517       this.setMultiplier(conf.getFloat(REGION_REPLICA_HOST_COST_KEY,
1518         DEFAULT_REGION_REPLICA_HOST_COST_KEY));
1519     }
1520 
1521     @Override
1522     void init(Cluster cluster) {
1523       super.init(cluster);
1524       // max cost is the case where every region replica is hosted together regardless of host
1525       maxCost = cluster.numHosts > 1 ? getMaxCost(cluster) : 0;
1526       costsPerGroup = new long[cluster.numHosts];
1527       primariesOfRegionsPerGroup = cluster.multiServersPerHost // either server based or host based
1528           ? cluster.primariesOfRegionsPerHost
1529           : cluster.primariesOfRegionsPerServer;
1530       for (int i = 0 ; i < primariesOfRegionsPerGroup.length; i++) {
1531         costsPerGroup[i] = costPerGroup(primariesOfRegionsPerGroup[i]);
1532       }
1533     }
1534 
1535     long getMaxCost(Cluster cluster) {
1536       if (!cluster.hasRegionReplicas) {
1537         return 0; // short circuit
1538       }
1539       // max cost is the case where every region replica is hosted together regardless of host
1540       int[] primariesOfRegions = new int[cluster.numRegions];
1541       System.arraycopy(cluster.regionIndexToPrimaryIndex, 0, primariesOfRegions, 0,
1542           cluster.regions.length);
1543 
1544       Arrays.sort(primariesOfRegions);
1545 
1546       // compute numReplicas from the sorted array
1547       return costPerGroup(primariesOfRegions);
1548     }
1549 
1550     @Override
1551     boolean isNeeded() {
1552       return cluster.hasRegionReplicas;
1553     }
1554 
1555     @Override
1556     double cost() {
1557       if (maxCost <= 0) {
1558         return 0;
1559       }
1560 
1561       long totalCost = 0;
1562       for (int i = 0 ; i < costsPerGroup.length; i++) {
1563         totalCost += costsPerGroup[i];
1564       }
1565       return scale(0, maxCost, totalCost);
1566     }
1567 
1568     /**
1569      * For each primary region, it computes the total number of replicas in the array (numReplicas)
1570      * and returns a sum of numReplicas-1 squared. For example, if the server hosts
1571      * regions a, b, c, d, e, f where a and b are same replicas, and c,d,e are same replicas, it
1572      * returns (2-1) * (2-1) + (3-1) * (3-1) + (1-1) * (1-1).
1573      * @param primariesOfRegions a sorted array of primary regions ids for the regions hosted
1574      * @return a sum of numReplicas-1 squared for each primary region in the group.
1575      */
1576     protected long costPerGroup(int[] primariesOfRegions) {
1577       long cost = 0;
1578       int currentPrimary = -1;
1579       int currentPrimaryIndex = -1;
1580       // primariesOfRegions is a sorted array of primary ids of regions. Replicas of regions
1581       // sharing the same primary will have consecutive numbers in the array.
1582       for (int j = 0 ; j <= primariesOfRegions.length; j++) {
1583         int primary = j < primariesOfRegions.length ? primariesOfRegions[j] : -1;
1584         if (primary != currentPrimary) { // we see a new primary
1585           int numReplicas = j - currentPrimaryIndex;
1586           // square the cost
1587           if (numReplicas > 1) { // means consecutive primaries, indicating co-location
1588             cost += (numReplicas - 1) * (numReplicas - 1);
1589           }
1590           currentPrimary = primary;
1591           currentPrimaryIndex = j;
1592         }
1593       }
1594 
1595       return cost;
1596     }
1597 
1598     @Override
1599     protected void regionMoved(int region, int oldServer, int newServer) {
1600       if (maxCost <= 0) {
1601         return; // no need to compute
1602       }
1603       if (cluster.multiServersPerHost) {
1604         int oldHost = cluster.serverIndexToHostIndex[oldServer];
1605         int newHost = cluster.serverIndexToHostIndex[newServer];
1606         if (newHost != oldHost) {
1607           costsPerGroup[oldHost] = costPerGroup(cluster.primariesOfRegionsPerHost[oldHost]);
1608           costsPerGroup[newHost] = costPerGroup(cluster.primariesOfRegionsPerHost[newHost]);
1609         }
1610       } else {
1611         costsPerGroup[oldServer] = costPerGroup(cluster.primariesOfRegionsPerServer[oldServer]);
1612         costsPerGroup[newServer] = costPerGroup(cluster.primariesOfRegionsPerServer[newServer]);
1613       }
1614     }
1615   }
1616 
1617   /**
1618    * A cost function for region replicas for the rack distribution. We give a relatively high
1619    * cost to hosting replicas of the same region in the same rack. We do not prevent the case
1620    * though.
1621    */
1622   static class RegionReplicaRackCostFunction extends RegionReplicaHostCostFunction {
1623     private static final String REGION_REPLICA_RACK_COST_KEY =
1624         "hbase.master.balancer.stochastic.regionReplicaRackCostKey";
1625     private static final float DEFAULT_REGION_REPLICA_RACK_COST_KEY = 10000;
1626 
1627     public RegionReplicaRackCostFunction(Configuration conf) {
1628       super(conf);
1629       this.setMultiplier(conf.getFloat(REGION_REPLICA_RACK_COST_KEY,
1630         DEFAULT_REGION_REPLICA_RACK_COST_KEY));
1631     }
1632 
1633     @Override
1634     void init(Cluster cluster) {
1635       this.cluster = cluster;
1636       if (cluster.numRacks <= 1) {
1637         maxCost = 0;
1638         return; // disabled for 1 rack
1639       }
1640       // max cost is the case where every region replica is hosted together regardless of rack
1641       maxCost = getMaxCost(cluster);
1642       costsPerGroup = new long[cluster.numRacks];
1643       for (int i = 0 ; i < cluster.primariesOfRegionsPerRack.length; i++) {
1644         costsPerGroup[i] = costPerGroup(cluster.primariesOfRegionsPerRack[i]);
1645       }
1646     }
1647 
1648     @Override
1649     protected void regionMoved(int region, int oldServer, int newServer) {
1650       if (maxCost <= 0) {
1651         return; // no need to compute
1652       }
1653       int oldRack = cluster.serverIndexToRackIndex[oldServer];
1654       int newRack = cluster.serverIndexToRackIndex[newServer];
1655       if (newRack != oldRack) {
1656         costsPerGroup[oldRack] = costPerGroup(cluster.primariesOfRegionsPerRack[oldRack]);
1657         costsPerGroup[newRack] = costPerGroup(cluster.primariesOfRegionsPerRack[newRack]);
1658       }
1659     }
1660   }
1661 
1662   /**
1663    * Compute the cost of total memstore size.  The more unbalanced the higher the
1664    * computed cost will be.  This uses a rolling average of regionload.
1665    */
1666   static class MemstoreSizeCostFunction extends CostFromRegionLoadAsRateFunction {
1667 
1668     private static final String MEMSTORE_SIZE_COST_KEY =
1669         "hbase.master.balancer.stochastic.memstoreSizeCost";
1670     private static final float DEFAULT_MEMSTORE_SIZE_COST = 5;
1671 
1672     MemstoreSizeCostFunction(Configuration conf) {
1673       super(conf);
1674       this.setMultiplier(conf.getFloat(MEMSTORE_SIZE_COST_KEY, DEFAULT_MEMSTORE_SIZE_COST));
1675     }
1676 
1677     @Override
1678     protected double getCostFromRl(RegionLoad rl) {
1679       return rl.getMemStoreSizeMB();
1680     }
1681   }
1682   /**
1683    * Compute the cost of total open storefiles size.  The more unbalanced the higher the
1684    * computed cost will be.  This uses a rolling average of regionload.
1685    */
1686   static class StoreFileCostFunction extends CostFromRegionLoadFunction {
1687 
1688     private static final String STOREFILE_SIZE_COST_KEY =
1689         "hbase.master.balancer.stochastic.storefileSizeCost";
1690     private static final float DEFAULT_STOREFILE_SIZE_COST = 5;
1691 
1692     StoreFileCostFunction(Configuration conf) {
1693       super(conf);
1694       this.setMultiplier(conf.getFloat(STOREFILE_SIZE_COST_KEY, DEFAULT_STOREFILE_SIZE_COST));
1695     }
1696 
1697     @Override
1698     protected double getCostFromRl(RegionLoad rl) {
1699       return rl.getStorefileSizeMB();
1700     }
1701   }
1702 
1703   /**
1704    * A helper function to compose the attribute name from tablename and costfunction name
1705    */
1706   public static String composeAttributeName(String tableName, String costFunctionName) {
1707     return tableName + TABLE_FUNCTION_SEP + costFunctionName;
1708   }
1709 }