This is an automated email from the ASF dual-hosted git repository.

apurtell pushed a commit to branch branch-2.5
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/branch-2.5 by this push:
     new 6556350d4f5 HBASE-22349 slop in StochasticLoadBalancer (#4371)
6556350d4f5 is described below

commit 6556350d4f5457327da91bab2f96bf32f609beff
Author: d-c-manning <[email protected]>
AuthorDate: Thu Apr 28 12:28:15 2022 -0700

    HBASE-22349 slop in StochasticLoadBalancer (#4371)
    
    Signed-off-by: Andrew Purtell <[email protected]>
    
    Conflicts:
            
hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/BaseLoadBalancer.java
            
hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/BalancerTestBase.java
---
 hbase-common/src/main/resources/hbase-default.xml  |  19 +-
 .../hbase/master/balancer/BaseLoadBalancer.java    |  39 ++--
 .../hbase/master/balancer/SimpleLoadBalancer.java  |  17 +-
 .../master/balancer/StochasticLoadBalancer.java    |  11 +-
 .../hbase/master/balancer/BalancerTestBase.java    | 199 +++++++++++++--------
 .../master/balancer/TestBalancerDecision.java      |   3 +
 .../master/balancer/TestSimpleLoadBalancer.java    |  33 ++--
 .../balancer/TestStochasticLoadBalancer.java       |  73 +++++++-
 8 files changed, 255 insertions(+), 139 deletions(-)

diff --git a/hbase-common/src/main/resources/hbase-default.xml 
b/hbase-common/src/main/resources/hbase-default.xml
index ce5011deba2..85a0b314f03 100644
--- a/hbase-common/src/main/resources/hbase-default.xml
+++ b/hbase-common/src/main/resources/hbase-default.xml
@@ -619,11 +619,20 @@ possible configurations would overwhelm and obscure the 
important.
   </property>
   <property>
     <name>hbase.regions.slop</name>
-    <value>0.001</value>
-    <description>Rebalance if any regionserver has average + (average * slop) 
regions.
-      The default value of this parameter is 0.001 in StochasticLoadBalancer 
(the default load
-      balancer), while the default is 0.2 in other load balancers (i.e.,
-      SimpleLoadBalancer).</description>
+    <value>0.2</value>
+    <description>The load balancer can trigger for several reasons. This value 
controls one of
+      those reasons. Run the balancer if any regionserver has a region count 
outside the range of
+      average +/- (average * slop) regions.
+      If the value of slop is negative, disable sloppiness checks. The 
balancer can still run for
+      other reasons, but sloppiness will not be one of them.
+      If the value of slop is 0, run the balancer if any server has a region 
count more than 1
+      from the average.
+      If the value of slop is 100, run the balancer if any server has a region 
count greater than
+      101 times the average.
+      The default value of this parameter is 0.2, which runs the balancer if 
any server has a region
+      count less than 80% of the average, or greater than 120% of the average.
+      Note that for the default StochasticLoadBalancer, this does not 
guarantee any balancing
+      actions will be taken, but only that the balancer will attempt to 
run.</description>
   </property>
   <property>
     <name>hbase.normalizer.period</name>
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/BaseLoadBalancer.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/BaseLoadBalancer.java
index 1052617b59f..93117405b01 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/BaseLoadBalancer.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/BaseLoadBalancer.java
@@ -26,6 +26,7 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.NavigableMap;
 import java.util.Random;
 import java.util.Set;
 import java.util.TreeMap;
@@ -249,6 +250,28 @@ public abstract class BaseLoadBalancer implements 
LoadBalancer {
     return isServerExistsWithMoreRegions && isServerExistsWithZeroRegions;
   }
 
+  protected final boolean sloppyRegionServerExist(ClusterLoadState cs) {
+    if (slop < 0) {
+      LOG.debug("Slop is less than zero, not checking for sloppiness.");
+      return false;
+    }
+    float average = cs.getLoadAverage(); // for logging
+    int floor = (int) Math.floor(average * (1 - slop));
+    int ceiling = (int) Math.ceil(average * (1 + slop));
+    if (!(cs.getMaxLoad() > ceiling || cs.getMinLoad() < floor)) {
+      NavigableMap<ServerAndLoad, List<RegionInfo>> serversByLoad = 
cs.getServersByLoad();
+      if (LOG.isTraceEnabled()) {
+        // If nothing to balance, then don't say anything unless trace-level 
logging.
+        LOG.trace("Skipping load balancing because balanced cluster; " + 
"servers=" +
+          cs.getNumServers() + " regions=" + cs.getNumRegions() + " average=" 
+ average +
+          " mostloaded=" + serversByLoad.lastKey().getLoad() + " leastloaded=" 
+
+          serversByLoad.firstKey().getLoad());
+      }
+      return false;
+    }
+    return true;
+  }
+
   /**
    * Generates a bulk assignment plan to be used on cluster startup using a
    * simple round-robin assignment.
@@ -532,16 +555,6 @@ public abstract class BaseLoadBalancer implements 
LoadBalancer {
     return assignments;
   }
 
-  protected final float normalizeSlop(float slop) {
-    if (slop < 0) {
-      return 0;
-    }
-    if (slop > 1) {
-      return 1;
-    }
-    return slop;
-  }
-
   protected float getDefaultSlop() {
     return 0.2f;
   }
@@ -554,11 +567,9 @@ public abstract class BaseLoadBalancer implements 
LoadBalancer {
   }
 
   protected void loadConf(Configuration conf) {
-    this.slop =normalizeSlop(conf.getFloat("hbase.regions.slop", 
getDefaultSlop()));
-
-    this.onlySystemTablesOnMaster = 
LoadBalancer.isSystemTablesOnlyOnMaster(conf);
-
+    this.slop = conf.getFloat("hbase.regions.slop", getDefaultSlop());
     this.rackManager = new RackManager(getConf());
+    this.onlySystemTablesOnMaster = 
LoadBalancer.isSystemTablesOnlyOnMaster(conf);
     useRegionFinder = conf.getBoolean("hbase.master.balancer.uselocality", 
true);
     if (useRegionFinder) {
       regionFinder = createRegionLocationFinder(conf);
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/SimpleLoadBalancer.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/SimpleLoadBalancer.java
index 4e180aad9d2..8bea2584a3f 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/SimpleLoadBalancer.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/SimpleLoadBalancer.java
@@ -195,24 +195,9 @@ public class SimpleLoadBalancer extends BaseLoadBalancer {
     if (idleRegionServerExist(c)) {
       return true;
     }
-
     // Check if we even need to do any load balancing
     // HBASE-3681 check sloppiness first
-    float average = cs.getLoadAverage(); // for logging
-    int floor = (int) Math.floor(average * (1 - slop));
-    int ceiling = (int) Math.ceil(average * (1 + slop));
-    if (!(cs.getMaxLoad() > ceiling || cs.getMinLoad() < floor)) {
-      NavigableMap<ServerAndLoad, List<RegionInfo>> serversByLoad = 
cs.getServersByLoad();
-      if (LOG.isTraceEnabled()) {
-        // If nothing to balance, then don't say anything unless trace-level 
logging.
-        LOG.trace("Skipping load balancing because balanced cluster; " + 
"servers=" +
-          cs.getNumServers() + " regions=" + cs.getNumRegions() + " average=" 
+ average +
-          " mostloaded=" + serversByLoad.lastKey().getLoad() + " leastloaded=" 
+
-          serversByLoad.firstKey().getLoad());
-      }
-      return false;
-    }
-    return true;
+    return sloppyRegionServerExist(cs);
   }
 
   /**
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/StochasticLoadBalancer.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/StochasticLoadBalancer.java
index a69931e63ca..a798b4a3dd0 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/StochasticLoadBalancer.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/StochasticLoadBalancer.java
@@ -211,11 +211,6 @@ public class StochasticLoadBalancer extends 
BaseLoadBalancer {
     return this.candidateGenerators;
   }
 
-  @Override
-  protected float getDefaultSlop() {
-    return 0.001f;
-  }
-
   protected List<CandidateGenerator> createCandidateGenerators() {
     List<CandidateGenerator> candidateGenerators = new 
ArrayList<CandidateGenerator>(4);
     candidateGenerators.add(GeneratorType.RANDOM.ordinal(), new 
RandomCandidateGenerator());
@@ -370,6 +365,12 @@ public class StochasticLoadBalancer extends 
BaseLoadBalancer {
       return true;
     }
 
+    if (sloppyRegionServerExist(cs)) {
+      LOG.info("Running balancer because cluster has sloppy server(s)."+
+        " function cost={}", functionCost());
+      return true;
+    }
+
     double total = 0.0;
     for (CostFunction c : costFunctions) {
       if (!c.isNeeded()) {
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/BalancerTestBase.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/BalancerTestBase.java
index 6c779f692e1..05a0e8ed9b5 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/BalancerTestBase.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/BalancerTestBase.java
@@ -75,7 +75,6 @@ public class BalancerTestBase {
   public static void beforeAllTests() throws Exception {
     conf = HBaseConfiguration.create();
     conf.setClass("hbase.util.ip.to.rack.determiner", MockMapping.class, 
DNSToSwitchMapping.class);
-    conf.setFloat("hbase.regions.slop", 0.0f);
     conf.setFloat("hbase.master.balancer.stochastic.localityCost", 0);
     loadBalancer = new StochasticLoadBalancer(dummyMetricsStochasticBalancer);
     MasterServices services = mock(MasterServices.class);
@@ -85,83 +84,122 @@ public class BalancerTestBase {
   }
 
   protected int[] largeCluster = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0,
-      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0,
-      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0,
-      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0,
-      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0,
-      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0,
-      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0,
-      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0,
-      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0,
-      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0,
-      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0,
-      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0,
-      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0,
-      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56 };
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56 };
 
   // int[testnum][servernumber] -> numregions
   protected int[][] clusterStateMocks = new int[][]{
-      // 1 node
-      new int[]{0},
-      new int[]{1},
-      new int[]{10},
-      // 2 node
-      new int[]{0, 0},
-      new int[]{2, 0},
-      new int[]{2, 1},
-      new int[]{2, 2},
-      new int[]{2, 3},
-      new int[]{2, 4},
-      new int[]{1, 1},
-      new int[]{0, 1},
-      new int[]{10, 1},
-      new int[]{514, 1432},
-      new int[]{48, 53},
-      // 3 node
-      new int[]{0, 1, 2},
-      new int[]{1, 2, 3},
-      new int[]{0, 2, 2},
-      new int[]{0, 3, 0},
-      new int[]{0, 4, 0},
-      new int[]{20, 20, 0},
-      // 4 node
-      new int[]{0, 1, 2, 3},
-      new int[]{4, 0, 0, 0},
-      new int[]{5, 0, 0, 0},
-      new int[]{6, 6, 0, 0},
-      new int[]{6, 2, 0, 0},
-      new int[]{6, 1, 0, 0},
-      new int[]{6, 0, 0, 0},
-      new int[]{4, 4, 4, 7},
-      new int[]{4, 4, 4, 8},
-      new int[]{0, 0, 0, 7},
-      // 5 node
-      new int[]{1, 1, 1, 1, 4},
-      // 6 nodes
-      new int[]{1500, 500, 500, 500, 10, 0},
-      new int[]{1500, 500, 500, 500, 500, 0},
-      // more nodes
-      new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
-      new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 10},
-      new int[]{6, 6, 5, 6, 6, 6, 6, 6, 6, 1},
-      new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 54},
-      new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 55},
-      new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 56},
-      new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 16},
-      new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 8},
-      new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 9},
-      new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 10},
-      new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 123},
-      new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 155},
-      new int[]{10, 7, 12, 8, 11, 10, 9, 14},
-      new int[]{13, 14, 6, 10, 10, 10, 8, 10},
-      new int[]{130, 14, 60, 10, 100, 10, 80, 10},
-      new int[]{130, 140, 60, 100, 100, 100, 80, 100},
-      new int[]{0, 5 , 5, 5, 5},
-      largeCluster,
+    // 1 node
+    new int[]{0},
+    new int[]{1},
+    new int[]{10},
+    // 2 node
+    new int[]{0, 0},
+    new int[]{2, 0},
+    new int[]{2, 1},
+    new int[]{2, 2},
+    new int[]{2, 3},
+    new int[]{2, 4},
+    new int[]{1, 1},
+    new int[]{0, 1},
+    new int[]{10, 1},
+    new int[]{514, 1432},
+    new int[]{48, 53},
+    // 3 node
+    new int[]{0, 1, 2},
+    new int[]{1, 2, 3},
+    new int[]{0, 2, 2},
+    new int[]{0, 3, 0},
+    new int[]{0, 4, 0},
+    new int[]{20, 20, 0},
+    // 4 node
+    new int[]{0, 1, 2, 3},
+    new int[]{4, 0, 0, 0},
+    new int[]{5, 0, 0, 0},
+    new int[]{6, 6, 0, 0},
+    new int[]{6, 2, 0, 0},
+    new int[]{6, 1, 0, 0},
+    new int[]{6, 0, 0, 0},
+    new int[]{4, 4, 4, 7},
+    new int[]{4, 4, 4, 8},
+    new int[]{0, 0, 0, 7},
+    // 5 node
+    new int[]{1, 1, 1, 1, 4},
+    // 6 nodes
+    new int[]{1500, 500, 500, 500, 10, 0},
+    new int[]{1500, 500, 500, 500, 500, 0},
+    // more nodes
+    new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
+    new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 10},
+    new int[]{6, 6, 5, 6, 6, 6, 6, 6, 6, 1},
+    new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 54},
+    new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 55},
+    new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 56},
+    new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 16},
+    new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 8},
+    new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 9},
+    new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 10},
+    new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 123},
+    new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 155},
+    new int[]{10, 7, 12, 8, 11, 10, 9, 14},
+    new int[]{13, 14, 6, 10, 10, 10, 8, 10},
+    new int[]{130, 14, 60, 10, 100, 10, 80, 10},
+    new int[]{130, 140, 60, 100, 100, 100, 80, 100},
+    new int[]{0, 5 , 5, 5, 5},
+    largeCluster,
 
   };
 
+  // int[testnum][servernumber] -> numregions
+  protected int[][] clusterStateMocksWithNoSlop = new int[][] {
+    // 1 node
+    new int[]{0},
+    new int[]{1},
+    new int[]{10},
+    // 2 node
+    new int[]{0, 0},
+    new int[]{2, 1},
+    new int[]{2, 2},
+    new int[]{2, 3},
+    new int[]{1, 1},
+    new int[]{80, 120},
+    new int[]{1428, 1432},
+    // more nodes
+    new int[]{100, 90, 120, 90, 110, 100, 90, 120},
+  };
+
+  // int[testnum][servernumber] -> numregions
+  protected int[][] clusterStateMocksWithSlop = new int[][] {
+    // 2 node
+    new int[]{1, 4},
+    new int[]{10, 20},
+    new int[]{80, 123},
+    // more nodes
+    new int[]{100, 100, 100, 100, 100, 100, 100, 100, 100, 200},
+    new int[] {
+      10, 5, 5, 5, 5, 5, 5, 5, 5, 5
+      , 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
+      , 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
+      , 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
+      , 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
+      , 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
+      , 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
+      , 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
+      , 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
+      , 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
+    },
+  };
 
   // This class is introduced because IP to rack resolution can be lengthy.
   public static class MockMapping implements DNSToSwitchMapping {
@@ -380,6 +418,23 @@ public class BalancerTestBase {
     map.put(sn, sal);
   }
 
+  protected TreeMap<ServerName, List<RegionInfo>> mockClusterServers(int[][] 
mockCluster) {
+    // dimension1: table, dimension2: regions per server
+    int numTables = mockCluster.length;
+    TreeMap<ServerName, List<RegionInfo>> servers = new TreeMap<>();
+    for (int i = 0; i < numTables; i++) {
+      TableName tableName = TableName.valueOf("table" + i);
+      for (int j = 0; j < mockCluster[i].length; j++) {
+        ServerName serverName = ServerName.valueOf("server" + j, 1000, -1);
+        int numRegions = mockCluster[i][j];
+        List<RegionInfo> regions = createRegions(numRegions, tableName);
+        servers.putIfAbsent(serverName, new ArrayList<>());
+        servers.get(serverName).addAll(regions);
+      }
+    }
+    return servers;
+  }
+
   protected TreeMap<ServerName, List<RegionInfo>> mockClusterServers(int[] 
mockCluster) {
     return mockClusterServers(mockCluster, -1);
   }
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestBalancerDecision.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestBalancerDecision.java
index 74419951f28..76169066873 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestBalancerDecision.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestBalancerDecision.java
@@ -57,7 +57,9 @@ public class TestBalancerDecision extends BalancerTestBase {
     conf.setBoolean("hbase.master.balancer.decision.buffer.enabled", true);
     loadBalancer.onConfigurationChange(conf);
     float minCost = 
conf.getFloat("hbase.master.balancer.stochastic.minCostNeedBalance", 0.05f);
+    float slop = conf.getFloat(HConstants.LOAD_BALANCER_SLOP_KEY, 0.2f);
     conf.setFloat("hbase.master.balancer.stochastic.minCostNeedBalance", 1.0f);
+    conf.setFloat(HConstants.LOAD_BALANCER_SLOP_KEY, -1f);
     try {
       // Test with/without per table balancer.
       boolean[] perTableBalancerConfigs = {true, false};
@@ -92,6 +94,7 @@ public class TestBalancerDecision extends BalancerTestBase {
       // reset config
       conf.unset(HConstants.HBASE_MASTER_LOADBALANCE_BYTABLE);
       conf.setFloat("hbase.master.balancer.stochastic.minCostNeedBalance", 
minCost);
+      conf.setFloat(HConstants.LOAD_BALANCER_SLOP_KEY, slop);
       loadBalancer.onConfigurationChange(conf);
     }
   }
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestSimpleLoadBalancer.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestSimpleLoadBalancer.java
index ecad7a25503..2ae6b680d7a 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestSimpleLoadBalancer.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestSimpleLoadBalancer.java
@@ -225,28 +225,19 @@ public class TestSimpleLoadBalancer extends 
BalancerTestBase {
   }
 
   @Test
-  public void testBalanceClusterOverallStrictly() throws Exception {
-    int[] regionNumOfTable1PerServer = { 3, 3, 4, 4, 4, 4, 5, 5, 5 };
-    int[] regionNumOfTable2PerServer = { 2, 2, 2, 2, 2, 2, 2, 2, 1 };
-    TreeMap<ServerName, List<RegionInfo>> serverRegionInfo = new TreeMap<>();
-    List<ServerAndLoad> serverAndLoads = new ArrayList<>();
-    for (int i = 0; i < regionNumOfTable1PerServer.length; i++) {
-      ServerName serverName = ServerName.valueOf("server" + i, 1000, -1);
-      List<RegionInfo> regions1 =
-          createRegions(regionNumOfTable1PerServer[i], 
TableName.valueOf("table1"));
-      List<RegionInfo> regions2 =
-          createRegions(regionNumOfTable2PerServer[i], 
TableName.valueOf("table2"));
-      regions1.addAll(regions2);
-      serverRegionInfo.put(serverName, regions1);
-      ServerAndLoad serverAndLoad = new ServerAndLoad(serverName,
-          regionNumOfTable1PerServer[i] + regionNumOfTable2PerServer[i]);
-      serverAndLoads.add(serverAndLoad);
-    }
-    HashMap<TableName, TreeMap<ServerName, List<RegionInfo>>> LoadOfAllTable =
+  public void testBalanceClusterOverallStrictly() {
+    int[][] regionsPerServerPerTable = new int[][]{
+      new int[]{ 3, 3, 4, 4, 4, 4, 5, 5, 5 },
+      new int[]{ 2, 2, 2, 2, 2, 2, 2, 2, 1 },
+    };
+    TreeMap<ServerName, List<RegionInfo>> serverRegionInfo =
+      mockClusterServers(regionsPerServerPerTable);
+    List<ServerAndLoad> serverAndLoads = convertToList(serverRegionInfo);
+    Map<TableName, TreeMap<ServerName, List<RegionInfo>>> loadOfAllTable =
         mockClusterServersWithTables(serverRegionInfo);
-    loadBalancer.setClusterLoad((Map) LoadOfAllTable);
-    List<RegionPlan> partialplans = 
loadBalancer.balanceTable(TableName.valueOf("table1"),
-      LoadOfAllTable.get(TableName.valueOf("table1")));
+    loadBalancer.setClusterLoad((Map) loadOfAllTable);
+    List<RegionPlan> partialplans = 
loadBalancer.balanceTable(TableName.valueOf("table0"),
+      loadOfAllTable.get(TableName.valueOf("table0")));
     List<ServerAndLoad> balancedServerLoads =
         reconcile(serverAndLoads, partialplans, serverRegionInfo);
     for (ServerAndLoad serverAndLoad : balancedServerLoads) {
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestStochasticLoadBalancer.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestStochasticLoadBalancer.java
index 620dd033735..14b6b0ea75a 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestStochasticLoadBalancer.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestStochasticLoadBalancer.java
@@ -18,6 +18,7 @@
 package org.apache.hadoop.hbase.master.balancer;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -256,7 +257,9 @@ public class TestStochasticLoadBalancer extends 
BalancerTestBase {
   @Test
   public void testNeedBalance() {
     float minCost = 
conf.getFloat("hbase.master.balancer.stochastic.minCostNeedBalance", 0.05f);
+    float slop = conf.getFloat(HConstants.LOAD_BALANCER_SLOP_KEY, 0.2f);
     conf.setFloat("hbase.master.balancer.stochastic.minCostNeedBalance", 1.0f);
+    conf.setFloat(HConstants.LOAD_BALANCER_SLOP_KEY, -1f);
     try {
       // Test with/without per table balancer.
       boolean[] perTableBalancerConfigs = {true, false};
@@ -265,22 +268,80 @@ public class TestStochasticLoadBalancer extends 
BalancerTestBase {
         loadBalancer.onConfigurationChange(conf);
 
         for (int[] mockCluster : clusterStateMocks) {
-          Map<ServerName, List<RegionInfo>> servers = 
mockClusterServers(mockCluster);
-          Map<TableName, Map<ServerName, List<RegionInfo>>> LoadOfAllTable =
-              (Map) mockClusterServersWithTables(servers);
-          List<RegionPlan> plans = loadBalancer.balanceCluster(LoadOfAllTable);
-          boolean emptyPlans = plans == null || plans.isEmpty();
-          assertTrue(emptyPlans || needsBalanceIdleRegion(mockCluster));
+          assertTrue(hasEmptyBalancerPlans(mockCluster) || 
needsBalanceIdleRegion(mockCluster));
         }
       }
     } finally {
       // reset config
       conf.unset(HConstants.HBASE_MASTER_LOADBALANCE_BYTABLE);
       conf.setFloat("hbase.master.balancer.stochastic.minCostNeedBalance", 
minCost);
+      conf.setFloat(HConstants.LOAD_BALANCER_SLOP_KEY, slop);
       loadBalancer.onConfigurationChange(conf);
     }
   }
 
+  @Test
+  public void testBalanceOfSloppyServers() {
+    float minCost = 
conf.getFloat("hbase.master.balancer.stochastic.minCostNeedBalance", 0.025f);
+    try {
+      // We are testing slop checks, so don't "accidentally" balance due to a 
minCost calculation.
+      // During development, imbalance of a 100 server cluster, with one node 
having 10 regions
+      // and the rest having 5, is 0.0048. With minCostNeedBalance default of 
0.025, test should
+      // validate slop checks without this override. We override just to 
ensure we will always
+      // validate slop check here, and for small clusters as well.
+      conf.setFloat("hbase.master.balancer.stochastic.minCostNeedBalance", 
1.0f);
+      loadBalancer.onConfigurationChange(conf);
+      for (int[] mockCluster : clusterStateMocksWithNoSlop) {
+        assertTrue(hasEmptyBalancerPlans(mockCluster));
+      }
+      for (int[] mockCluster : clusterStateMocksWithSlop) {
+        assertFalse(hasEmptyBalancerPlans(mockCluster));
+      }
+    } finally {
+      // reset config
+      conf.setFloat("hbase.master.balancer.stochastic.minCostNeedBalance", 
minCost);
+      loadBalancer.onConfigurationChange(conf);
+    }
+  }
+
+  @Test
+  public void testSloppyTablesLoadBalanceByTable() {
+    int[][] regionsPerServerPerTable = new int[][] {
+      new int[] { 8, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
+        , 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5},
+      new int[] { 2, 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
+        , 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5},
+    };
+    float minCost = 
conf.getFloat("hbase.master.balancer.stochastic.minCostNeedBalance", 0.025f);
+    try {
+      conf.setFloat("hbase.master.balancer.stochastic.minCostNeedBalance", 
1.0f);
+      conf.setBoolean(HConstants.HBASE_MASTER_LOADBALANCE_BYTABLE, true);
+      loadBalancer.onConfigurationChange(conf);
+      assertFalse(hasEmptyBalancerPlans(regionsPerServerPerTable));
+    } finally {
+      conf.setFloat("hbase.master.balancer.stochastic.minCostNeedBalance", 
minCost);
+      conf.unset(HConstants.HBASE_MASTER_LOADBALANCE_BYTABLE);
+      loadBalancer.onConfigurationChange(conf);
+    }
+  }
+
+  private boolean hasEmptyBalancerPlans(int[] mockCluster) {
+    Map<ServerName, List<RegionInfo>> servers = 
mockClusterServers(mockCluster);
+    return hasEmptyBalancerPlans(servers);
+  }
+
+  private boolean hasEmptyBalancerPlans(int[][] mockCluster) {
+    Map<ServerName, List<RegionInfo>> servers = 
mockClusterServers(mockCluster);
+    return hasEmptyBalancerPlans(servers);
+  }
+
+  private boolean hasEmptyBalancerPlans(Map<ServerName, List<RegionInfo>> 
servers) {
+    Map<TableName, Map<ServerName, List<RegionInfo>>> loadOfAllTable =
+      (Map) mockClusterServersWithTables(servers);
+    List<RegionPlan> plans = loadBalancer.balanceCluster(loadOfAllTable);
+    return plans == null || plans.isEmpty();
+  }
+
   @Test
   public void testLocalityCost() throws Exception {
     Configuration conf = HBaseConfiguration.create();

Reply via email to