|
27 | 27 | */ |
28 | 28 |
|
29 | 29 | import static org.apache.hadoop.hbase.HConstants.BUCKET_CACHE_PERSISTENT_PATH_KEY; |
| 30 | +import static org.apache.hadoop.hbase.HConstants.BUCKET_CACHE_SIZE_KEY; |
30 | 31 |
|
31 | 32 | import java.math.BigDecimal; |
32 | 33 | import java.text.DecimalFormat; |
@@ -111,6 +112,8 @@ public enum GeneratorFunctionType { |
111 | 112 | private float lowCacheRatioThreshold; |
112 | 113 | private float potentialCacheRatioAfterMove; |
113 | 114 | private float minFreeCacheSpaceFactor; |
| 115 | + private long cachePrefetchOverheadBytes; |
| 116 | + private boolean cacheSpaceTrackingEnabled; |
114 | 117 |
|
115 | 118 | private BigDecimal simulatedRatio = BigDecimal.ZERO; |
116 | 119 |
|
@@ -141,6 +144,16 @@ public void updateConfiguration(Configuration configuration) { |
141 | 144 | POTENTIAL_CACHE_RATIO_AFTER_MOVE_DEFAULT); |
142 | 145 | minFreeCacheSpaceFactor = |
143 | 146 | configuration.getFloat(MIN_FREE_CACHE_SPACE_FACTOR_KEY, MIN_FREE_CACHE_SPACE_FACTOR_DEFAULT); |
| 147 | + float bucketCacheSizeMB = configuration.getFloat(BUCKET_CACHE_SIZE_KEY, 0F); |
| 148 | + float acceptableFactor = configuration.getFloat("hbase.bucketcache.acceptfactor", 0.95f); |
| 149 | + cachePrefetchOverheadBytes = |
| 150 | + (long) (bucketCacheSizeMB * 1024L * 1024L * (1 - acceptableFactor)); |
| 151 | + cacheSpaceTrackingEnabled = bucketCacheSizeMB > 0; |
| 152 | + if (!cacheSpaceTrackingEnabled) { |
| 153 | + LOG.warn("{} is not configured on the master. The free-space relocation heuristic cannot " |
| 154 | + + "account for the prefetch threshold and may move regions to servers where prefetch " |
| 155 | + + "will be blocked.", BUCKET_CACHE_SIZE_KEY); |
| 156 | + } |
144 | 157 | } |
145 | 158 |
|
146 | 159 | /** |
@@ -207,11 +220,14 @@ public void updateClusterMetrics(ClusterMetrics clusterMetrics) { |
207 | 220 | } |
208 | 221 |
|
209 | 222 | protected Map<ServerName, Long> getServerBlockCacheFreeBytes() { |
210 | | - if (clusterStatus == null) { |
| 223 | + if (clusterStatus == null || !cacheSpaceTrackingEnabled) { |
211 | 224 | return null; |
212 | 225 | } |
213 | 226 | Map<ServerName, Long> map = new HashMap<>(); |
214 | | - clusterStatus.getLiveServerMetrics().forEach((sn, sm) -> map.put(sn, sm.getCacheFreeSize())); |
| 227 | + clusterStatus.getLiveServerMetrics().forEach((sn, sm) -> { |
| 228 | + long effectiveFree = Math.max(0, sm.getCacheFreeSize() - cachePrefetchOverheadBytes); |
| 229 | + map.put(sn, effectiveFree); |
| 230 | + }); |
215 | 231 | return map; |
216 | 232 | } |
217 | 233 |
|
@@ -291,36 +307,71 @@ private RegionInfo getRegionInfoByEncodedName(BalancerClusterState cluster, Stri |
291 | 307 | return null; |
292 | 308 | } |
293 | 309 |
|
| 310 | + private boolean serverHasCacheSpaceForRegion(BalancerClusterState cluster, int region, |
| 311 | + int server) { |
| 312 | + if (cluster.serverBlockCacheFreeSize == null) { |
| 313 | + return true; |
| 314 | + } |
| 315 | + int regionSizeMb = cluster.getRegionSizeMinusColdDataMB(region); |
| 316 | + if (regionSizeMb <= 0) { |
| 317 | + return true; |
| 318 | + } |
| 319 | + long bytesNeeded = (long) regionSizeMb * 1024L * 1024L; |
| 320 | + return cluster.serverBlockCacheFreeSize[server] >= bytesNeeded; |
| 321 | + } |
| 322 | + |
294 | 323 | @Override |
295 | 324 | public void throttle(RegionPlan plan) { |
| 325 | + synchronized (this) { |
| 326 | + try { |
| 327 | + // Release the monitor while waiting to avoid blocking other threads. |
| 328 | + wait(getThrottleDurationMs(plan)); |
| 329 | + } catch (InterruptedException e) { |
| 330 | + throw new RuntimeException(e); |
| 331 | + } |
| 332 | + } |
| 333 | + } |
| 334 | + |
| 335 | + public long getThrottleDurationMs(RegionPlan plan) { |
296 | 336 | Pair<ServerName, Float> rsRatio = this.regionCacheRatioOnOldServerMap.get(plan.getRegionName()); |
297 | 337 | if ( |
298 | 338 | rsRatio != null && plan.getDestination().equals(rsRatio.getFirst()) |
299 | 339 | && rsRatio.getSecond() >= ratioThreshold |
300 | 340 | ) { |
301 | 341 | LOG.debug("Moving region {} to server {} with cache ratio {}. No throttling needed.", |
302 | 342 | plan.getRegionInfo().getEncodedName(), plan.getDestination(), rsRatio.getSecond()); |
| 343 | + return 0L; |
| 344 | + } |
| 345 | + // Skip throttling for regions with low cache ratio on their source server — there is |
| 346 | + // negligible cached data to lose, so no warm-up delay is needed on the destination. |
| 347 | + float cacheRatioOnSource = getRegionCacheRatioOnSource(plan); |
| 348 | + if (cacheRatioOnSource < lowCacheRatioThreshold) { |
| 349 | + LOG.debug( |
| 350 | + "Moving region {} to server {} with low cache ratio {} on source. No throttling needed.", |
| 351 | + plan.getRegionInfo().getEncodedName(), plan.getDestination(), cacheRatioOnSource); |
| 352 | + return 0L; |
| 353 | + } |
| 354 | + if (rsRatio != null) { |
| 355 | + LOG.debug("Moving region {} to server {} with cache ratio: {}. Throttling move for {}ms.", |
| 356 | + plan.getRegionInfo().getEncodedName(), plan.getDestination(), |
| 357 | + plan.getDestination().equals(rsRatio.getFirst()) ? rsRatio.getSecond() : "unknown", |
| 358 | + sleepTime); |
303 | 359 | } else { |
304 | | - if (rsRatio != null) { |
305 | | - LOG.debug("Moving region {} to server {} with cache ratio: {}. Throttling move for {}ms.", |
306 | | - plan.getRegionInfo().getEncodedName(), plan.getDestination(), |
307 | | - plan.getDestination().equals(rsRatio.getFirst()) ? rsRatio.getSecond() : "unknown", |
308 | | - sleepTime); |
309 | | - } else { |
310 | | - LOG.debug( |
311 | | - "Moving region {} to server {} with no cache ratio info for the region. " |
312 | | - + "Throttling move for {}ms.", |
313 | | - plan.getRegionInfo().getEncodedName(), plan.getDestination(), sleepTime); |
314 | | - } |
315 | | - synchronized (this) { |
316 | | - try { |
317 | | - // Release the monitor while waiting to avoid blocking other threads. |
318 | | - wait(sleepTime); |
319 | | - } catch (InterruptedException e) { |
320 | | - throw new RuntimeException(e); |
321 | | - } |
322 | | - } |
| 360 | + LOG.debug( |
| 361 | + "Moving region {} to server {} with no cache ratio info for the region. " |
| 362 | + + "Throttling move for {}ms.", |
| 363 | + plan.getRegionInfo().getEncodedName(), plan.getDestination(), sleepTime); |
323 | 364 | } |
| 365 | + return sleepTime; |
| 366 | + } |
| 367 | + |
| 368 | + private float getRegionCacheRatioOnSource(RegionPlan plan) { |
| 369 | + Deque<BalancerRegionLoad> regionLoad = loads.get(plan.getRegionName()); |
| 370 | + if (regionLoad != null && !regionLoad.isEmpty()) { |
| 371 | + return regionLoad.getFirst().getCurrentRegionCacheRatio(); |
| 372 | + } |
| 373 | + // Unknown cache ratio — assume it may be cached and require throttling |
| 374 | + return 1.0f; |
324 | 375 | } |
325 | 376 |
|
326 | 377 | @Override |
@@ -452,6 +503,28 @@ private boolean moveRegionToOldServer(BalancerClusterState cluster, int regionIn |
452 | 503 | return false; |
453 | 504 | } |
454 | 505 |
|
| 506 | + // If the region is already well-cached on its current server, don't disrupt it. |
| 507 | + // The old server's historical cache data may be stale, and moving a hot region |
| 508 | + // causes unnecessary cache churn. |
| 509 | + if (cacheRatioOnCurrentServer >= ratioThreshold) { |
| 510 | + if (LOG.isDebugEnabled()) { |
| 511 | + LOG.debug( |
| 512 | + "Region {} not moved from {} to {} as it is already well-cached ({}) on current server", |
| 513 | + cluster.regions[regionIndex].getEncodedName(), cluster.servers[currentServerIndex], |
| 514 | + cluster.servers[oldServerIndex], cacheRatioOnCurrentServer); |
| 515 | + } |
| 516 | + return false; |
| 517 | + } |
| 518 | + |
| 519 | + if (!serverHasCacheSpaceForRegion(cluster, regionIndex, oldServerIndex)) { |
| 520 | + if (LOG.isDebugEnabled()) { |
| 521 | + LOG.debug("Region {} not moved from {} to {} as destination server lacks cache space", |
| 522 | + cluster.regions[regionIndex].getEncodedName(), cluster.servers[currentServerIndex], |
| 523 | + cluster.servers[oldServerIndex]); |
| 524 | + } |
| 525 | + return false; |
| 526 | + } |
| 527 | + |
455 | 528 | DecimalFormat df = new DecimalFormat("#"); |
456 | 529 | df.setMaximumFractionDigits(4); |
457 | 530 |
|
@@ -512,60 +585,6 @@ private class CacheAwareSkewnessCandidateGenerator extends LoadCandidateGenerato |
512 | 585 | @Override |
513 | 586 | BalanceAction pickRandomRegions(BalancerClusterState cluster, int thisServer, int otherServer) { |
514 | 587 | simulatedRatio = BigDecimal.ZERO; |
515 | | - // First move all the regions which were hosted previously on some other server back to their |
516 | | - // old servers |
517 | | - if ( |
518 | | - !regionCacheRatioOnOldServerMap.isEmpty() |
519 | | - && regionCacheRatioOnOldServerMap.entrySet().iterator().hasNext() |
520 | | - ) { |
521 | | - // Get the first region index in the historical cache ratio list |
522 | | - Map.Entry<String, Pair<ServerName, Float>> regionEntry = |
523 | | - regionCacheRatioOnOldServerMap.entrySet().iterator().next(); |
524 | | - String regionEncodedName = regionEntry.getKey(); |
525 | | - |
526 | | - RegionInfo regionInfo = getRegionInfoByEncodedName(cluster, regionEncodedName); |
527 | | - if (regionInfo == null) { |
528 | | - LOG.warn("Region {} does not exist", regionEncodedName); |
529 | | - regionCacheRatioOnOldServerMap.remove(regionEncodedName); |
530 | | - return BalanceAction.NULL_ACTION; |
531 | | - } |
532 | | - if (regionInfo.isMetaRegion() || regionInfo.getTable().isSystemTable()) { |
533 | | - regionCacheRatioOnOldServerMap.remove(regionEncodedName); |
534 | | - return BalanceAction.NULL_ACTION; |
535 | | - } |
536 | | - |
537 | | - int regionIndex = cluster.regionsToIndex.get(regionInfo); |
538 | | - |
539 | | - // Get the current host name for this region |
540 | | - thisServer = cluster.regionIndexToServerIndex[regionIndex]; |
541 | | - |
542 | | - // Get the old server index |
543 | | - otherServer = cluster.serversToIndex.get(regionEntry.getValue().getFirst().getAddress()); |
544 | | - |
545 | | - regionCacheRatioOnOldServerMap.remove(regionEncodedName); |
546 | | - |
547 | | - if (otherServer < 0) { |
548 | | - // The old server has been moved to other host and hence, the region cannot be moved back |
549 | | - // to the old server |
550 | | - if (LOG.isDebugEnabled()) { |
551 | | - LOG.debug( |
552 | | - "CacheAwareSkewnessCandidateGenerator: Region {} not moved to the old " |
553 | | - + "server {} as the server does not exist", |
554 | | - regionEncodedName, regionEntry.getValue().getFirst().getHostname()); |
555 | | - } |
556 | | - return BalanceAction.NULL_ACTION; |
557 | | - } |
558 | | - |
559 | | - if (LOG.isDebugEnabled()) { |
560 | | - LOG.debug( |
561 | | - "CacheAwareSkewnessCandidateGenerator: Region {} moved from {} to {} as it " |
562 | | - + "was hosted their earlier", |
563 | | - regionEncodedName, cluster.servers[thisServer].getHostname(), |
564 | | - cluster.servers[otherServer].getHostname()); |
565 | | - } |
566 | | - |
567 | | - return getAction(thisServer, regionIndex, otherServer, -1); |
568 | | - } |
569 | 588 |
|
570 | 589 | if (thisServer < 0 || otherServer < 0) { |
571 | 590 | return BalanceAction.NULL_ACTION; |
@@ -642,7 +661,7 @@ protected void regionMoved(int region, int oldServer, int newServer) { |
642 | 661 |
|
643 | 662 | @Override |
644 | 663 | public final void updateWeight(Map<Class<? extends CandidateGenerator>, Double> weights) { |
645 | | - weights.merge(LoadCandidateGenerator.class, cost(), Double::sum); |
| 664 | + weights.merge(CacheAwareSkewnessCandidateGenerator.class, cost(), Double::sum); |
646 | 665 | } |
647 | 666 | } |
648 | 667 |
|
@@ -739,6 +758,9 @@ private double[] computeCurrentWeightedContributions(BalancerClusterState cluste |
739 | 758 | * from low ratios when capacity exists somewhere in the cluster. |
740 | 759 | */ |
741 | 760 | private double potentialBestWeightedFromFreeCache(BalancerClusterState cluster, int region) { |
| 761 | + if (cluster.serverBlockCacheFreeSize == null) { |
| 762 | + return 0.0; |
| 763 | + } |
742 | 764 | float observedRatio = cluster.getSumRegionCacheAndColdDataRatio(region); |
743 | 765 | if (observedRatio >= lowCacheRatioThreshold) { |
744 | 766 | return 0.0; |
@@ -769,9 +791,11 @@ protected void regionMoved(int region, int oldServer, int newServer) { |
769 | 791 | if (simulatedRatio.equals(BigDecimal.ZERO)) { |
770 | 792 | double potentialCachedSizeOnNewServer = |
771 | 793 | cluster.getRegionSizeMinusColdDataMB(region) * potentialCacheRatioAfterMove; |
772 | | - boolean simulateCacheBasedOnFreeSpace = |
773 | | - cluster.getOrComputeRegionCacheRatio(region, oldServer) < lowCacheRatioThreshold |
774 | | - && cluster.serverBlockCacheFreeSize[newServer] >= potentialCachedSizeOnNewServer; |
| 794 | + long potentialCachedBytesOnNewServer = |
| 795 | + (long) (potentialCachedSizeOnNewServer * 1024L * 1024L); |
| 796 | + boolean simulateCacheBasedOnFreeSpace = cluster.serverBlockCacheFreeSize != null |
| 797 | + && cluster.getOrComputeRegionCacheRatio(region, oldServer) < lowCacheRatioThreshold |
| 798 | + && cluster.serverBlockCacheFreeSize[newServer] >= potentialCachedBytesOnNewServer; |
775 | 799 | double regionCacheRatioOnNewServer = simulateCacheBasedOnFreeSpace |
776 | 800 | ? potentialCachedSizeOnNewServer |
777 | 801 | : cluster.getOrComputeWeightedRegionCacheRatio(region, newServer); |
@@ -808,7 +832,7 @@ private int getServerWithBestCacheRatioForRegion(int region) { |
808 | 832 |
|
809 | 833 | @Override |
810 | 834 | public void updateWeight(Map<Class<? extends CandidateGenerator>, Double> weights) { |
811 | | - weights.merge(LoadCandidateGenerator.class, cost(), Double::sum); |
| 835 | + weights.merge(CacheAwareCandidateGenerator.class, cost(), Double::sum); |
812 | 836 | } |
813 | 837 | } |
814 | 838 | } |
0 commit comments