Copilot commented on code in PR #8502:
URL: https://github.com/apache/hbase/pull/8502#discussion_r3669415872


##########
hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestCacheAwareLoadBalancer.java:
##########
@@ -116,11 +123,527 @@ public static void beforeAllTests() throws Exception {
     tableDescs = constructTableDesc(false);
     Configuration conf = HBaseConfiguration.create();
     conf.set(HConstants.BUCKET_CACHE_PERSISTENT_PATH_KEY, 
"prefetch_file_list");
+    conf.setFloat(HConstants.BUCKET_CACHE_SIZE_KEY, 10);
     loadBalancer = new CacheAwareLoadBalancer();
     loadBalancer.setClusterInfoProvider(new DummyClusterInfoProvider(conf));
     loadBalancer.loadConf(conf);
   }
 
+  @Test
+  public void testRegionsNotCachedOnOldServerAndCurrentServer() throws 
Exception {
+    // The regions are not cached on old server as well as the current server. 
This causes
+    // skewness in the region allocation which should be fixed by the balancer
+
+    Map<ServerName, List<RegionInfo>> clusterState = new HashMap<>();
+    ServerName server0 = servers.get(0);
+    ServerName server1 = servers.get(1);
+    ServerName server2 = servers.get(2);
+
+    // Simulate that the regions previously hosted by server1 are now hosted 
on server0
+    List<RegionInfo> regionsOnServer0 = randomRegions(10);
+    List<RegionInfo> regionsOnServer1 = randomRegions(0);
+    List<RegionInfo> regionsOnServer2 = randomRegions(5);
+
+    clusterState.put(server0, regionsOnServer0);
+    clusterState.put(server1, regionsOnServer1);
+    clusterState.put(server2, regionsOnServer2);
+
+    // Mock cluster metrics — give only server1 free cache so moves are 
directed there
+    Map<ServerName, ServerMetrics> serverMetricsMap = new TreeMap<>();
+    ServerMetrics sm0 = mockServerMetricsWithRegionCacheInfo(server0, 
regionsOnServer0, 0.0f,
+      new ArrayList<>(), 0, 10);
+    when(sm0.getCacheFreeSize()).thenReturn(0L);
+    ServerMetrics sm1 = mockServerMetricsWithRegionCacheInfo(server1, 
regionsOnServer1, 0.0f,
+      new ArrayList<>(), 0, 10);
+    ServerMetrics sm2 = mockServerMetricsWithRegionCacheInfo(server2, 
regionsOnServer2, 0.0f,
+      new ArrayList<>(), 0, 10);
+    when(sm2.getCacheFreeSize()).thenReturn(0L);
+    serverMetricsMap.put(server0, sm0);
+    serverMetricsMap.put(server1, sm1);
+    serverMetricsMap.put(server2, sm2);
+    ClusterMetrics clusterMetrics = mock(ClusterMetrics.class);
+    when(clusterMetrics.getLiveServerMetrics()).thenReturn(serverMetricsMap);
+    loadBalancer.updateClusterMetrics(clusterMetrics);
+
+    Map<TableName, Map<ServerName, List<RegionInfo>>> LoadOfAllTable =
+      (Map) mockClusterServersWithTables(clusterState);
+    List<RegionPlan> plans = loadBalancer.balanceCluster(LoadOfAllTable);
+    Set<RegionInfo> regionsMovedFromServer0 = new HashSet<>();
+    Map<ServerName, List<RegionInfo>> targetServers = new HashMap<>();
+    for (RegionPlan plan : plans) {
+      if (plan.getSource().equals(server0)) {
+        regionsMovedFromServer0.add(plan.getRegionInfo());
+        if (!targetServers.containsKey(plan.getDestination())) {
+          targetServers.put(plan.getDestination(), new ArrayList<>());
+        }
+        targetServers.get(plan.getDestination()).add(plan.getRegionInfo());
+      }
+    }
+    // should move at least 5 regions from server0 to balance cluster (10/0/5 
-> ~5/5/5)
+    assertTrue(regionsMovedFromServer0.size() >= 5,
+      "Expected at least 5 moves from server0, got " + 
regionsMovedFromServer0.size());
+  }
+
+  /**
+   * Regions on the overloaded RS report low block-cache ratio; no RS reports 
prefetch/historical
+   * cache for those regions (so {@link 
CacheAwareLoadBalancer.CacheAwareCandidateGenerator} has no
+   * "old server" to prefer). Another RS has ample free block cache. The 
balancer should still emit
+   * plans that shed load from the hot RS onto the idle RS with spare cache 
capacity.
+   */
+  @Test
+  public void 
testLowCacheRatioNoHistoricalCacheRelocatesWhenTargetHasFreeBlockCache()
+    throws Exception {
+    Map<ServerName, List<RegionInfo>> clusterState = new HashMap<>();
+    ServerName server0 = servers.get(0);
+    ServerName server1 = servers.get(1);
+    ServerName server2 = servers.get(2);
+
+    List<RegionInfo> regionsOnServer0 = randomRegions(10);
+    List<RegionInfo> regionsOnServer1 = randomRegions(0);
+    List<RegionInfo> regionsOnServer2 = randomRegions(5);
+
+    clusterState.put(server0, regionsOnServer0);
+    clusterState.put(server1, regionsOnServer1);
+    clusterState.put(server2, regionsOnServer2);
+
+    // Below LOW_CACHE_RATIO_FOR_RELOCATION_DEFAULT (0.35);
+    ServerMetrics sm0 = mockServerMetricsWithRegionCacheInfo(server0, 
regionsOnServer0, 0.1f,
+      new ArrayList<>(), 0, 10);
+    when(sm0.getCacheFreeSize()).thenReturn(0L);
+    ServerMetrics sm1 = mockServerMetricsWithRegionCacheInfo(server1, 
regionsOnServer1, 0.0f,
+      new ArrayList<>(), 0, 10);
+    // Simulates 1GB free cache space on server1
+    when(sm1.getCacheFreeSize()).thenReturn(1024L * 1024 * 1024);
+    ServerMetrics sm2 = mockServerMetricsWithRegionCacheInfo(server2, 
regionsOnServer2, 1.0f,
+      new ArrayList<>(), 0, 10);
+    when(sm2.getCacheFreeSize()).thenReturn(0L);
+
+    Map<ServerName, ServerMetrics> serverMetricsMap = new TreeMap<>();
+    serverMetricsMap.put(server0, sm0);
+    serverMetricsMap.put(server1, sm1);
+    serverMetricsMap.put(server2, sm2);
+    ClusterMetrics clusterMetrics = mock(ClusterMetrics.class);
+    when(clusterMetrics.getLiveServerMetrics()).thenReturn(serverMetricsMap);
+    loadBalancer.updateClusterMetrics(clusterMetrics);
+
+    assertTrue(loadBalancer.regionCacheRatioOnOldServerMap.isEmpty());
+
+    Map<TableName, Map<ServerName, List<RegionInfo>>> loadOfAllTable =
+      (Map) mockClusterServersWithTables(clusterState);
+    List<RegionPlan> plans = loadBalancer.balanceCluster(loadOfAllTable);
+    assertNotNull(plans);
+
+    Set<RegionInfo> regionsMovedFromServer0 = new HashSet<>();
+    Map<ServerName, List<RegionInfo>> targetServers = new HashMap<>();
+    for (RegionPlan plan : plans) {
+      if (plan.getSource().equals(server0)) {
+        regionsMovedFromServer0.add(plan.getRegionInfo());
+        if (!targetServers.containsKey(plan.getDestination())) {
+          targetServers.put(plan.getDestination(), new ArrayList<>());
+        }
+        targetServers.get(plan.getDestination()).add(plan.getRegionInfo());
+      }
+    }
+    assertEquals(5, regionsMovedFromServer0.size());
+    assertNotNull(targetServers.get(server1));
+    assertEquals(5, targetServers.get(server1).size());
+  }
+
+  @Test
+  public void 
testRegionsPartiallyCachedOnOldServerAndNotCachedOnCurrentServer() throws 
Exception {
+    // The regions are partially cached on old server but not cached on the 
current server
+
+    Map<ServerName, List<RegionInfo>> clusterState = new HashMap<>();
+    ServerName server0 = servers.get(0);
+    ServerName server1 = servers.get(1);
+    ServerName server2 = servers.get(2);
+
+    // Simulate that the regions previously hosted by server1 are now hosted 
on server0
+    List<RegionInfo> regionsOnServer0 = randomRegions(10);
+    List<RegionInfo> regionsOnServer1 = randomRegions(0);
+    List<RegionInfo> regionsOnServer2 = randomRegions(5);
+
+    clusterState.put(server0, regionsOnServer0);
+    clusterState.put(server1, regionsOnServer1);
+    clusterState.put(server2, regionsOnServer2);
+
+    // Mock cluster metrics
+
+    // Mock 5 regions from server0 were previously hosted on server1

Review Comment:
   The comment says "Mock 5 regions" but the subList call on the next line (end 
index = size() - 1) produces 4 regions. This is misleading when reading the 
test setup.
   
   This issue also appears on line 485 of the same file.



##########
hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/CacheAwareLoadBalancer.java:
##########
@@ -383,6 +431,31 @@ private boolean moveRegionToOldServer(BalancerClusterState 
cluster, int regionIn
         return false;
       }
 
+      // If the region is already well-cached on its current server, don't 
disrupt it.
+      // The old server's historical cache data may be stale, and moving a hot 
region
+      // causes unnecessary cache churn.
+      if (cacheRatioOnCurrentServer >= ratioThreshold) {
+        if (LOG.isDebugEnabled()) {
+          LOG.debug(
+            "Region {} not moved from {} to {} as it is already well-cached 
({}) on current server",
+            cluster.regions[regionIndex].getEncodedName(), 
cluster.servers[currentServerIndex],
+            cluster.servers[oldServerIndex], cacheRatioOnCurrentServer);
+        }
+        return false;
+      }
+
+      if (!serverHasCacheSpaceForRegion(cluster, regionIndex, oldServerIndex)) 
{
+        if (LOG.isDebugEnabled()) {
+          LOG.debug("Region {} not moved from {} to {} as destination server 
lacks cache space",
+            cluster.regions[regionIndex].getEncodedName(), 
cluster.servers[currentServerIndex],
+            cluster.servers[oldServerIndex]);
+        }
+        return false;
+      }
+
+      DecimalFormat df = new DecimalFormat("#");
+      df.setMaximumFractionDigits(4);

Review Comment:
   DecimalFormat is created with pattern "#", which suppresses the decimal 
separator. Even with setMaximumFractionDigits(4), this can round 0.8 to "1" in 
the debug log, making the cache ratio output misleading.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to