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