001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.master.balancer;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertTrue;
023
024import java.util.Arrays;
025import java.util.HashMap;
026import java.util.Map;
027import org.apache.hadoop.conf.Configuration;
028import org.apache.hadoop.hbase.HBaseClassTestRule;
029import org.apache.hadoop.hbase.HConstants;
030import org.apache.hadoop.hbase.ServerName;
031import org.apache.hadoop.hbase.testclassification.MasterTests;
032import org.apache.hadoop.hbase.testclassification.MediumTests;
033import org.apache.hadoop.hbase.util.Pair;
034import org.junit.Before;
035import org.junit.BeforeClass;
036import org.junit.ClassRule;
037import org.junit.Test;
038import org.junit.experimental.categories.Category;
039
040@Category({ MasterTests.class, MediumTests.class })
041public class TestCacheAwareLoadBalancerCostFunctions extends StochasticBalancerTestBase {
042
043  @ClassRule
044  public static final HBaseClassTestRule CLASS_RULE =
045    HBaseClassTestRule.forClass(TestCacheAwareLoadBalancerCostFunctions.class);
046
047  // Mapping of test -> expected cache cost
048  private final float[] expectedCacheCost = { 0.0f, 0.0f, 0.5f, 1.0f, 0.0f, 0.572f, 0.0f, 0.075f };
049
050  /**
051   * Data set to testCacheCost: [test][0][0] = mapping of server to number of regions it hosts
052   * [test][region + 1][0] = server that region is hosted on [test][region + 1][server + 1] = size
053   * of region cached on server
054   */
055  private final int[][][] clusterRegionCacheRatioMocks = new int[][][] {
056    // Test 1: each region is entirely on server that hosts it
057    // Cost of moving the regions in this case should be high as the regions are fully cached
058    // on the server they are currently hosted on
059    new int[][] { new int[] { 2, 1, 1 }, // Server 0 has 2, server 1 has 1 and server 2 has 1
060      // region(s) hosted respectively
061      new int[] { 0, 100, 0, 0 }, // region 0 is hosted and cached only on server 0
062      new int[] { 0, 100, 0, 0 }, // region 1 is hosted and cached only on server 0
063      new int[] { 1, 0, 100, 0 }, // region 2 is hosted and cached only on server 1
064      new int[] { 2, 0, 0, 100 }, // region 3 is hosted and cached only on server 2
065    },
066
067    // Test 2: each region is cached completely on the server it is currently hosted on,
068    // but it was also cached on some other server historically
069    // Cost of moving the regions in this case should be high as the regions are fully cached
070    // on the server they are currently hosted on. Although, the regions were previously hosted and
071    // cached on some other server, since they are completely cached on the new server,
072    // there is no need to move the regions back to the previously hosting cluster
073    new int[][] { new int[] { 1, 2, 1 }, // Server 0 has 1, server 1 has 2 and server 2 has 1
074      // region(s) hosted respectively
075      new int[] { 0, 100, 0, 100 }, // region 0 is hosted and currently cached on server 0,
076      // but previously cached completely on server 2
077      new int[] { 1, 100, 100, 0 }, // region 1 is hosted and currently cached on server 1,
078      // but previously cached completely on server 0
079      new int[] { 1, 0, 100, 100 }, // region 2 is hosted and currently cached on server 1,
080      // but previously cached on server 2
081      new int[] { 2, 0, 100, 100 }, // region 3 is hosted and currently cached on server 2,
082    // but previously cached on server 1
083    },
084
085    // Test 3: The regions were hosted and fully cached on a server but later moved to other
086    // because of server crash procedure. The regions are partially cached on the server they
087    // are currently hosted on
088    new int[][] { new int[] { 1, 2, 1 }, new int[] { 0, 50, 0, 100 }, // Region 0 is currently
089      // hosted and partially
090      // cached on
091      // server 0, but was fully
092      // cached on server 2
093      // previously
094      new int[] { 1, 100, 50, 0 }, // Region 1 is currently hosted and partially cached on
095      // server 1, but was fully cached on server 0 previously
096      new int[] { 1, 0, 50, 100 }, // Region 2 is currently hosted and partially cached on
097      // server 1, but was fully cached on server 2 previously
098      new int[] { 2, 0, 100, 50 }, // Region 3 is currently hosted and partially cached on
099    // server 2, but was fully cached on server 1 previously
100    },
101
102    // Test 4: The regions were hosted and fully cached on a server, but later moved to other
103    // server because of server crash procedure. The regions are not at all cached on the server
104    // they are currently hosted on
105    new int[][] { new int[] { 1, 1, 2 }, new int[] { 0, 0, 0, 100 }, // Region 0 is currently hosted
106      // but not cached on server
107      // 0,
108      // but was fully cached on
109      // server 2 previously
110      new int[] { 1, 100, 0, 0 }, // Region 1 is currently hosted but not cached on server 1,
111      // but was fully cached on server 0 previously
112      new int[] { 2, 0, 100, 0 }, // Region 2 is currently hosted but not cached on server 2,
113      // but was fully cached on server 1 previously
114      new int[] { 2, 100, 0, 0 }, // Region 3 is currently hosted but not cached on server 2,
115    // but was fully cached on server 1 previously
116    },
117
118    // Test 5: The regions were partially cached on old servers, before moving to the new server
119    // where also, they are partially cached
120    new int[][] { new int[] { 2, 1, 1 }, new int[] { 0, 50, 50, 0 }, // Region 0 is hosted and
121      // partially cached on
122      // server 0, but
123      // was previously hosted and
124      // partially cached on
125      // server 1
126      new int[] { 0, 50, 0, 50 }, // Region 1 is hosted and partially cached on server 0, but
127      // was previously hosted and partially cached on server 2
128      new int[] { 1, 0, 50, 50 }, // Region 2 is hosted and partially cached on server 1, but
129      // was previously hosted and partially cached on server 2
130      new int[] { 2, 0, 50, 50 }, // Region 3 is hosted and partially cached on server 2, but
131    // was previously hosted and partially cached on server 1
132    },
133
134    // Test 6: The regions are less cached on the new servers as compared to what they were
135    // cached on the server before they were moved to the new servers
136    new int[][] { new int[] { 1, 2, 1 }, new int[] { 0, 30, 70, 0 }, // Region 0 is hosted and
137      // cached 30% on server 0,
138      // but was
139      // previously hosted and
140      // cached 70% on server 1
141      new int[] { 1, 70, 30, 0 }, // Region 1 is hosted and cached 30% on server 1, but was
142      // previously hosted and cached 70% on server 0
143      new int[] { 1, 0, 30, 70 }, // Region 2 is hosted and cached 30% on server 1, but was
144      // previously hosted and cached 70% on server 2
145      new int[] { 2, 0, 70, 30 }, // Region 3 is hosted and cached 30% on server 2, but was
146    // previously hosted and cached 70% on server 1
147    },
148
149    // Test 7: The regions are more cached on the new servers as compared to what they were
150    // cached on the server before they were moved to the new servers
151    new int[][] { new int[] { 2, 1, 1 }, new int[] { 0, 80, 20, 0 }, // Region 0 is hosted and 80%
152      // cached on server 0, but
153      // was
154      // previously hosted and 20%
155      // cached on server 1
156      new int[] { 0, 80, 0, 20 }, // Region 1 is hosted and 80% cached on server 0, but was
157      // previously hosted and 20% cached on server 2
158      new int[] { 1, 20, 80, 0 }, // Region 2 is hosted and 80% cached on server 1, but was
159      // previously hosted and 20% cached on server 0
160      new int[] { 2, 0, 20, 80 }, // Region 3 is hosted and 80% cached on server 2, but was
161    // previously hosted and 20% cached on server 1
162    },
163
164    // Test 8: The regions are randomly assigned to the server with some regions historically
165    // hosted on other region servers
166    new int[][] { new int[] { 1, 2, 1 }, new int[] { 0, 34, 0, 58 }, // Region 0 is hosted and
167      // partially cached on
168      // server 0,
169      // but was previously hosted
170      // and partially cached on
171      // server 2
172      // current cache ratio <
173      // historical cache ratio
174      new int[] { 1, 78, 100, 0 }, // Region 1 is hosted and fully cached on server 1,
175      // but was previously hosted and partially cached on server 0
176      // current cache ratio > historical cache ratio
177      new int[] { 1, 66, 66, 0 }, // Region 2 is hosted and partially cached on server 1,
178      // but was previously hosted and partially cached on server 0
179      // current cache ratio == historical cache ratio
180      new int[] { 2, 0, 0, 96 }, // Region 3 is hosted and partially cached on server 0
181    // No historical cache ratio
182    }, };
183
184  private static Configuration storedConfiguration;
185
186  private CacheAwareLoadBalancer loadBalancer = new CacheAwareLoadBalancer();
187
188  @BeforeClass
189  public static void saveInitialConfiguration() {
190    storedConfiguration = new Configuration(conf);
191  }
192
193  @Before
194  public void beforeEachTest() {
195    conf = new Configuration(storedConfiguration);
196    loadBalancer.loadConf(conf);
197  }
198
199  @Test
200  public void testVerifyCacheAwareSkewnessCostFunctionEnabled() {
201    CacheAwareLoadBalancer lb = new CacheAwareLoadBalancer();
202    lb.loadConf(conf);
203    assertTrue(Arrays.asList(lb.getCostFunctionNames())
204      .contains(CacheAwareLoadBalancer.CacheAwareRegionSkewnessCostFunction.class.getSimpleName()));
205  }
206
207  @Test
208  public void testVerifyCacheAwareSkewnessCostFunctionDisabled() {
209    conf.setFloat(
210      CacheAwareLoadBalancer.CacheAwareRegionSkewnessCostFunction.REGION_COUNT_SKEW_COST_KEY, 0.0f);
211
212    CacheAwareLoadBalancer lb = new CacheAwareLoadBalancer();
213    lb.loadConf(conf);
214
215    assertFalse(Arrays.asList(lb.getCostFunctionNames())
216      .contains(CacheAwareLoadBalancer.CacheAwareRegionSkewnessCostFunction.class.getSimpleName()));
217  }
218
219  @Test
220  public void testVerifyCacheCostFunctionEnabled() {
221    conf.set(HConstants.BUCKET_CACHE_PERSISTENT_PATH_KEY, "/tmp/prefetch.persistence");
222
223    CacheAwareLoadBalancer lb = new CacheAwareLoadBalancer();
224    lb.loadConf(conf);
225
226    assertTrue(Arrays.asList(lb.getCostFunctionNames())
227      .contains(CacheAwareLoadBalancer.CacheAwareCostFunction.class.getSimpleName()));
228  }
229
230  @Test
231  public void testVerifyCacheCostFunctionDisabledByNoBucketCachePersistence() {
232    assertFalse(Arrays.asList(loadBalancer.getCostFunctionNames())
233      .contains(CacheAwareLoadBalancer.CacheAwareCostFunction.class.getSimpleName()));
234  }
235
236  @Test
237  public void testVerifyCacheCostFunctionDisabledByNoMultiplier() {
238    conf.set(HConstants.BUCKET_CACHE_PERSISTENT_PATH_KEY, "/tmp/prefetch.persistence");
239    conf.setFloat("hbase.master.balancer.stochastic.cacheCost", 0.0f);
240    assertFalse(Arrays.asList(loadBalancer.getCostFunctionNames())
241      .contains(CacheAwareLoadBalancer.CacheAwareCostFunction.class.getSimpleName()));
242  }
243
244  @Test
245  public void testCacheCost() {
246    conf.set(HConstants.BUCKET_CACHE_PERSISTENT_PATH_KEY, "/tmp/prefetch.persistence");
247    CacheAwareLoadBalancer.CacheAwareCostFunction costFunction =
248      new CacheAwareLoadBalancer.CacheAwareCostFunction(conf);
249
250    for (int test = 0; test < clusterRegionCacheRatioMocks.length; test++) {
251      int[][] clusterRegionLocations = clusterRegionCacheRatioMocks[test];
252      MockClusterForCacheCost cluster = new MockClusterForCacheCost(clusterRegionLocations);
253      costFunction.prepare(cluster);
254      double cost = costFunction.cost();
255      assertEquals(expectedCacheCost[test], cost, 0.01);
256    }
257  }
258
259  private class MockClusterForCacheCost extends BalancerClusterState {
260    private final Map<Pair<Integer, Integer>, Float> regionServerCacheRatio = new HashMap<>();
261
262    public MockClusterForCacheCost(int[][] regionsArray) {
263      // regions[0] is an array where index = serverIndex and value = number of regions
264      super(mockClusterServersUnsorted(regionsArray[0], 1), null, null, null, null);
265      Map<String, Pair<ServerName, Float>> oldCacheRatio = new HashMap<>();
266      for (int i = 1; i < regionsArray.length; i++) {
267        int regionIndex = i - 1;
268        for (int j = 1; j < regionsArray[i].length; j++) {
269          int serverIndex = j - 1;
270          float cacheRatio = (float) regionsArray[i][j] / 100;
271          regionServerCacheRatio.put(new Pair<>(regionIndex, serverIndex), cacheRatio);
272          if (cacheRatio > 0.0f && serverIndex != regionsArray[i][0]) {
273            // This is the historical cacheRatio value
274            oldCacheRatio.put(regions[regionIndex].getEncodedName(),
275              new Pair<>(servers[serverIndex], cacheRatio));
276          }
277        }
278      }
279      regionCacheRatioOnOldServerMap = oldCacheRatio;
280    }
281
282    @Override
283    public int getTotalRegionHFileSizeMB(int region) {
284      return 1;
285    }
286
287    @Override
288    protected float getRegionCacheRatioOnRegionServer(int region, int regionServerIndex) {
289      float cacheRatio = 0.0f;
290
291      // Get the cache ratio if the region is currently hosted on this server
292      if (regionServerIndex == regionIndexToServerIndex[region]) {
293        return regionServerCacheRatio.get(new Pair<>(region, regionServerIndex));
294      }
295
296      // Region is not currently hosted on this server. Check if the region was cached on this
297      // server earlier. This can happen when the server was shutdown and the cache was persisted.
298      // Search using the index name and server name and not the index id and server id as these
299      // ids may change when a server is marked as dead or a new server is added.
300      String regionEncodedName = regions[region].getEncodedName();
301      ServerName serverName = servers[regionServerIndex];
302      if (
303        regionCacheRatioOnOldServerMap != null
304          && regionCacheRatioOnOldServerMap.containsKey(regionEncodedName)
305      ) {
306        Pair<ServerName, Float> serverCacheRatio =
307          regionCacheRatioOnOldServerMap.get(regionEncodedName);
308        if (ServerName.isSameAddress(serverName, serverCacheRatio.getFirst())) {
309          cacheRatio = serverCacheRatio.getSecond();
310          regionCacheRatioOnOldServerMap.remove(regionEncodedName);
311        }
312      }
313      return cacheRatio;
314    }
315  }
316}