blob: 448e576b1bc708ab9e05fd838b9799083e7267ba [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.master.balancer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.testclassification.MasterTests;
import org.apache.hadoop.hbase.testclassification.MediumTests;
import org.apache.hadoop.hbase.util.Pair;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
@Category({ MasterTests.class, MediumTests.class })
public class TestCacheAwareLoadBalancerCostFunctions extends StochasticBalancerTestBase {
@ClassRule
public static final HBaseClassTestRule CLASS_RULE =
HBaseClassTestRule.forClass(TestCacheAwareLoadBalancerCostFunctions.class);
// Mapping of test -> expected cache cost
private final float[] expectedCacheCost = { 0.0f, 0.0f, 0.5f, 1.0f, 0.0f, 0.572f, 0.0f, 0.075f };
/**
* Data set to testCacheCost: [test][0][0] = mapping of server to number of regions it hosts
* [test][region + 1][0] = server that region is hosted on [test][region + 1][server + 1] = size
* of region cached on server
*/
private final int[][][] clusterRegionCacheRatioMocks = new int[][][] {
// Test 1: each region is entirely on server that hosts it
// Cost of moving the regions in this case should be high as the regions are fully cached
// on the server they are currently hosted on
new int[][] { new int[] { 2, 1, 1 }, // Server 0 has 2, server 1 has 1 and server 2 has 1
// region(s) hosted respectively
new int[] { 0, 100, 0, 0 }, // region 0 is hosted and cached only on server 0
new int[] { 0, 100, 0, 0 }, // region 1 is hosted and cached only on server 0
new int[] { 1, 0, 100, 0 }, // region 2 is hosted and cached only on server 1
new int[] { 2, 0, 0, 100 }, // region 3 is hosted and cached only on server 2
},
// Test 2: each region is cached completely on the server it is currently hosted on,
// but it was also cached on some other server historically
// Cost of moving the regions in this case should be high as the regions are fully cached
// on the server they are currently hosted on. Although, the regions were previously hosted and
// cached on some other server, since they are completely cached on the new server,
// there is no need to move the regions back to the previously hosting cluster
new int[][] { new int[] { 1, 2, 1 }, // Server 0 has 1, server 1 has 2 and server 2 has 1
// region(s) hosted respectively
new int[] { 0, 100, 0, 100 }, // region 0 is hosted and currently cached on server 0,
// but previously cached completely on server 2
new int[] { 1, 100, 100, 0 }, // region 1 is hosted and currently cached on server 1,
// but previously cached completely on server 0
new int[] { 1, 0, 100, 100 }, // region 2 is hosted and currently cached on server 1,
// but previously cached on server 2
new int[] { 2, 0, 100, 100 }, // region 3 is hosted and currently cached on server 2,
// but previously cached on server 1
},
// Test 3: The regions were hosted and fully cached on a server but later moved to other
// because of server crash procedure. The regions are partially cached on the server they
// are currently hosted on
new int[][] { new int[] { 1, 2, 1 }, new int[] { 0, 50, 0, 100 }, // Region 0 is currently
// hosted and partially
// cached on
// server 0, but was fully
// cached on server 2
// previously
new int[] { 1, 100, 50, 0 }, // Region 1 is currently hosted and partially cached on
// server 1, but was fully cached on server 0 previously
new int[] { 1, 0, 50, 100 }, // Region 2 is currently hosted and partially cached on
// server 1, but was fully cached on server 2 previously
new int[] { 2, 0, 100, 50 }, // Region 3 is currently hosted and partially cached on
// server 2, but was fully cached on server 1 previously
},
// Test 4: The regions were hosted and fully cached on a server, but later moved to other
// server because of server crash procedure. The regions are not at all cached on the server
// they are currently hosted on
new int[][] { new int[] { 1, 1, 2 }, new int[] { 0, 0, 0, 100 }, // Region 0 is currently hosted
// but not cached on server
// 0,
// but was fully cached on
// server 2 previously
new int[] { 1, 100, 0, 0 }, // Region 1 is currently hosted but not cached on server 1,
// but was fully cached on server 0 previously
new int[] { 2, 0, 100, 0 }, // Region 2 is currently hosted but not cached on server 2,
// but was fully cached on server 1 previously
new int[] { 2, 100, 0, 0 }, // Region 3 is currently hosted but not cached on server 2,
// but was fully cached on server 1 previously
},
// Test 5: The regions were partially cached on old servers, before moving to the new server
// where also, they are partially cached
new int[][] { new int[] { 2, 1, 1 }, new int[] { 0, 50, 50, 0 }, // Region 0 is hosted and
// partially cached on
// server 0, but
// was previously hosted and
// partially cached on
// server 1
new int[] { 0, 50, 0, 50 }, // Region 1 is hosted and partially cached on server 0, but
// was previously hosted and partially cached on server 2
new int[] { 1, 0, 50, 50 }, // Region 2 is hosted and partially cached on server 1, but
// was previously hosted and partially cached on server 2
new int[] { 2, 0, 50, 50 }, // Region 3 is hosted and partially cached on server 2, but
// was previously hosted and partially cached on server 1
},
// Test 6: The regions are less cached on the new servers as compared to what they were
// cached on the server before they were moved to the new servers
new int[][] { new int[] { 1, 2, 1 }, new int[] { 0, 30, 70, 0 }, // Region 0 is hosted and
// cached 30% on server 0,
// but was
// previously hosted and
// cached 70% on server 1
new int[] { 1, 70, 30, 0 }, // Region 1 is hosted and cached 30% on server 1, but was
// previously hosted and cached 70% on server 0
new int[] { 1, 0, 30, 70 }, // Region 2 is hosted and cached 30% on server 1, but was
// previously hosted and cached 70% on server 2
new int[] { 2, 0, 70, 30 }, // Region 3 is hosted and cached 30% on server 2, but was
// previously hosted and cached 70% on server 1
},
// Test 7: The regions are more cached on the new servers as compared to what they were
// cached on the server before they were moved to the new servers
new int[][] { new int[] { 2, 1, 1 }, new int[] { 0, 80, 20, 0 }, // Region 0 is hosted and 80%
// cached on server 0, but
// was
// previously hosted and 20%
// cached on server 1
new int[] { 0, 80, 0, 20 }, // Region 1 is hosted and 80% cached on server 0, but was
// previously hosted and 20% cached on server 2
new int[] { 1, 20, 80, 0 }, // Region 2 is hosted and 80% cached on server 1, but was
// previously hosted and 20% cached on server 0
new int[] { 2, 0, 20, 80 }, // Region 3 is hosted and 80% cached on server 2, but was
// previously hosted and 20% cached on server 1
},
// Test 8: The regions are randomly assigned to the server with some regions historically
// hosted on other region servers
new int[][] { new int[] { 1, 2, 1 }, new int[] { 0, 34, 0, 58 }, // Region 0 is hosted and
// partially cached on
// server 0,
// but was previously hosted
// and partially cached on
// server 2
// current cache ratio <
// historical cache ratio
new int[] { 1, 78, 100, 0 }, // Region 1 is hosted and fully cached on server 1,
// but was previously hosted and partially cached on server 0
// current cache ratio > historical cache ratio
new int[] { 1, 66, 66, 0 }, // Region 2 is hosted and partially cached on server 1,
// but was previously hosted and partially cached on server 0
// current cache ratio == historical cache ratio
new int[] { 2, 0, 0, 96 }, // Region 3 is hosted and partially cached on server 0
// No historical cache ratio
}, };
private static Configuration storedConfiguration;
private CacheAwareLoadBalancer loadBalancer = new CacheAwareLoadBalancer();
@BeforeClass
public static void saveInitialConfiguration() {
storedConfiguration = new Configuration(conf);
}
@Before
public void beforeEachTest() {
conf = new Configuration(storedConfiguration);
loadBalancer.loadConf(conf);
}
@Test
public void testVerifyCacheAwareSkewnessCostFunctionEnabled() {
CacheAwareLoadBalancer lb = new CacheAwareLoadBalancer();
lb.loadConf(conf);
assertTrue(Arrays.asList(lb.getCostFunctionNames())
.contains(CacheAwareLoadBalancer.CacheAwareRegionSkewnessCostFunction.class.getSimpleName()));
}
@Test
public void testVerifyCacheAwareSkewnessCostFunctionDisabled() {
conf.setFloat(
CacheAwareLoadBalancer.CacheAwareRegionSkewnessCostFunction.REGION_COUNT_SKEW_COST_KEY, 0.0f);
CacheAwareLoadBalancer lb = new CacheAwareLoadBalancer();
lb.loadConf(conf);
assertFalse(Arrays.asList(lb.getCostFunctionNames())
.contains(CacheAwareLoadBalancer.CacheAwareRegionSkewnessCostFunction.class.getSimpleName()));
}
@Test
public void testVerifyCacheCostFunctionEnabled() {
conf.set(HConstants.BUCKET_CACHE_PERSISTENT_PATH_KEY, "/tmp/prefetch.persistence");
CacheAwareLoadBalancer lb = new CacheAwareLoadBalancer();
lb.loadConf(conf);
assertTrue(Arrays.asList(lb.getCostFunctionNames())
.contains(CacheAwareLoadBalancer.CacheAwareCostFunction.class.getSimpleName()));
}
@Test
public void testVerifyCacheCostFunctionDisabledByNoBucketCachePersistence() {
assertFalse(Arrays.asList(loadBalancer.getCostFunctionNames())
.contains(CacheAwareLoadBalancer.CacheAwareCostFunction.class.getSimpleName()));
}
@Test
public void testVerifyCacheCostFunctionDisabledByNoMultiplier() {
conf.set(HConstants.BUCKET_CACHE_PERSISTENT_PATH_KEY, "/tmp/prefetch.persistence");
conf.setFloat("hbase.master.balancer.stochastic.cacheCost", 0.0f);
assertFalse(Arrays.asList(loadBalancer.getCostFunctionNames())
.contains(CacheAwareLoadBalancer.CacheAwareCostFunction.class.getSimpleName()));
}
@Test
public void testCacheCost() {
conf.set(HConstants.BUCKET_CACHE_PERSISTENT_PATH_KEY, "/tmp/prefetch.persistence");
CacheAwareLoadBalancer.CacheAwareCostFunction costFunction =
new CacheAwareLoadBalancer.CacheAwareCostFunction(conf);
for (int test = 0; test < clusterRegionCacheRatioMocks.length; test++) {
int[][] clusterRegionLocations = clusterRegionCacheRatioMocks[test];
MockClusterForCacheCost cluster = new MockClusterForCacheCost(clusterRegionLocations);
costFunction.prepare(cluster);
double cost = costFunction.cost();
assertEquals(expectedCacheCost[test], cost, 0.01);
}
}
private class MockClusterForCacheCost extends BalancerClusterState {
private final Map<Pair<Integer, Integer>, Float> regionServerCacheRatio = new HashMap<>();
public MockClusterForCacheCost(int[][] regionsArray) {
// regions[0] is an array where index = serverIndex and value = number of regions
super(mockClusterServersUnsorted(regionsArray[0], 1), null, null, null, null);
Map<String, Pair<ServerName, Float>> oldCacheRatio = new HashMap<>();
for (int i = 1; i < regionsArray.length; i++) {
int regionIndex = i - 1;
for (int j = 1; j < regionsArray[i].length; j++) {
int serverIndex = j - 1;
float cacheRatio = (float) regionsArray[i][j] / 100;
regionServerCacheRatio.put(new Pair<>(regionIndex, serverIndex), cacheRatio);
if (cacheRatio > 0.0f && serverIndex != regionsArray[i][0]) {
// This is the historical cacheRatio value
oldCacheRatio.put(regions[regionIndex].getEncodedName(),
new Pair<>(servers[serverIndex], cacheRatio));
}
}
}
regionCacheRatioOnOldServerMap = oldCacheRatio;
}
@Override
public int getTotalRegionHFileSizeMB(int region) {
return 1;
}
@Override
protected float getRegionCacheRatioOnRegionServer(int region, int regionServerIndex) {
float cacheRatio = 0.0f;
// Get the cache ratio if the region is currently hosted on this server
if (regionServerIndex == regionIndexToServerIndex[region]) {
return regionServerCacheRatio.get(new Pair<>(region, regionServerIndex));
}
// Region is not currently hosted on this server. Check if the region was cached on this
// server earlier. This can happen when the server was shutdown and the cache was persisted.
// Search using the index name and server name and not the index id and server id as these
// ids may change when a server is marked as dead or a new server is added.
String regionEncodedName = regions[region].getEncodedName();
ServerName serverName = servers[regionServerIndex];
if (
regionCacheRatioOnOldServerMap != null
&& regionCacheRatioOnOldServerMap.containsKey(regionEncodedName)
) {
Pair<ServerName, Float> serverCacheRatio =
regionCacheRatioOnOldServerMap.get(regionEncodedName);
if (ServerName.isSameAddress(serverName, serverCacheRatio.getFirst())) {
cacheRatio = serverCacheRatio.getSecond();
regionCacheRatioOnOldServerMap.remove(regionEncodedName);
}
}
return cacheRatio;
}
}
}