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}