Skip to content

Commit 42a899e

Browse files
committed
HBASE-25873 Refactor and cleanup the code for CostFunction
1 parent 1c6994a commit 42a899e

18 files changed

Lines changed: 229 additions & 202 deletions

hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/AssignRegionAction.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,4 @@ public BalanceAction undoAction() {
5050
public String toString() {
5151
return getType() + ": " + region + ":" + server;
5252
}
53-
5453
}

hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/CostFromRegionLoadAsRateFunction.java

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.hadoop.hbase.master.balancer;
1919

2020
import java.util.Collection;
21+
import java.util.Iterator;
2122
import org.apache.yetus.audience.InterfaceAudience;
2223

2324
/**
@@ -30,18 +31,20 @@ abstract class CostFromRegionLoadAsRateFunction extends CostFromRegionLoadFuncti
3031

3132
@Override
3233
protected double getRegionLoadCost(Collection<BalancerRegionLoad> regionLoadList) {
34+
Iterator<BalancerRegionLoad> iter = regionLoadList.iterator();
35+
if (!iter.hasNext()) {
36+
return 0;
37+
}
38+
double previous = getCostFromRl(iter.next());
39+
if (!iter.hasNext()) {
40+
return 0;
41+
}
3342
double cost = 0;
34-
double previous = 0;
35-
boolean isFirst = true;
36-
for (BalancerRegionLoad rl : regionLoadList) {
37-
double current = getCostFromRl(rl);
38-
if (isFirst) {
39-
isFirst = false;
40-
} else {
41-
cost += current - previous;
42-
}
43+
do {
44+
double current = getCostFromRl(iter.next());
45+
cost += current - previous;
4346
previous = current;
44-
}
47+
} while (iter.hasNext());
4548
return Math.max(0, cost / (regionLoadList.size() - 1));
4649
}
4750
}

hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/CostFromRegionLoadFunction.java

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@
1818
package org.apache.hadoop.hbase.master.balancer;
1919

2020
import java.util.Collection;
21-
import java.util.Deque;
22-
import java.util.Map;
23-
import org.apache.hadoop.hbase.ClusterMetrics;
2421
import org.apache.yetus.audience.InterfaceAudience;
2522

2623
/**
@@ -30,39 +27,29 @@
3027
@InterfaceAudience.Private
3128
abstract class CostFromRegionLoadFunction extends CostFunction {
3229

33-
private ClusterMetrics clusterStatus;
34-
private Map<String, Deque<BalancerRegionLoad>> loads;
3530
private double[] stats;
3631

37-
void setClusterMetrics(ClusterMetrics status) {
38-
this.clusterStatus = status;
39-
}
40-
41-
void setLoads(Map<String, Deque<BalancerRegionLoad>> l) {
42-
this.loads = l;
43-
}
44-
4532
@Override
46-
protected final double cost() {
47-
if (clusterStatus == null || loads == null) {
48-
return 0;
49-
}
50-
33+
void prepare(BalancerClusterState cluster) {
34+
super.prepare(cluster);
5135
if (stats == null || stats.length != cluster.numServers) {
5236
stats = new double[cluster.numServers];
5337
}
38+
}
5439

40+
@Override
41+
protected final double cost() {
5542
for (int i = 0; i < stats.length; i++) {
5643
// Cost this server has from RegionLoad
57-
long cost = 0;
44+
double cost = 0;
5845

5946
// for every region on this server get the rl
6047
for (int regionIndex : cluster.regionsPerServer[i]) {
6148
Collection<BalancerRegionLoad> regionLoadList = cluster.regionLoads[regionIndex];
6249

6350
// Now if we found a region load get the type of cost that was requested.
6451
if (regionLoadList != null) {
65-
cost = (long) (cost + getRegionLoadCost(regionLoadList));
52+
cost += getRegionLoadCost(regionLoadList);
6653
}
6754
}
6855

hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/CostFunction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ void setMultiplier(float m) {
4545
* Called once per LB invocation to give the cost function to initialize it's state, and perform
4646
* any costly calculation.
4747
*/
48-
void init(BalancerClusterState cluster) {
48+
void prepare(BalancerClusterState cluster) {
4949
this.cluster = cluster;
5050
}
5151

hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/HeterogeneousRegionCountCostFunction.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515
package org.apache.hadoop.hbase.master.balancer;
1616

17+
import com.google.errorprone.annotations.RestrictedApi;
1718
import java.io.BufferedReader;
1819
import java.io.FileReader;
1920
import java.io.IOException;
@@ -119,7 +120,7 @@ public class HeterogeneousRegionCountCostFunction extends CostFunction {
119120
* any costly calculation.
120121
*/
121122
@Override
122-
void init(final BalancerClusterState cluster) {
123+
void prepare(final BalancerClusterState cluster) {
123124
this.cluster = cluster;
124125
this.loadRules();
125126
}
@@ -148,6 +149,8 @@ protected double cost() {
148149
/**
149150
* used to load the rule files.
150151
*/
152+
@RestrictedApi(explanation = "Should only be called in tests", link = "",
153+
allowedOnPath = ".*(/src/test/.*|HeterogeneousRegionCountCostFunction).java")
151154
void loadRules() {
152155
final List<String> lines = readFile(this.rulesPath);
153156
if (null == lines) {

hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/LocalityBasedCostFunction.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ abstract class LocalityBasedCostFunction extends CostFunction {
4747
abstract int regionIndexToEntityIndex(int region);
4848

4949
@Override
50-
void init(BalancerClusterState cluster) {
51-
super.init(cluster);
50+
void prepare(BalancerClusterState cluster) {
51+
super.prepare(cluster);
5252
locality = 0.0;
5353
bestLocality = 0.0;
5454

hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/MoveCostFunction.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,27 +38,35 @@ class MoveCostFunction extends CostFunction {
3838
private static final float DEFAULT_MAX_MOVE_PERCENT = 0.25f;
3939

4040
private final float maxMovesPercent;
41-
private final Configuration conf;
41+
private final OffPeakHours offPeakHours;
42+
private final float moveCost;
43+
private final float moveCostOffPeak;
4244

4345
MoveCostFunction(Configuration conf) {
44-
this.conf = conf;
4546
// What percent of the number of regions a single run of the balancer can move.
4647
maxMovesPercent = conf.getFloat(MAX_MOVES_PERCENT_KEY, DEFAULT_MAX_MOVE_PERCENT);
47-
48+
offPeakHours = OffPeakHours.getInstance(conf);
49+
moveCost = conf.getFloat(MOVE_COST_KEY, DEFAULT_MOVE_COST);
50+
moveCostOffPeak = conf.getFloat(MOVE_COST_OFFPEAK_KEY, DEFAULT_MOVE_COST_OFFPEAK);
4851
// Initialize the multiplier so that addCostFunction will add this cost function.
4952
// It may change during later evaluations, due to OffPeakHours.
50-
this.setMultiplier(conf.getFloat(MOVE_COST_KEY, DEFAULT_MOVE_COST));
53+
this.setMultiplier(moveCost);
5154
}
5255

5356
@Override
54-
protected double cost() {
57+
void prepare(BalancerClusterState cluster) {
58+
super.prepare(cluster);
5559
// Move cost multiplier should be the same cost or higher than the rest of the costs to ensure
5660
// that large benefits are need to overcome the cost of a move.
57-
if (OffPeakHours.getInstance(conf).isOffPeakHour()) {
58-
this.setMultiplier(conf.getFloat(MOVE_COST_OFFPEAK_KEY, DEFAULT_MOVE_COST_OFFPEAK));
61+
if (offPeakHours.isOffPeakHour()) {
62+
this.setMultiplier(moveCostOffPeak);
5963
} else {
60-
this.setMultiplier(conf.getFloat(MOVE_COST_KEY, DEFAULT_MOVE_COST));
64+
this.setMultiplier(moveCost);
6165
}
66+
}
67+
68+
@Override
69+
protected double cost() {
6270
// Try and size the max number of Moves, but always be prepared to move some.
6371
int maxMoves = Math.max((int) (cluster.numRegions * maxMovesPercent), DEFAULT_MAX_MOVES);
6472

hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/PrimaryRegionCountSkewCostFunction.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,25 @@ class PrimaryRegionCountSkewCostFunction extends CostFunction {
3131
"hbase.master.balancer.stochastic.primaryRegionCountCost";
3232
private static final float DEFAULT_PRIMARY_REGION_COUNT_SKEW_COST = 500;
3333

34+
private final float primaryRegionCountCost;
3435
private double[] stats;
3536

3637
PrimaryRegionCountSkewCostFunction(Configuration conf) {
3738
// Load multiplier should be the greatest as primary regions serve majority of reads/writes.
38-
this.setMultiplier(
39-
conf.getFloat(PRIMARY_REGION_COUNT_SKEW_COST_KEY, DEFAULT_PRIMARY_REGION_COUNT_SKEW_COST));
39+
primaryRegionCountCost =
40+
conf.getFloat(PRIMARY_REGION_COUNT_SKEW_COST_KEY, DEFAULT_PRIMARY_REGION_COUNT_SKEW_COST);
41+
this.setMultiplier(primaryRegionCountCost);
42+
}
43+
44+
@Override
45+
void prepare(BalancerClusterState cluster) {
46+
super.prepare(cluster);
47+
if (!isNeeded()) {
48+
return;
49+
}
50+
if (stats == null || stats.length != cluster.numServers) {
51+
stats = new double[cluster.numServers];
52+
}
4053
}
4154

4255
@Override
@@ -46,13 +59,6 @@ boolean isNeeded() {
4659

4760
@Override
4861
protected double cost() {
49-
if (!cluster.hasRegionReplicas) {
50-
return 0;
51-
}
52-
if (stats == null || stats.length != cluster.numServers) {
53-
stats = new double[cluster.numServers];
54-
}
55-
5662
for (int i = 0; i < cluster.numServers; i++) {
5763
stats[i] = 0;
5864
for (int regionIdx : cluster.regionsPerServer[i]) {

hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/RegionCountSkewCostFunction.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,19 @@ class RegionCountSkewCostFunction extends CostFunction {
3434
"hbase.master.balancer.stochastic.regionCountCost";
3535
static final float DEFAULT_REGION_COUNT_SKEW_COST = 500;
3636

37-
private double[] stats = null;
37+
private double[] stats;
3838

3939
RegionCountSkewCostFunction(Configuration conf) {
4040
// Load multiplier should be the greatest as it is the most general way to balance data.
4141
this.setMultiplier(conf.getFloat(REGION_COUNT_SKEW_COST_KEY, DEFAULT_REGION_COUNT_SKEW_COST));
4242
}
4343

4444
@Override
45-
void init(BalancerClusterState cluster) {
46-
super.init(cluster);
45+
void prepare(BalancerClusterState cluster) {
46+
super.prepare(cluster);
47+
if (stats == null || stats.length != cluster.numServers) {
48+
stats = new double[cluster.numServers];
49+
}
4750
LOG.debug("{} sees a total of {} servers and {} regions.", getClass().getSimpleName(),
4851
cluster.numServers, cluster.numRegions);
4952
if (LOG.isTraceEnabled()) {
@@ -56,9 +59,6 @@ void init(BalancerClusterState cluster) {
5659

5760
@Override
5861
protected double cost() {
59-
if (stats == null || stats.length != cluster.numServers) {
60-
stats = new double[cluster.numServers];
61-
}
6262
for (int i = 0; i < cluster.numServers; i++) {
6363
stats[i] = cluster.regionsPerServer[i].length;
6464
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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 java.util.Arrays;
21+
import org.apache.yetus.audience.InterfaceAudience;
22+
23+
/**
24+
* A cost function for region replicas. We give a high cost for hosting replicas of the same region
25+
* in the same server, host or rack. We do not prevent the case though, since if numReplicas >
26+
* numRegionServers, we still want to keep the replica open.
27+
*/
28+
@InterfaceAudience.Private
29+
abstract class RegionReplicaGroupingCostFunction extends CostFunction {
30+
31+
protected long maxCost = 0;
32+
protected long[] costsPerGroup; // group is either server, host or rack
33+
34+
@Override
35+
final void prepare(BalancerClusterState cluster) {
36+
super.prepare(cluster);
37+
if (!isNeeded()) {
38+
return;
39+
}
40+
loadCosts();
41+
}
42+
43+
protected abstract void loadCosts();
44+
45+
protected final long getMaxCost(BalancerClusterState cluster) {
46+
// max cost is the case where every region replica is hosted together regardless of host
47+
int[] primariesOfRegions = new int[cluster.numRegions];
48+
System.arraycopy(cluster.regionIndexToPrimaryIndex, 0, primariesOfRegions, 0,
49+
cluster.regions.length);
50+
51+
Arrays.sort(primariesOfRegions);
52+
53+
// compute numReplicas from the sorted array
54+
return costPerGroup(primariesOfRegions);
55+
}
56+
57+
@Override
58+
boolean isNeeded() {
59+
return cluster.hasRegionReplicas;
60+
}
61+
62+
@Override
63+
protected double cost() {
64+
if (maxCost <= 0) {
65+
return 0;
66+
}
67+
68+
long totalCost = 0;
69+
for (int i = 0; i < costsPerGroup.length; i++) {
70+
totalCost += costsPerGroup[i];
71+
}
72+
return scale(0, maxCost, totalCost);
73+
}
74+
75+
/**
76+
* For each primary region, it computes the total number of replicas in the array (numReplicas)
77+
* and returns a sum of numReplicas-1 squared. For example, if the server hosts regions a, b, c,
78+
* d, e, f where a and b are same replicas, and c,d,e are same replicas, it returns (2-1) * (2-1)
79+
* + (3-1) * (3-1) + (1-1) * (1-1).
80+
* @param primariesOfRegions a sorted array of primary regions ids for the regions hosted
81+
* @return a sum of numReplicas-1 squared for each primary region in the group.
82+
*/
83+
protected final long costPerGroup(int[] primariesOfRegions) {
84+
long cost = 0;
85+
int currentPrimary = -1;
86+
int currentPrimaryIndex = -1;
87+
// primariesOfRegions is a sorted array of primary ids of regions. Replicas of regions
88+
// sharing the same primary will have consecutive numbers in the array.
89+
for (int j = 0; j <= primariesOfRegions.length; j++) {
90+
int primary = j < primariesOfRegions.length ? primariesOfRegions[j] : -1;
91+
if (primary != currentPrimary) { // we see a new primary
92+
int numReplicas = j - currentPrimaryIndex;
93+
// square the cost
94+
if (numReplicas > 1) { // means consecutive primaries, indicating co-location
95+
cost += (numReplicas - 1) * (numReplicas - 1);
96+
}
97+
currentPrimary = primary;
98+
currentPrimaryIndex = j;
99+
}
100+
}
101+
102+
return cost;
103+
}
104+
}

0 commit comments

Comments
 (0)