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}