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}