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.normalizer; 019 020import static org.apache.hbase.thirdparty.org.apache.commons.collections4.CollectionUtils.isEmpty; 021 022import java.time.Instant; 023import java.time.Period; 024import java.util.ArrayList; 025import java.util.Collections; 026import java.util.LinkedList; 027import java.util.List; 028import java.util.Objects; 029import java.util.function.BooleanSupplier; 030import java.util.function.Function; 031import org.apache.hadoop.conf.Configuration; 032import org.apache.hadoop.hbase.HBaseInterfaceAudience; 033import org.apache.hadoop.hbase.RegionMetrics; 034import org.apache.hadoop.hbase.ServerMetrics; 035import org.apache.hadoop.hbase.ServerName; 036import org.apache.hadoop.hbase.Size; 037import org.apache.hadoop.hbase.TableName; 038import org.apache.hadoop.hbase.client.MasterSwitchType; 039import org.apache.hadoop.hbase.client.RegionInfo; 040import org.apache.hadoop.hbase.client.TableDescriptor; 041import org.apache.hadoop.hbase.conf.ConfigurationObserver; 042import org.apache.hadoop.hbase.master.MasterServices; 043import org.apache.hadoop.hbase.master.RegionState; 044import org.apache.hadoop.hbase.master.assignment.RegionStates; 045import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 046import org.apache.yetus.audience.InterfaceAudience; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 049 050/** 051 * Simple implementation of region normalizer. Logic in use: 052 * <ol> 053 * <li>Get all regions of a given table</li> 054 * <li>Get avg size S of the regions in the table (by total size of store files reported in 055 * RegionMetrics)</li> 056 * <li>For each region R0, if R0 is bigger than S * 2, it is kindly requested to split.</li> 057 * <li>Otherwise, for the next region in the chain R1, if R0 + R1 is smaller then S, R0 and R1 are 058 * kindly requested to merge.</li> 059 * </ol> 060 */ 061@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG) 062class SimpleRegionNormalizer implements RegionNormalizer, ConfigurationObserver { 063 private static final Logger LOG = LoggerFactory.getLogger(SimpleRegionNormalizer.class); 064 065 static final String SPLIT_ENABLED_KEY = "hbase.normalizer.split.enabled"; 066 static final boolean DEFAULT_SPLIT_ENABLED = true; 067 static final String MERGE_ENABLED_KEY = "hbase.normalizer.merge.enabled"; 068 static final boolean DEFAULT_MERGE_ENABLED = true; 069 /** 070 * @deprecated since 2.5.0 and will be removed in 4.0.0. Use 071 * {@link SimpleRegionNormalizer#MERGE_MIN_REGION_COUNT_KEY} instead. 072 * @see <a href="https://issues.apache.org/jira/browse/HBASE-25745">HBASE-25745</a> 073 */ 074 @Deprecated 075 static final String MIN_REGION_COUNT_KEY = "hbase.normalizer.min.region.count"; 076 static final String MERGE_MIN_REGION_COUNT_KEY = "hbase.normalizer.merge.min.region.count"; 077 static final int DEFAULT_MERGE_MIN_REGION_COUNT = 3; 078 static final String MERGE_MIN_REGION_AGE_DAYS_KEY = "hbase.normalizer.merge.min_region_age.days"; 079 static final int DEFAULT_MERGE_MIN_REGION_AGE_DAYS = 3; 080 static final String MERGE_MIN_REGION_SIZE_MB_KEY = "hbase.normalizer.merge.min_region_size.mb"; 081 static final int DEFAULT_MERGE_MIN_REGION_SIZE_MB = 0; 082 083 private MasterServices masterServices; 084 private NormalizerConfiguration normalizerConfiguration; 085 086 public SimpleRegionNormalizer() { 087 masterServices = null; 088 normalizerConfiguration = new NormalizerConfiguration(); 089 } 090 091 @Override 092 public Configuration getConf() { 093 return normalizerConfiguration.getConf(); 094 } 095 096 @Override 097 public void setConf(final Configuration conf) { 098 if (conf == null) { 099 return; 100 } 101 normalizerConfiguration = new NormalizerConfiguration(conf, normalizerConfiguration); 102 } 103 104 @Override 105 public void onConfigurationChange(Configuration conf) { 106 LOG.debug("Updating configuration parameters according to new configuration instance."); 107 setConf(conf); 108 } 109 110 private static int parseMergeMinRegionCount(final Configuration conf) { 111 final int parsedValue = conf.getInt(MERGE_MIN_REGION_COUNT_KEY, DEFAULT_MERGE_MIN_REGION_COUNT); 112 final int settledValue = Math.max(1, parsedValue); 113 if (parsedValue != settledValue) { 114 warnInvalidValue(MERGE_MIN_REGION_COUNT_KEY, parsedValue, settledValue); 115 } 116 return settledValue; 117 } 118 119 private static Period parseMergeMinRegionAge(final Configuration conf) { 120 final int parsedValue = 121 conf.getInt(MERGE_MIN_REGION_AGE_DAYS_KEY, DEFAULT_MERGE_MIN_REGION_AGE_DAYS); 122 final int settledValue = Math.max(0, parsedValue); 123 if (parsedValue != settledValue) { 124 warnInvalidValue(MERGE_MIN_REGION_AGE_DAYS_KEY, parsedValue, settledValue); 125 } 126 return Period.ofDays(settledValue); 127 } 128 129 private static long parseMergeMinRegionSizeMb(final Configuration conf) { 130 final long parsedValue = 131 conf.getLong(MERGE_MIN_REGION_SIZE_MB_KEY, DEFAULT_MERGE_MIN_REGION_SIZE_MB); 132 final long settledValue = Math.max(0, parsedValue); 133 if (parsedValue != settledValue) { 134 warnInvalidValue(MERGE_MIN_REGION_SIZE_MB_KEY, parsedValue, settledValue); 135 } 136 return settledValue; 137 } 138 139 private static <T> void warnInvalidValue(final String key, final T parsedValue, 140 final T settledValue) { 141 LOG.warn("Configured value {}={} is invalid. Setting value to {}.", key, parsedValue, 142 settledValue); 143 } 144 145 private static <T> void logConfigurationUpdated(final String key, final T oldValue, 146 final T newValue) { 147 if (!Objects.equals(oldValue, newValue)) { 148 LOG.info("Updated configuration for key '{}' from {} to {}", key, oldValue, newValue); 149 } 150 } 151 152 /** 153 * Return this instance's configured value for {@value #SPLIT_ENABLED_KEY}. 154 */ 155 public boolean isSplitEnabled() { 156 return normalizerConfiguration.isSplitEnabled(); 157 } 158 159 /** 160 * Return this instance's configured value for {@value #MERGE_ENABLED_KEY}. 161 */ 162 public boolean isMergeEnabled() { 163 return normalizerConfiguration.isMergeEnabled(); 164 } 165 166 /** 167 * Return this instance's configured value for {@value #MERGE_MIN_REGION_COUNT_KEY}. 168 */ 169 public int getMergeMinRegionCount() { 170 return normalizerConfiguration.getMergeMinRegionCount(); 171 } 172 173 /** 174 * Return this instance's configured value for {@value #MERGE_MIN_REGION_AGE_DAYS_KEY}. 175 */ 176 public Period getMergeMinRegionAge() { 177 return normalizerConfiguration.getMergeMinRegionAge(); 178 } 179 180 /** 181 * Return this instance's configured value for {@value #MERGE_MIN_REGION_SIZE_MB_KEY}. 182 */ 183 public long getMergeMinRegionSizeMb() { 184 return normalizerConfiguration.getMergeMinRegionSizeMb(); 185 } 186 187 @Override 188 public void setMasterServices(final MasterServices masterServices) { 189 this.masterServices = masterServices; 190 } 191 192 @Override 193 public List<NormalizationPlan> computePlansForTable(final TableDescriptor tableDescriptor) { 194 if (tableDescriptor == null) { 195 return Collections.emptyList(); 196 } 197 TableName table = tableDescriptor.getTableName(); 198 if (table.isSystemTable()) { 199 LOG.debug("Normalization of system table {} isn't allowed", table); 200 return Collections.emptyList(); 201 } 202 203 final boolean proceedWithSplitPlanning = proceedWithSplitPlanning(tableDescriptor); 204 final boolean proceedWithMergePlanning = proceedWithMergePlanning(tableDescriptor); 205 if (!proceedWithMergePlanning && !proceedWithSplitPlanning) { 206 LOG.debug("Both split and merge are disabled. Skipping normalization of table: {}", table); 207 return Collections.emptyList(); 208 } 209 210 final NormalizeContext ctx = new NormalizeContext(tableDescriptor); 211 if (isEmpty(ctx.getTableRegions())) { 212 return Collections.emptyList(); 213 } 214 215 LOG.debug("Computing normalization plan for table: {}, number of regions: {}", table, 216 ctx.getTableRegions().size()); 217 218 final List<NormalizationPlan> plans = new ArrayList<>(); 219 int splitPlansCount = 0; 220 if (proceedWithSplitPlanning) { 221 List<NormalizationPlan> splitPlans = computeSplitNormalizationPlans(ctx); 222 splitPlansCount = splitPlans.size(); 223 plans.addAll(splitPlans); 224 } 225 int mergePlansCount = 0; 226 if (proceedWithMergePlanning) { 227 List<NormalizationPlan> mergePlans = computeMergeNormalizationPlans(ctx); 228 mergePlansCount = mergePlans.size(); 229 plans.addAll(mergePlans); 230 } 231 232 LOG.debug("Computed normalization plans for table {}. Total plans: {}, split plans: {}, " 233 + "merge plans: {}", table, plans.size(), splitPlansCount, mergePlansCount); 234 return plans; 235 } 236 237 /** Returns size of region in MB and if region is not found than -1 */ 238 private long getRegionSizeMB(RegionInfo hri) { 239 ServerName sn = 240 masterServices.getAssignmentManager().getRegionStates().getRegionServerOfRegion(hri); 241 if (sn == null) { 242 LOG.debug("{} region was not found on any Server", hri.getRegionNameAsString()); 243 return -1; 244 } 245 ServerMetrics serverMetrics = masterServices.getServerManager().getLoad(sn); 246 if (serverMetrics == null) { 247 LOG.debug("server {} was not found in ServerManager", sn.getServerName()); 248 return -1; 249 } 250 RegionMetrics regionLoad = serverMetrics.getRegionMetrics().get(hri.getRegionName()); 251 if (regionLoad == null) { 252 LOG.debug("{} was not found in RegionsLoad", hri.getRegionNameAsString()); 253 return -1; 254 } 255 return (long) regionLoad.getStoreFileSize().get(Size.Unit.MEGABYTE); 256 } 257 258 private boolean isMasterSwitchEnabled(final MasterSwitchType masterSwitchType) { 259 return masterServices.isSplitOrMergeEnabled(masterSwitchType); 260 } 261 262 private boolean proceedWithSplitPlanning(TableDescriptor tableDescriptor) { 263 String value = tableDescriptor.getValue(SPLIT_ENABLED_KEY); 264 return (value == null ? isSplitEnabled() : Boolean.parseBoolean(value)) 265 && isMasterSwitchEnabled(MasterSwitchType.SPLIT); 266 } 267 268 private boolean proceedWithMergePlanning(TableDescriptor tableDescriptor) { 269 String value = tableDescriptor.getValue(MERGE_ENABLED_KEY); 270 return (value == null ? isMergeEnabled() : Boolean.parseBoolean(value)) 271 && isMasterSwitchEnabled(MasterSwitchType.MERGE); 272 } 273 274 /** 275 * Also make sure tableRegions contains regions of the same table 276 * @param tableRegions regions of table to normalize 277 * @param tableDescriptor the TableDescriptor 278 * @return average region size depending on 279 * @see TableDescriptor#getNormalizerTargetRegionCount() 280 */ 281 private double getAverageRegionSizeMb(final List<RegionInfo> tableRegions, 282 final TableDescriptor tableDescriptor) { 283 if (isEmpty(tableRegions)) { 284 throw new IllegalStateException( 285 "Cannot calculate average size of a table without any regions."); 286 } 287 TableName table = tableDescriptor.getTableName(); 288 double avgRegionSize; 289 int targetRegionCount = tableDescriptor.getNormalizerTargetRegionCount(); 290 long targetRegionSize = tableDescriptor.getNormalizerTargetRegionSize(); 291 LOG.debug("Table {} configured with target region count {}, target region size {}", table, 292 targetRegionCount, targetRegionSize); 293 294 if (targetRegionSize > 0) { 295 avgRegionSize = targetRegionSize; 296 } else { 297 final int regionCount = tableRegions.size(); 298 final long totalSizeMb = tableRegions.stream().mapToLong(this::getRegionSizeMB).sum(); 299 if (targetRegionCount > 0) { 300 avgRegionSize = totalSizeMb / (double) targetRegionCount; 301 } else { 302 avgRegionSize = totalSizeMb / (double) regionCount; 303 } 304 LOG.debug("Table {}, total aggregated regions size: {} MB and average region size {} MB", 305 table, totalSizeMb, String.format("%.3f", avgRegionSize)); 306 } 307 308 return avgRegionSize; 309 } 310 311 /** 312 * Determine if a {@link RegionInfo} should be considered for a merge operation. 313 * </p> 314 * Callers beware: for safe concurrency, be sure to pass in the local instance of 315 * {@link NormalizerConfiguration}, don't use {@code this}'s instance. 316 */ 317 private boolean skipForMerge(final NormalizerConfiguration normalizerConfiguration, 318 final NormalizeContext ctx, final RegionInfo regionInfo) { 319 final RegionState state = ctx.getRegionStates().getRegionState(regionInfo); 320 final String name = regionInfo.getEncodedName(); 321 return logTraceReason(() -> state == null, 322 "skipping merge of region {} because no state information is available.", name) 323 || logTraceReason(() -> !Objects.equals(state.getState(), RegionState.State.OPEN), 324 "skipping merge of region {} because it is not open.", name) 325 || logTraceReason(() -> !isOldEnoughForMerge(normalizerConfiguration, ctx, regionInfo), 326 "skipping merge of region {} because it is not old enough.", name) 327 || logTraceReason(() -> !isLargeEnoughForMerge(normalizerConfiguration, ctx, regionInfo), 328 "skipping merge region {} because it is not large enough.", name); 329 } 330 331 /** 332 * Computes the merge plans that should be executed for this table to converge average region 333 * towards target average or target region count. 334 */ 335 private List<NormalizationPlan> computeMergeNormalizationPlans(final NormalizeContext ctx) { 336 final NormalizerConfiguration configuration = normalizerConfiguration; 337 if (ctx.getTableRegions().size() < configuration.getMergeMinRegionCount(ctx)) { 338 LOG.debug( 339 "Table {} has {} regions, required min number of regions for normalizer to run" 340 + " is {}, not computing merge plans.", 341 ctx.getTableName(), ctx.getTableRegions().size(), configuration.getMergeMinRegionCount()); 342 return Collections.emptyList(); 343 } 344 345 final long avgRegionSizeMb = (long) ctx.getAverageRegionSizeMb(); 346 if (avgRegionSizeMb < configuration.getMergeMinRegionSizeMb(ctx)) { 347 return Collections.emptyList(); 348 } 349 LOG.debug("Computing normalization plan for table {}. average region size: {} MB, number of" 350 + " regions: {}.", ctx.getTableName(), avgRegionSizeMb, ctx.getTableRegions().size()); 351 352 // this nested loop walks the table's region chain once, looking for contiguous sequences of 353 // regions that meet the criteria for merge. The outer loop tracks the starting point of the 354 // next sequence, the inner loop looks for the end of that sequence. A single sequence becomes 355 // an instance of MergeNormalizationPlan. 356 357 final List<NormalizationPlan> plans = new LinkedList<>(); 358 final List<NormalizationTarget> rangeMembers = new LinkedList<>(); 359 long sumRangeMembersSizeMb; 360 int current = 0; 361 for (int rangeStart = 0; rangeStart < ctx.getTableRegions().size() - 1 362 && current < ctx.getTableRegions().size();) { 363 // walk the region chain looking for contiguous sequences of regions that can be merged. 364 rangeMembers.clear(); 365 sumRangeMembersSizeMb = 0; 366 for (current = rangeStart; current < ctx.getTableRegions().size(); current++) { 367 final RegionInfo regionInfo = ctx.getTableRegions().get(current); 368 final long regionSizeMb = getRegionSizeMB(regionInfo); 369 if (skipForMerge(configuration, ctx, regionInfo)) { 370 // this region cannot participate in a range. resume the outer loop. 371 rangeStart = Math.max(current, rangeStart + 1); 372 break; 373 } 374 if ( 375 rangeMembers.isEmpty() // when there are no range members, seed the range with whatever 376 // we have. this way we're prepared in case the next region is 377 // 0-size. 378 || (rangeMembers.size() == 1 && sumRangeMembersSizeMb == 0) // when there is only one 379 // region and the size is 0, 380 // seed the range with 381 // whatever we have. 382 || regionSizeMb == 0 // always add an empty region to the current range. 383 || (regionSizeMb + sumRangeMembersSizeMb <= avgRegionSizeMb) 384 ) { // add the current region 385 // to the range when 386 // there's capacity 387 // remaining. 388 rangeMembers.add(new NormalizationTarget(regionInfo, regionSizeMb)); 389 sumRangeMembersSizeMb += regionSizeMb; 390 continue; 391 } 392 // we have accumulated enough regions to fill a range. resume the outer loop. 393 rangeStart = Math.max(current, rangeStart + 1); 394 break; 395 } 396 if (rangeMembers.size() > 1) { 397 plans.add(new MergeNormalizationPlan.Builder().setTargets(rangeMembers).build()); 398 } 399 } 400 return plans; 401 } 402 403 /** 404 * Determine if a region in {@link RegionState} should be considered for a split operation. 405 */ 406 private static boolean skipForSplit(final RegionState state, final RegionInfo regionInfo) { 407 final String name = regionInfo.getEncodedName(); 408 return logTraceReason(() -> state == null, 409 "skipping split of region {} because no state information is available.", name) 410 || logTraceReason(() -> !Objects.equals(state.getState(), RegionState.State.OPEN), 411 "skipping merge of region {} because it is not open.", name); 412 } 413 414 /** 415 * Computes the split plans that should be executed for this table to converge average region size 416 * towards target average or target region count. <br /> 417 * if the region is > 2 times larger than average, we split it. split is more high priority 418 * normalization action than merge. 419 */ 420 private List<NormalizationPlan> computeSplitNormalizationPlans(final NormalizeContext ctx) { 421 final double avgRegionSize = ctx.getAverageRegionSizeMb(); 422 LOG.debug("Table {}, average region size: {} MB", ctx.getTableName(), 423 String.format("%.3f", avgRegionSize)); 424 425 final List<NormalizationPlan> plans = new ArrayList<>(); 426 for (final RegionInfo hri : ctx.getTableRegions()) { 427 if (skipForSplit(ctx.getRegionStates().getRegionState(hri), hri)) { 428 continue; 429 } 430 final long regionSizeMb = getRegionSizeMB(hri); 431 if (regionSizeMb > 2 * avgRegionSize) { 432 LOG.info( 433 "Table {}, large region {} has size {} MB, more than twice avg size {} MB, " 434 + "splitting", 435 ctx.getTableName(), hri.getRegionNameAsString(), regionSizeMb, 436 String.format("%.3f", avgRegionSize)); 437 plans.add(new SplitNormalizationPlan(hri, regionSizeMb)); 438 } 439 } 440 return plans; 441 } 442 443 /** 444 * Return {@code true} when {@code regionInfo} has a creation date that is old enough to be 445 * considered for a merge operation, {@code false} otherwise. 446 */ 447 private static boolean isOldEnoughForMerge(final NormalizerConfiguration normalizerConfiguration, 448 final NormalizeContext ctx, final RegionInfo regionInfo) { 449 final Instant currentTime = Instant.ofEpochMilli(EnvironmentEdgeManager.currentTime()); 450 final Instant regionCreateTime = Instant.ofEpochMilli(regionInfo.getRegionId()); 451 return currentTime 452 .isAfter(regionCreateTime.plus(normalizerConfiguration.getMergeMinRegionAge(ctx))); 453 } 454 455 /** 456 * Return {@code true} when {@code regionInfo} has a size that is sufficient to be considered for 457 * a merge operation, {@code false} otherwise. 458 * </p> 459 * Callers beware: for safe concurrency, be sure to pass in the local instance of 460 * {@link NormalizerConfiguration}, don't use {@code this}'s instance. 461 */ 462 private boolean isLargeEnoughForMerge(final NormalizerConfiguration normalizerConfiguration, 463 final NormalizeContext ctx, final RegionInfo regionInfo) { 464 return getRegionSizeMB(regionInfo) >= normalizerConfiguration.getMergeMinRegionSizeMb(ctx); 465 } 466 467 private static boolean logTraceReason(final BooleanSupplier predicate, final String fmtWhenTrue, 468 final Object... args) { 469 final boolean value = predicate.getAsBoolean(); 470 if (value) { 471 LOG.trace(fmtWhenTrue, args); 472 } 473 return value; 474 } 475 476 /** 477 * Holds the configuration values read from {@link Configuration}. Encapsulation in a POJO enables 478 * atomic hot-reloading of configs without locks. 479 */ 480 private static final class NormalizerConfiguration { 481 private final Configuration conf; 482 private final boolean splitEnabled; 483 private final boolean mergeEnabled; 484 private final int mergeMinRegionCount; 485 private final Period mergeMinRegionAge; 486 private final long mergeMinRegionSizeMb; 487 488 private NormalizerConfiguration() { 489 conf = null; 490 splitEnabled = DEFAULT_SPLIT_ENABLED; 491 mergeEnabled = DEFAULT_MERGE_ENABLED; 492 mergeMinRegionCount = DEFAULT_MERGE_MIN_REGION_COUNT; 493 mergeMinRegionAge = Period.ofDays(DEFAULT_MERGE_MIN_REGION_AGE_DAYS); 494 mergeMinRegionSizeMb = DEFAULT_MERGE_MIN_REGION_SIZE_MB; 495 } 496 497 private NormalizerConfiguration(final Configuration conf, 498 final NormalizerConfiguration currentConfiguration) { 499 this.conf = conf; 500 splitEnabled = conf.getBoolean(SPLIT_ENABLED_KEY, DEFAULT_SPLIT_ENABLED); 501 mergeEnabled = conf.getBoolean(MERGE_ENABLED_KEY, DEFAULT_MERGE_ENABLED); 502 mergeMinRegionCount = parseMergeMinRegionCount(conf); 503 mergeMinRegionAge = parseMergeMinRegionAge(conf); 504 mergeMinRegionSizeMb = parseMergeMinRegionSizeMb(conf); 505 logConfigurationUpdated(SPLIT_ENABLED_KEY, currentConfiguration.isSplitEnabled(), 506 splitEnabled); 507 logConfigurationUpdated(MERGE_ENABLED_KEY, currentConfiguration.isMergeEnabled(), 508 mergeEnabled); 509 logConfigurationUpdated(MERGE_MIN_REGION_COUNT_KEY, 510 currentConfiguration.getMergeMinRegionCount(), mergeMinRegionCount); 511 logConfigurationUpdated(MERGE_MIN_REGION_AGE_DAYS_KEY, 512 currentConfiguration.getMergeMinRegionAge(), mergeMinRegionAge); 513 logConfigurationUpdated(MERGE_MIN_REGION_SIZE_MB_KEY, 514 currentConfiguration.getMergeMinRegionSizeMb(), mergeMinRegionSizeMb); 515 } 516 517 public Configuration getConf() { 518 return conf; 519 } 520 521 public boolean isSplitEnabled() { 522 return splitEnabled; 523 } 524 525 public boolean isMergeEnabled() { 526 return mergeEnabled; 527 } 528 529 public int getMergeMinRegionCount() { 530 return mergeMinRegionCount; 531 } 532 533 public int getMergeMinRegionCount(NormalizeContext context) { 534 String stringValue = 535 context.getOrDefault(MERGE_MIN_REGION_COUNT_KEY, Function.identity(), null); 536 if (stringValue == null) { 537 stringValue = context.getOrDefault(MIN_REGION_COUNT_KEY, Function.identity(), null); 538 if (stringValue != null) { 539 LOG.debug( 540 "The config key {} in table descriptor is deprecated. Instead please use {}. " 541 + "In future release we will remove the deprecated config.", 542 MIN_REGION_COUNT_KEY, MERGE_MIN_REGION_COUNT_KEY); 543 } 544 } 545 final int mergeMinRegionCount = stringValue == null ? 0 : Integer.parseInt(stringValue); 546 if (mergeMinRegionCount <= 0) { 547 return getMergeMinRegionCount(); 548 } 549 return mergeMinRegionCount; 550 } 551 552 public Period getMergeMinRegionAge() { 553 return mergeMinRegionAge; 554 } 555 556 public Period getMergeMinRegionAge(NormalizeContext context) { 557 final int mergeMinRegionAge = 558 context.getOrDefault(MERGE_MIN_REGION_AGE_DAYS_KEY, Integer::parseInt, -1); 559 if (mergeMinRegionAge < 0) { 560 return getMergeMinRegionAge(); 561 } 562 return Period.ofDays(mergeMinRegionAge); 563 } 564 565 public long getMergeMinRegionSizeMb() { 566 return mergeMinRegionSizeMb; 567 } 568 569 public long getMergeMinRegionSizeMb(NormalizeContext context) { 570 final long mergeMinRegionSizeMb = 571 context.getOrDefault(MERGE_MIN_REGION_SIZE_MB_KEY, Long::parseLong, (long) -1); 572 if (mergeMinRegionSizeMb < 0) { 573 return getMergeMinRegionSizeMb(); 574 } 575 return mergeMinRegionSizeMb; 576 } 577 } 578 579 /** 580 * Inner class caries the state necessary to perform a single invocation of 581 * {@link #computePlansForTable(TableDescriptor)}. Grabbing this data from the assignment manager 582 * up-front allows any computed values to be realized just once. 583 */ 584 private class NormalizeContext { 585 private final TableName tableName; 586 private final RegionStates regionStates; 587 private final List<RegionInfo> tableRegions; 588 private final double averageRegionSizeMb; 589 private final TableDescriptor tableDescriptor; 590 591 public NormalizeContext(final TableDescriptor tableDescriptor) { 592 this.tableDescriptor = tableDescriptor; 593 tableName = tableDescriptor.getTableName(); 594 regionStates = 595 SimpleRegionNormalizer.this.masterServices.getAssignmentManager().getRegionStates(); 596 tableRegions = regionStates.getRegionsOfTable(tableName); 597 // The list of regionInfo from getRegionsOfTable() is ordered by regionName. 598 // regionName does not necessary guarantee the order by STARTKEY (let's say 'aa1', 'aa1!', 599 // in order by regionName, it will be 'aa1!' followed by 'aa1'). 600 // This could result in normalizer merging non-adjacent regions into one and creates overlaps. 601 // In order to avoid that, sort the list by RegionInfo.COMPARATOR. 602 // See HBASE-24376 603 tableRegions.sort(RegionInfo.COMPARATOR); 604 averageRegionSizeMb = 605 SimpleRegionNormalizer.this.getAverageRegionSizeMb(this.tableRegions, this.tableDescriptor); 606 } 607 608 public TableName getTableName() { 609 return tableName; 610 } 611 612 public RegionStates getRegionStates() { 613 return regionStates; 614 } 615 616 public List<RegionInfo> getTableRegions() { 617 return tableRegions; 618 } 619 620 public double getAverageRegionSizeMb() { 621 return averageRegionSizeMb; 622 } 623 624 public <T> T getOrDefault(String key, Function<String, T> function, T defaultValue) { 625 String value = tableDescriptor.getValue(key); 626 if (value == null) { 627 return defaultValue; 628 } else { 629 return function.apply(value); 630 } 631 } 632 } 633}