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