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 static org.junit.Assert.assertEquals;
21  import static org.junit.Assert.assertNotNull;
22  import static org.junit.Assert.assertNull;
23  import static org.junit.Assert.assertTrue;
24  import static org.mockito.Mockito.mock;
25  import static org.mockito.Mockito.when;
26  
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.HashMap;
30  import java.util.Iterator;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Map.Entry;
34  import java.util.Queue;
35  import java.util.TreeMap;
36  
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  import org.apache.hadoop.conf.Configuration;
40  import org.apache.hadoop.hbase.ClusterStatus;
41  import org.apache.hadoop.hbase.HBaseConfiguration;
42  import org.apache.hadoop.hbase.HRegionInfo;
43  import org.apache.hadoop.hbase.RegionLoad;
44  import org.apache.hadoop.hbase.ServerLoad;
45  import org.apache.hadoop.hbase.ServerName;
46  import org.apache.hadoop.hbase.client.RegionReplicaUtil;
47  import org.apache.hadoop.hbase.master.MockNoopMasterServices;
48  import org.apache.hadoop.hbase.master.RackManager;
49  import org.apache.hadoop.hbase.master.RegionPlan;
50  import org.apache.hadoop.hbase.master.balancer.BaseLoadBalancer.Cluster;
51  import org.apache.hadoop.hbase.testclassification.FlakeyTests;
52  import org.apache.hadoop.hbase.testclassification.MediumTests;
53  import org.apache.hadoop.hbase.util.Bytes;
54  import org.junit.Ignore;
55  import org.junit.Test;
56  import org.junit.experimental.categories.Category;
57  
58  @Category({FlakeyTests.class, MediumTests.class})
59  public class TestStochasticLoadBalancer extends BalancerTestBase {
60    public static final String REGION_KEY = "testRegion";
61    private static final Log LOG = LogFactory.getLog(TestStochasticLoadBalancer.class);
62  
63    @Test
64    public void testKeepRegionLoad() throws Exception {
65  
66      ServerName sn = ServerName.valueOf("test:8080", 100);
67      int numClusterStatusToAdd = 20000;
68      for (int i = 0; i < numClusterStatusToAdd; i++) {
69        ServerLoad sl = mock(ServerLoad.class);
70  
71        RegionLoad rl = mock(RegionLoad.class);
72        when(rl.getStores()).thenReturn(i);
73  
74        Map<byte[], RegionLoad> regionLoadMap =
75            new TreeMap<byte[], RegionLoad>(Bytes.BYTES_COMPARATOR);
76        regionLoadMap.put(Bytes.toBytes(REGION_KEY), rl);
77        when(sl.getRegionsLoad()).thenReturn(regionLoadMap);
78  
79        ClusterStatus clusterStatus = mock(ClusterStatus.class);
80        when(clusterStatus.getServers()).thenReturn(Arrays.asList(sn));
81        when(clusterStatus.getLoad(sn)).thenReturn(sl);
82  
83        loadBalancer.setClusterStatus(clusterStatus);
84      }
85      assertTrue(loadBalancer.loads.get(REGION_KEY) != null);
86      assertTrue(loadBalancer.loads.get(REGION_KEY).size() == 15);
87  
88      Queue<RegionLoad> loads = loadBalancer.loads.get(REGION_KEY);
89      int i = 0;
90      while(loads.size() > 0) {
91        RegionLoad rl = loads.remove();
92        assertEquals(i + (numClusterStatusToAdd - 15), rl.getStores());
93        i ++;
94      }
95    }
96  
97    /**
98     * Test the load balancing algorithm.
99     *
100    * Invariant is that all servers should be hosting either floor(average) or
101    * ceiling(average)
102    *
103    * @throws Exception
104    */
105   @Test
106   public void testBalanceCluster() throws Exception {
107 
108     for (int[] mockCluster : clusterStateMocks) {
109       Map<ServerName, List<HRegionInfo>> servers = mockClusterServers(mockCluster);
110       List<ServerAndLoad> list = convertToList(servers);
111       LOG.info("Mock Cluster : " + printMock(list) + " " + printStats(list));
112       List<RegionPlan> plans = loadBalancer.balanceCluster(servers);
113       List<ServerAndLoad> balancedCluster = reconcile(list, plans, servers);
114       LOG.info("Mock Balance : " + printMock(balancedCluster));
115       assertClusterAsBalanced(balancedCluster);
116       List<RegionPlan> secondPlans =  loadBalancer.balanceCluster(servers);
117       assertNull(secondPlans);
118       for (Map.Entry<ServerName, List<HRegionInfo>> entry : servers.entrySet()) {
119         returnRegions(entry.getValue());
120         returnServer(entry.getKey());
121       }
122     }
123   }
124 
125   @Test
126   public void testLocalityCost() throws Exception {
127     Configuration conf = HBaseConfiguration.create();
128     MockNoopMasterServices master = new MockNoopMasterServices();
129     StochasticLoadBalancer.CostFunction
130         costFunction = new StochasticLoadBalancer.LocalityCostFunction(conf, master);
131 
132     for (int[][] clusterRegionLocations : clusterRegionLocationMocks) {
133       MockCluster cluster = new MockCluster(clusterRegionLocations);
134       costFunction.init(cluster);
135       double cost = costFunction.cost();
136 
137       assertEquals(0.5f, cost, 0.001);
138     }
139   }
140 
141   @Test
142   public void testMoveCost() throws Exception {
143     Configuration conf = HBaseConfiguration.create();
144     StochasticLoadBalancer.CostFunction
145         costFunction = new StochasticLoadBalancer.MoveCostFunction(conf);
146     for (int[] mockCluster : clusterStateMocks) {
147       BaseLoadBalancer.Cluster cluster = mockCluster(mockCluster);
148       costFunction.init(cluster);
149       double cost = costFunction.cost();
150       assertEquals(0.0f, cost, 0.001);
151 
152       // cluster region number is smaller than maxMoves=600
153       cluster.setNumRegions(200);
154       cluster.setNumMovedRegions(10);
155       cost = costFunction.cost();
156       assertEquals(0.05f, cost, 0.001);
157       cluster.setNumMovedRegions(100);
158       cost = costFunction.cost();
159       assertEquals(0.5f, cost, 0.001);
160       cluster.setNumMovedRegions(200);
161       cost = costFunction.cost();
162       assertEquals(1.0f, cost, 0.001);
163 
164 
165       // cluster region number is bigger than maxMoves=2500
166       cluster.setNumRegions(10000);
167       cluster.setNumMovedRegions(250);
168       cost = costFunction.cost();
169       assertEquals(0.1f, cost, 0.001);
170       cluster.setNumMovedRegions(1250);
171       cost = costFunction.cost();
172       assertEquals(0.5f, cost, 0.001);
173       cluster.setNumMovedRegions(2500);
174       cost = costFunction.cost();
175       assertEquals(1.0f, cost, 0.01);
176     }
177   }
178 
179   @Test
180   public void testSkewCost() {
181     Configuration conf = HBaseConfiguration.create();
182     StochasticLoadBalancer.CostFunction
183         costFunction = new StochasticLoadBalancer.RegionCountSkewCostFunction(conf);
184     for (int[] mockCluster : clusterStateMocks) {
185       costFunction.init(mockCluster(mockCluster));
186       double cost = costFunction.cost();
187       assertTrue(cost >= 0);
188       assertTrue(cost <= 1.01);
189     }
190 
191     costFunction.init(mockCluster(new int[]{0, 0, 0, 0, 1}));
192     assertEquals(0,costFunction.cost(), 0.01);
193     costFunction.init(mockCluster(new int[]{0, 0, 0, 1, 1}));
194     assertEquals(0, costFunction.cost(), 0.01);
195     costFunction.init(mockCluster(new int[]{0, 0, 1, 1, 1}));
196     assertEquals(0, costFunction.cost(), 0.01);
197     costFunction.init(mockCluster(new int[]{0, 1, 1, 1, 1}));
198     assertEquals(0, costFunction.cost(), 0.01);
199     costFunction.init(mockCluster(new int[]{1, 1, 1, 1, 1}));
200     assertEquals(0, costFunction.cost(), 0.01);
201     costFunction.init(mockCluster(new int[]{10000, 0, 0, 0, 0}));
202     assertEquals(1, costFunction.cost(), 0.01);
203   }
204 
205   @Test
206   public void testTableSkewCost() {
207     Configuration conf = HBaseConfiguration.create();
208     StochasticLoadBalancer.CostFunction
209         costFunction = new StochasticLoadBalancer.TableSkewCostFunction(conf);
210     for (int[] mockCluster : clusterStateMocks) {
211       BaseLoadBalancer.Cluster cluster = mockCluster(mockCluster);
212       costFunction.init(cluster);
213       double cost = costFunction.cost();
214       assertTrue(cost >= 0);
215       assertTrue(cost <= 1.01);
216     }
217   }
218 
219   @Test
220   public void testCostFromArray() {
221     Configuration conf = HBaseConfiguration.create();
222     StochasticLoadBalancer.CostFromRegionLoadFunction
223         costFunction = new StochasticLoadBalancer.MemstoreSizeCostFunction(conf);
224     costFunction.init(mockCluster(new int[]{0, 0, 0, 0, 1}));
225 
226     double[] statOne = new double[100];
227     for (int i =0; i < 100; i++) {
228       statOne[i] = 10;
229     }
230     assertEquals(0, costFunction.costFromArray(statOne), 0.01);
231 
232     double[] statTwo= new double[101];
233     for (int i =0; i < 100; i++) {
234       statTwo[i] = 0;
235     }
236     statTwo[100] = 100;
237     assertEquals(1, costFunction.costFromArray(statTwo), 0.01);
238 
239     double[] statThree = new double[200];
240     for (int i =0; i < 100; i++) {
241       statThree[i] = (0);
242       statThree[i+100] = 100;
243     }
244     assertEquals(0.5, costFunction.costFromArray(statThree), 0.01);
245   }
246 
247   @Test(timeout =  60000)
248   public void testLosingRs() throws Exception {
249     int numNodes = 3;
250     int numRegions = 20;
251     int numRegionsPerServer = 3; //all servers except one
252     int replication = 1;
253     int numTables = 2;
254 
255     Map<ServerName, List<HRegionInfo>> serverMap =
256         createServerMap(numNodes, numRegions, numRegionsPerServer, replication, numTables);
257     List<ServerAndLoad> list = convertToList(serverMap);
258 
259 
260     List<RegionPlan> plans = loadBalancer.balanceCluster(serverMap);
261     assertNotNull(plans);
262 
263     // Apply the plan to the mock cluster.
264     List<ServerAndLoad> balancedCluster = reconcile(list, plans, serverMap);
265 
266     assertClusterAsBalanced(balancedCluster);
267 
268     ServerName sn = serverMap.keySet().toArray(new ServerName[serverMap.size()])[0];
269 
270     ServerName deadSn = ServerName.valueOf(sn.getHostname(), sn.getPort(), sn.getStartcode() - 100);
271 
272     serverMap.put(deadSn, new ArrayList<HRegionInfo>(0));
273 
274     plans = loadBalancer.balanceCluster(serverMap);
275     assertNull(plans);
276   }
277 
278   @Test
279   public void testReplicaCost() {
280     Configuration conf = HBaseConfiguration.create();
281     StochasticLoadBalancer.CostFunction
282         costFunction = new StochasticLoadBalancer.RegionReplicaHostCostFunction(conf);
283     for (int[] mockCluster : clusterStateMocks) {
284       BaseLoadBalancer.Cluster cluster = mockCluster(mockCluster);
285       costFunction.init(cluster);
286       double cost = costFunction.cost();
287       assertTrue(cost >= 0);
288       assertTrue(cost <= 1.01);
289     }
290   }
291 
292   @Test
293   public void testReplicaCostForReplicas() {
294     Configuration conf = HBaseConfiguration.create();
295     StochasticLoadBalancer.CostFunction
296         costFunction = new StochasticLoadBalancer.RegionReplicaHostCostFunction(conf);
297 
298     int [] servers = new int[] {3,3,3,3,3};
299     TreeMap<ServerName, List<HRegionInfo>> clusterState = mockClusterServers(servers);
300 
301     BaseLoadBalancer.Cluster cluster;
302 
303     cluster = new BaseLoadBalancer.Cluster(clusterState, null, null, null);
304     costFunction.init(cluster);
305     double costWithoutReplicas = costFunction.cost();
306     assertEquals(0, costWithoutReplicas, 0);
307 
308     // replicate the region from first server to the last server
309     HRegionInfo replica1 = RegionReplicaUtil.getRegionInfoForReplica(
310       clusterState.firstEntry().getValue().get(0),1);
311     clusterState.lastEntry().getValue().add(replica1);
312 
313     cluster = new BaseLoadBalancer.Cluster(clusterState, null, null, null);
314     costFunction.init(cluster);
315     double costWith1ReplicaDifferentServer = costFunction.cost();
316 
317     assertEquals(0, costWith1ReplicaDifferentServer, 0);
318 
319     // add a third replica to the last server
320     HRegionInfo replica2 = RegionReplicaUtil.getRegionInfoForReplica(replica1, 2);
321     clusterState.lastEntry().getValue().add(replica2);
322 
323     cluster = new BaseLoadBalancer.Cluster(clusterState, null, null, null);
324     costFunction.init(cluster);
325     double costWith1ReplicaSameServer = costFunction.cost();
326 
327     assertTrue(costWith1ReplicaDifferentServer < costWith1ReplicaSameServer);
328 
329     // test with replication = 4 for following:
330 
331     HRegionInfo replica3;
332     Iterator<Entry<ServerName, List<HRegionInfo>>> it;
333     Entry<ServerName, List<HRegionInfo>> entry;
334 
335     clusterState = mockClusterServers(servers);
336     it = clusterState.entrySet().iterator();
337     entry = it.next(); //first server
338     HRegionInfo hri = entry.getValue().get(0);
339     replica1 = RegionReplicaUtil.getRegionInfoForReplica(hri, 1);
340     replica2 = RegionReplicaUtil.getRegionInfoForReplica(hri, 2);
341     replica3 = RegionReplicaUtil.getRegionInfoForReplica(hri, 3);
342     entry.getValue().add(replica1);
343     entry.getValue().add(replica2);
344     it.next().getValue().add(replica3); //2nd server
345 
346     cluster = new BaseLoadBalancer.Cluster(clusterState, null, null, null);
347     costFunction.init(cluster);
348     double costWith3ReplicasSameServer = costFunction.cost();
349 
350     clusterState = mockClusterServers(servers);
351     hri = clusterState.firstEntry().getValue().get(0);
352     replica1 = RegionReplicaUtil.getRegionInfoForReplica(hri, 1);
353     replica2 = RegionReplicaUtil.getRegionInfoForReplica(hri, 2);
354     replica3 = RegionReplicaUtil.getRegionInfoForReplica(hri, 3);
355 
356     clusterState.firstEntry().getValue().add(replica1);
357     clusterState.lastEntry().getValue().add(replica2);
358     clusterState.lastEntry().getValue().add(replica3);
359 
360     cluster = new BaseLoadBalancer.Cluster(clusterState, null, null, null);
361     costFunction.init(cluster);
362     double costWith2ReplicasOnTwoServers = costFunction.cost();
363 
364     assertTrue(costWith2ReplicasOnTwoServers < costWith3ReplicasSameServer);
365   }
366 
367   @Test
368   public void testNeedsBalanceForColocatedReplicas() {
369     // check for the case where there are two hosts and with one rack, and where
370     // both the replicas are hosted on the same server
371     List<HRegionInfo> regions = randomRegions(1);
372     ServerName s1 = ServerName.valueOf("host1", 1000, 11111);
373     ServerName s2 = ServerName.valueOf("host11", 1000, 11111);
374     Map<ServerName, List<HRegionInfo>> map = new HashMap<ServerName, List<HRegionInfo>>();
375     map.put(s1, regions);
376     regions.add(RegionReplicaUtil.getRegionInfoForReplica(regions.get(0), 1));
377     // until the step above s1 holds two replicas of a region
378     regions = randomRegions(1);
379     map.put(s2, regions);
380     assertTrue(loadBalancer.needsBalance(new Cluster(map, null, null, null)));
381     // check for the case where there are two hosts on the same rack and there are two racks
382     // and both the replicas are on the same rack
383     map.clear();
384     regions = randomRegions(1);
385     List<HRegionInfo> regionsOnS2 = new ArrayList<HRegionInfo>(1);
386     regionsOnS2.add(RegionReplicaUtil.getRegionInfoForReplica(regions.get(0), 1));
387     map.put(s1, regions);
388     map.put(s2, regionsOnS2);
389     // add another server so that the cluster has some host on another rack
390     map.put(ServerName.valueOf("host2", 1000, 11111), randomRegions(1));
391     assertTrue(loadBalancer.needsBalance(new Cluster(map, null, null,
392         new ForTestRackManagerOne())));
393   }
394 
395   @Test (timeout = 60000)
396   public void testSmallCluster() {
397     int numNodes = 10;
398     int numRegions = 1000;
399     int numRegionsPerServer = 40; //all servers except one
400     int replication = 1;
401     int numTables = 10;
402     testWithCluster(numNodes, numRegions, numRegionsPerServer, replication, numTables, true, true);
403   }
404 
405   @Test (timeout = 60000)
406   public void testSmallCluster2() {
407     int numNodes = 20;
408     int numRegions = 2000;
409     int numRegionsPerServer = 40; //all servers except one
410     int replication = 1;
411     int numTables = 10;
412     testWithCluster(numNodes, numRegions, numRegionsPerServer, replication, numTables, true, true);
413   }
414 
415   @Test (timeout = 60000)
416   public void testSmallCluster3() {
417     int numNodes = 20;
418     int numRegions = 2000;
419     int numRegionsPerServer = 1; // all servers except one
420     int replication = 1;
421     int numTables = 10;
422     /* fails because of max moves */
423     testWithCluster(numNodes, numRegions, numRegionsPerServer, replication, numTables, false, false);
424   }
425 
426   @Test (timeout = 800000)
427   public void testMidCluster() {
428     int numNodes = 100;
429     int numRegions = 10000;
430     int numRegionsPerServer = 60; // all servers except one
431     int replication = 1;
432     int numTables = 40;
433     testWithCluster(numNodes, numRegions, numRegionsPerServer, replication, numTables, true, true);
434   }
435 
436   @Test (timeout = 800000)
437   public void testMidCluster2() {
438     int numNodes = 200;
439     int numRegions = 100000;
440     int numRegionsPerServer = 40; // all servers except one
441     int replication = 1;
442     int numTables = 400;
443     testWithCluster(numNodes,
444         numRegions,
445         numRegionsPerServer,
446         replication,
447         numTables,
448         false, /* num large num regions means may not always get to best balance with one run */
449         false);
450   }
451 
452 
453   @Test (timeout = 800000)
454   public void testMidCluster3() {
455     int numNodes = 100;
456     int numRegions = 2000;
457     int numRegionsPerServer = 9; // all servers except one
458     int replication = 1;
459     int numTables = 110;
460     testWithCluster(numNodes, numRegions, numRegionsPerServer, replication, numTables, true, true);
461     // TODO(eclark): Make sure that the tables are well distributed.
462   }
463 
464   @Test
465   public void testLargeCluster() {
466     int numNodes = 1000;
467     int numRegions = 100000; //100 regions per RS
468     int numRegionsPerServer = 80; //all servers except one
469     int numTables = 100;
470     int replication = 1;
471     testWithCluster(numNodes, numRegions, numRegionsPerServer, replication, numTables, true, true);
472   }
473 
474   @Test (timeout = 800000)
475   public void testRegionReplicasOnSmallCluster() {
476     int numNodes = 10;
477     int numRegions = 1000;
478     int replication = 3; // 3 replicas per region
479     int numRegionsPerServer = 80; //all regions are mostly balanced
480     int numTables = 10;
481     testWithCluster(numNodes, numRegions, numRegionsPerServer, replication, numTables, true, true);
482   }
483 
484   @Ignore @Test (timeout = 800000) // Test is flakey. TODO: Fix!
485   public void testRegionReplicationOnMidClusterSameHosts() {
486     conf.setLong(StochasticLoadBalancer.MAX_STEPS_KEY, 2000000L);
487     conf.setLong("hbase.master.balancer.stochastic.maxRunningTime", 90 * 1000); // 90 sec
488     conf.setFloat("hbase.master.balancer.stochastic.maxMovePercent", 1.0f);
489     loadBalancer.setConf(conf);
490     int numHosts = 100;
491     int numRegions = 100 * 100;
492     int replication = 3; // 3 replicas per region
493     int numRegionsPerServer = 5;
494     int numTables = 10;
495     Map<ServerName, List<HRegionInfo>> serverMap =
496         createServerMap(numHosts, numRegions, numRegionsPerServer, replication, numTables);
497     int numNodesPerHost = 4;
498 
499     // create a new map with 4 RS per host.
500     Map<ServerName, List<HRegionInfo>> newServerMap = new TreeMap<ServerName, List<HRegionInfo>>(serverMap);
501     for (Map.Entry<ServerName, List<HRegionInfo>> entry : serverMap.entrySet()) {
502       for (int i=1; i < numNodesPerHost; i++) {
503         ServerName s1 = entry.getKey();
504         ServerName s2 = ServerName.valueOf(s1.getHostname(), s1.getPort() + i, 1); // create an RS for the same host
505         newServerMap.put(s2, new ArrayList<HRegionInfo>());
506       }
507     }
508 
509     testWithCluster(newServerMap, null, true, true);
510   }
511 
512   private static class ForTestRackManager extends RackManager {
513     int numRacks;
514     public ForTestRackManager(int numRacks) {
515       this.numRacks = numRacks;
516     }
517     @Override
518     public String getRack(ServerName server) {
519       return "rack_" + (server.hashCode() % numRacks);
520     }
521   }
522 
523   private static class ForTestRackManagerOne extends RackManager {
524   @Override
525     public String getRack(ServerName server) {
526       return server.getHostname().endsWith("1") ? "rack1" : "rack2";
527     }
528   }
529 
530   @Test (timeout = 800000)
531   public void testRegionReplicationOnMidClusterWithRacks() {
532     conf.setLong(StochasticLoadBalancer.MAX_STEPS_KEY, 10000000L);
533     conf.setFloat("hbase.master.balancer.stochastic.maxMovePercent", 1.0f);
534     conf.setLong("hbase.master.balancer.stochastic.maxRunningTime", 120 * 1000); // 120 sec
535     loadBalancer.setConf(conf);
536     int numNodes = 30;
537     int numRegions = numNodes * 30;
538     int replication = 3; // 3 replicas per region
539     int numRegionsPerServer = 28;
540     int numTables = 10;
541     int numRacks = 4; // all replicas should be on a different rack
542     Map<ServerName, List<HRegionInfo>> serverMap =
543         createServerMap(numNodes, numRegions, numRegionsPerServer, replication, numTables);
544     RackManager rm = new ForTestRackManager(numRacks);
545 
546     testWithCluster(serverMap, rm, false, true);
547   }
548 
549 }