View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional information regarding
4    * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * copy of the License at
7    * <p>
8    * http://www.apache.org/licenses/LICENSE-2.0
9    * <p>
10   * Unless required by applicable law or agreed to in writing, software distributed under the License
11   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing permissions and limitations under
13   * the License.
14   */
15  package org.apache.hadoop.hbase.master.balancer;
16  
17  import static junit.framework.TestCase.assertNotNull;
18  import static junit.framework.TestCase.assertTrue;
19  import static org.junit.Assert.assertNull;
20  
21  import java.io.IOException;
22  import java.util.Arrays;
23  import java.util.Collections;
24  import java.util.LinkedList;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Queue;
28  import java.util.Random;
29  import java.util.TreeMap;
30  import java.util.concurrent.ThreadLocalRandom;
31  
32  import org.apache.hadoop.conf.Configuration;
33  import org.apache.hadoop.hbase.HRegionInfo;
34  import org.apache.hadoop.hbase.ServerName;
35  import org.apache.hadoop.hbase.client.RegionReplicaUtil;
36  import org.apache.hadoop.hbase.master.RackManager;
37  import org.apache.hadoop.hbase.master.RegionPlan;
38  import org.apache.hadoop.hbase.testclassification.MediumTests;
39  import org.junit.BeforeClass;
40  import org.junit.Test;
41  import org.junit.experimental.categories.Category;
42  import org.slf4j.Logger;
43  import org.slf4j.LoggerFactory;
44  
45  @Category({MediumTests.class})
46  public class TestStochasticLoadBalancerHeterogeneousCost extends BalancerTestBase {
47  
48    private static final Logger LOG =
49        LoggerFactory.getLogger(TestStochasticLoadBalancerHeterogeneousCost.class);
50    private static final double allowedWindow = 1.20;
51  
52    @BeforeClass
53    public static void beforeAllTests() {
54      conf = new Configuration();
55      conf.setFloat("hbase.master.balancer.stochastic.regionCountCost", 0);
56      conf.setFloat("hbase.master.balancer.stochastic.primaryRegionCountCost", 0);
57      conf.setFloat("hbase.master.balancer.stochastic.tableSkewCost", 0);
58      conf.setBoolean("hbase.master.balancer.stochastic.runMaxSteps", true);
59      conf.set(StochasticLoadBalancer.COST_FUNCTIONS_COST_FUNCTIONS_KEY,
60        HeterogeneousRegionCountCostFunction.class.getName());
61  
62      conf.set(
63        HeterogeneousRegionCountCostFunction.HBASE_MASTER_BALANCER_HETEROGENEOUS_RULES_FILE,
64        TestStochasticLoadBalancerHeterogeneousCostRules.DEFAULT_RULES_TMP_LOCATION);
65      loadBalancer = new StochasticLoadBalancer();
66      loadBalancer.setConf(conf);
67    }
68  
69    @Test
70    public void testDefault() throws IOException {
71      final List<String> rules = Collections.emptyList();
72  
73      final int numNodes = 2;
74      final int numRegions = 300;
75      final int numRegionsPerServer = 250;
76  
77      // Initial state: { rs1:50 , rs0:250 }
78      // Cluster can hold 300/400 regions (75%)
79      // Expected balanced Cluster: { rs0:150 , rs1:150 }
80      this.testHeterogeneousWithCluster(numNodes, numRegions, numRegionsPerServer, rules);
81    }
82  
83    @Test
84    public void testOneGroup() throws IOException {
85      final List<String> rules = Collections.singletonList("rs.* 100");
86  
87      final int numNodes = 4;
88      final int numRegions = 300;
89      final int numRegionsPerServer = 30;
90  
91      // Initial state: { rs0:30 , rs1:30 , rs2:30 , rs3:210 }.
92      // The cluster can hold 300/400 regions (75%)
93      // Expected balanced Cluster: { rs0:75 , rs1:75 , rs2:75 , rs3:75 }
94      this.testHeterogeneousWithCluster(numNodes, numRegions, numRegionsPerServer, rules);
95    }
96  
97    @Test
98    public void testTwoGroups() throws IOException {
99      final List<String> rules = Arrays.asList("rs[0-4] 200", "rs[5-9] 50");
100 
101     final int numNodes = 10;
102     final int numRegions = 500;
103     final int numRegionsPerServer = 50;
104 
105     // Initial state: { rs0:50 , rs1:50 , rs2:50 , rs3:50 , rs4:50 , rs5:50 , rs6:50 , rs7:50 ,
106     // rs8:50 , rs9:50 }
107     // the cluster can hold 500/1250 regions (40%)
108     // Expected balanced Cluster: { rs5:20 , rs6:20 , rs7:20 , rs8:20 , rs9:20 , rs0:80 , rs1:80 ,
109     // rs2:80 , rs3:80 , rs4:80 }
110     this.testHeterogeneousWithCluster(numNodes, numRegions, numRegionsPerServer, rules);
111   }
112 
113   @Test
114   public void testFourGroups() throws IOException {
115     final List<String> rules = Arrays.asList("rs[1-3] 200", "rs[4-7] 250", "rs[8-9] 100");
116 
117     final int numNodes = 10;
118     final int numRegions = 800;
119     final int numRegionsPerServer = 80;
120 
121     // Initial state: { rs0:80 , rs1:80 , rs2:80 , rs3:80 , rs4:80 , rs5:80 , rs6:80 , rs7:80 ,
122     // rs8:80 , rs9:80 }
123     // Cluster can hold 800/2000 regions (40%)
124     // Expected balanced Cluster: { rs8:40 , rs9:40 , rs2:80 , rs3:80 , rs1:82 , rs0:94 , rs4:96 ,
125     // rs5:96 , rs6:96 , rs7:96 }
126     this.testHeterogeneousWithCluster(numNodes, numRegions, numRegionsPerServer, rules);
127   }
128 
129   @Test
130   public void testOverloaded() throws IOException {
131     final List<String> rules = Collections.singletonList("rs[0-1] 50");
132 
133     final int numNodes = 2;
134     final int numRegions = 120;
135     final int numRegionsPerServer = 60;
136 
137     TestStochasticLoadBalancerHeterogeneousCostRules.createSimpleRulesFile(rules);
138     final Map<ServerName, List<HRegionInfo>> serverMap =
139         this.createServerMap(numNodes, numRegions, numRegionsPerServer, 1, 1);
140     final List<RegionPlan> plans = BalancerTestBase.loadBalancer.balanceCluster(serverMap);
141     // As we disabled all the other cost functions, balancing only according to
142     // the heterogeneous cost function should return nothing.
143     assertNull(plans);
144   }
145 
146   private void testHeterogeneousWithCluster(final int numNodes, final int numRegions,
147       final int numRegionsPerServer, final List<String> rules) throws IOException {
148 
149     TestStochasticLoadBalancerHeterogeneousCostRules.createSimpleRulesFile(rules);
150     final Map<ServerName, List<HRegionInfo>> serverMap =
151         this.createServerMap(numNodes, numRegions, numRegionsPerServer, 1, 1);
152     this.testWithCluster(serverMap, null, true, false);
153   }
154 
155   protected void testWithCluster(final Map<ServerName, List<HRegionInfo>> serverMap,
156       final RackManager rackManager, final boolean assertFullyBalanced,
157       final boolean assertFullyBalancedForReplicas) {
158     final List<ServerAndLoad> list = this.convertToList(serverMap);
159     LOG.info("Mock Cluster : " + this.printMock(list) + " " + this.printStats(list));
160 
161     BalancerTestBase.loadBalancer.setRackManager(rackManager);
162 
163     // Run the balancer.
164     final List<RegionPlan> plans = BalancerTestBase.loadBalancer.balanceCluster(serverMap);
165     assertNotNull(plans);
166 
167     // Check to see that this actually got to a stable place.
168     if (assertFullyBalanced || assertFullyBalancedForReplicas) {
169       // Apply the plan to the mock cluster.
170       final List<ServerAndLoad> balancedCluster = this.reconcile(list, plans, serverMap);
171 
172       // Print out the cluster loads to make debugging easier.
173       LOG.info("Mock Balanced cluster : " + this.printMock(balancedCluster));
174 
175       if (assertFullyBalanced) {
176         final List<RegionPlan> secondPlans =
177             BalancerTestBase.loadBalancer.balanceCluster(serverMap);
178         assertNull(secondPlans);
179 
180         // create external cost function to retrieve limit
181         // for each RS
182         final HeterogeneousRegionCountCostFunction cf =
183             new HeterogeneousRegionCountCostFunction(conf);
184         assertNotNull(cf);
185         BaseLoadBalancer.Cluster cluster =
186             new BaseLoadBalancer.Cluster(serverMap, null, null, null);
187         cf.init(cluster);
188 
189         // checking that we all hosts have a number of regions below their limit
190         for (final ServerAndLoad serverAndLoad : balancedCluster) {
191           final ServerName sn = serverAndLoad.getServerName();
192           final int numberRegions = serverAndLoad.getLoad();
193           final int limit = cf.findLimitForRS(sn);
194 
195           double usage = (double) numberRegions / (double) limit;
196           LOG.debug(
197             sn.getHostname() + ":" + numberRegions + "/" + limit + "(" + (usage * 100) + "%)");
198 
199           // as the balancer is stochastic, we cannot check exactly the result of the balancing,
200           // hence the allowedWindow parameter
201           assertTrue("Host " + sn.getHostname() + " should be below "
202               + cf.overallUsage * allowedWindow * 100 + "%",
203             usage <= cf.overallUsage * allowedWindow);
204         }
205       }
206 
207       if (assertFullyBalancedForReplicas) {
208         this.assertRegionReplicaPlacement(serverMap, rackManager);
209       }
210     }
211   }
212 
213   @Override
214   protected Map<ServerName, List<HRegionInfo>> createServerMap(int numNodes, int numRegions,
215       int numRegionsPerServer, int replication, int numTables) {
216     // construct a cluster of numNodes, having a total of numRegions. Each RS will hold
217     // numRegionsPerServer many regions except for the last one, which will host all the
218     // remaining regions
219     int[] cluster = new int[numNodes];
220     for (int i = 0; i < numNodes; i++) {
221       cluster[i] = numRegionsPerServer;
222     }
223     cluster[cluster.length - 1] = numRegions - ((cluster.length - 1) * numRegionsPerServer);
224     Map<ServerName, List<HRegionInfo>> clusterState = mockClusterServers(cluster, numTables);
225     if (replication > 0) {
226       // replicate the regions to the same servers
227       for (List<HRegionInfo> regions : clusterState.values()) {
228         int length = regions.size();
229         for (int i = 0; i < length; i++) {
230           for (int r = 1; r < replication; r++) {
231             regions.add(RegionReplicaUtil.getRegionInfoForReplica(regions.get(i), r));
232           }
233         }
234       }
235     }
236 
237     return clusterState;
238   }
239 
240   @Override
241   protected TreeMap<ServerName, List<HRegionInfo>> mockClusterServers(int[] mockCluster,
242       int numTables) {
243     int numServers = mockCluster.length;
244     TreeMap<ServerName, List<HRegionInfo>> servers = new TreeMap<>();
245     for (int i = 0; i < numServers; i++) {
246       int numRegions = mockCluster[i];
247       ServerAndLoad sal = createServer("rs" + i);
248       List<HRegionInfo> regions = randomRegions(numRegions, numTables);
249       servers.put(sal.getServerName(), regions);
250     }
251     return servers;
252   }
253 
254   private Queue<ServerName> serverQueue = new LinkedList<>();
255 
256   private ServerAndLoad createServer(final String host) {
257     if (!this.serverQueue.isEmpty()) {
258       ServerName sn = this.serverQueue.poll();
259       return new ServerAndLoad(sn, 0);
260     }
261     Random rand = ThreadLocalRandom.current();
262     int port = rand.nextInt(60000);
263     long startCode = rand.nextLong();
264     ServerName sn = ServerName.valueOf(host, port, startCode);
265     return new ServerAndLoad(sn, 0);
266   }
267 }