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 java.lang.String.format; 021import static org.apache.hadoop.hbase.master.normalizer.SimpleRegionNormalizer.DEFAULT_MERGE_MIN_REGION_AGE_DAYS; 022import static org.apache.hadoop.hbase.master.normalizer.SimpleRegionNormalizer.MERGE_ENABLED_KEY; 023import static org.apache.hadoop.hbase.master.normalizer.SimpleRegionNormalizer.MERGE_MIN_REGION_AGE_DAYS_KEY; 024import static org.apache.hadoop.hbase.master.normalizer.SimpleRegionNormalizer.MERGE_MIN_REGION_COUNT_KEY; 025import static org.apache.hadoop.hbase.master.normalizer.SimpleRegionNormalizer.MERGE_MIN_REGION_SIZE_MB_KEY; 026import static org.apache.hadoop.hbase.master.normalizer.SimpleRegionNormalizer.MIN_REGION_COUNT_KEY; 027import static org.apache.hadoop.hbase.master.normalizer.SimpleRegionNormalizer.SPLIT_ENABLED_KEY; 028import static org.hamcrest.MatcherAssert.assertThat; 029import static org.hamcrest.Matchers.contains; 030import static org.hamcrest.Matchers.empty; 031import static org.hamcrest.Matchers.everyItem; 032import static org.hamcrest.Matchers.greaterThanOrEqualTo; 033import static org.hamcrest.Matchers.instanceOf; 034import static org.hamcrest.Matchers.not; 035import static org.junit.Assert.assertEquals; 036import static org.junit.Assert.assertFalse; 037import static org.junit.Assert.assertTrue; 038import static org.mockito.ArgumentMatchers.any; 039import static org.mockito.Mockito.RETURNS_DEEP_STUBS; 040import static org.mockito.Mockito.when; 041 042import java.time.Instant; 043import java.time.Period; 044import java.util.ArrayList; 045import java.util.Collections; 046import java.util.HashMap; 047import java.util.List; 048import java.util.Map; 049import org.apache.hadoop.conf.Configuration; 050import org.apache.hadoop.hbase.HBaseClassTestRule; 051import org.apache.hadoop.hbase.HBaseConfiguration; 052import org.apache.hadoop.hbase.RegionMetrics; 053import org.apache.hadoop.hbase.ServerName; 054import org.apache.hadoop.hbase.Size; 055import org.apache.hadoop.hbase.TableName; 056import org.apache.hadoop.hbase.TableNameTestRule; 057import org.apache.hadoop.hbase.client.RegionInfo; 058import org.apache.hadoop.hbase.client.RegionInfoBuilder; 059import org.apache.hadoop.hbase.client.TableDescriptor; 060import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 061import org.apache.hadoop.hbase.master.MasterServices; 062import org.apache.hadoop.hbase.master.RegionState; 063import org.apache.hadoop.hbase.testclassification.MasterTests; 064import org.apache.hadoop.hbase.testclassification.SmallTests; 065import org.apache.hadoop.hbase.util.Bytes; 066import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 067import org.junit.Before; 068import org.junit.ClassRule; 069import org.junit.Rule; 070import org.junit.Test; 071import org.junit.experimental.categories.Category; 072import org.mockito.Mockito; 073 074/** 075 * Tests logic of {@link SimpleRegionNormalizer}. 076 */ 077@Category({ MasterTests.class, SmallTests.class }) 078public class TestSimpleRegionNormalizer { 079 080 @ClassRule 081 public static final HBaseClassTestRule CLASS_RULE = 082 HBaseClassTestRule.forClass(TestSimpleRegionNormalizer.class); 083 084 private Configuration conf; 085 private SimpleRegionNormalizer normalizer; 086 private MasterServices masterServices; 087 private TableDescriptor tableDescriptor; 088 089 @Rule 090 public TableNameTestRule name = new TableNameTestRule(); 091 092 @Before 093 public void before() { 094 conf = HBaseConfiguration.create(); 095 tableDescriptor = TableDescriptorBuilder.newBuilder(name.getTableName()).build(); 096 } 097 098 @Test 099 public void testNoNormalizationForMetaTable() { 100 TableName testTable = TableName.META_TABLE_NAME; 101 TableDescriptor testMetaTd = TableDescriptorBuilder.newBuilder(testTable).build(); 102 List<RegionInfo> RegionInfo = new ArrayList<>(); 103 Map<byte[], Integer> regionSizes = new HashMap<>(); 104 105 setupMocksForNormalizer(regionSizes, RegionInfo); 106 List<NormalizationPlan> plans = normalizer.computePlansForTable(testMetaTd); 107 assertThat(plans, empty()); 108 } 109 110 @Test 111 public void testNoNormalizationIfTooFewRegions() { 112 final TableName tableName = name.getTableName(); 113 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 2); 114 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 10, 15); 115 setupMocksForNormalizer(regionSizes, regionInfos); 116 117 List<NormalizationPlan> plans = normalizer.computePlansForTable(tableDescriptor); 118 assertThat(plans, empty()); 119 } 120 121 @Test 122 public void testNoNormalizationOnNormalizedCluster() { 123 final TableName tableName = name.getTableName(); 124 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 4); 125 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 10, 15, 8, 10); 126 setupMocksForNormalizer(regionSizes, regionInfos); 127 128 List<NormalizationPlan> plans = normalizer.computePlansForTable(tableDescriptor); 129 assertThat(plans, empty()); 130 } 131 132 private void noNormalizationOnTransitioningRegions(final RegionState.State state) { 133 final TableName tableName = name.getTableName(); 134 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 3); 135 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 10, 1, 100); 136 137 setupMocksForNormalizer(regionSizes, regionInfos); 138 when( 139 masterServices.getAssignmentManager().getRegionStates().getRegionState(any(RegionInfo.class))) 140 .thenReturn(RegionState.createForTesting(null, state)); 141 assertThat(normalizer.getMergeMinRegionCount(), greaterThanOrEqualTo(regionInfos.size())); 142 143 List<NormalizationPlan> plans = normalizer.computePlansForTable(tableDescriptor); 144 assertThat(format("Unexpected plans for RegionState %s", state), plans, empty()); 145 } 146 147 @Test 148 public void testNoNormalizationOnMergingNewRegions() { 149 noNormalizationOnTransitioningRegions(RegionState.State.MERGING_NEW); 150 } 151 152 @Test 153 public void testNoNormalizationOnMergingRegions() { 154 noNormalizationOnTransitioningRegions(RegionState.State.MERGING); 155 } 156 157 @Test 158 public void testNoNormalizationOnMergedRegions() { 159 noNormalizationOnTransitioningRegions(RegionState.State.MERGED); 160 } 161 162 @Test 163 public void testNoNormalizationOnSplittingNewRegions() { 164 noNormalizationOnTransitioningRegions(RegionState.State.SPLITTING_NEW); 165 } 166 167 @Test 168 public void testNoNormalizationOnSplittingRegions() { 169 noNormalizationOnTransitioningRegions(RegionState.State.SPLITTING); 170 } 171 172 @Test 173 public void testNoNormalizationOnSplitRegions() { 174 noNormalizationOnTransitioningRegions(RegionState.State.SPLIT); 175 } 176 177 @Test 178 public void testMergeOfSmallRegions() { 179 final TableName tableName = name.getTableName(); 180 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 5); 181 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 15, 5, 5, 15, 16); 182 setupMocksForNormalizer(regionSizes, regionInfos); 183 184 assertThat(normalizer.computePlansForTable(tableDescriptor), 185 contains(new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(1), 5) 186 .addTarget(regionInfos.get(2), 5).build())); 187 } 188 189 // Test for situation illustrated in HBASE-14867 190 @Test 191 public void testMergeOfSecondSmallestRegions() { 192 final TableName tableName = name.getTableName(); 193 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 6); 194 final Map<byte[], Integer> regionSizes = 195 createRegionSizesMap(regionInfos, 1, 10000, 10000, 10000, 2700, 2700); 196 setupMocksForNormalizer(regionSizes, regionInfos); 197 198 assertThat(normalizer.computePlansForTable(tableDescriptor), 199 contains(new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(4), 2700) 200 .addTarget(regionInfos.get(5), 2700).build())); 201 } 202 203 @Test 204 public void testMergeOfSmallNonAdjacentRegions() { 205 final TableName tableName = name.getTableName(); 206 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 5); 207 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 15, 5, 16, 15, 5); 208 setupMocksForNormalizer(regionSizes, regionInfos); 209 210 List<NormalizationPlan> plans = normalizer.computePlansForTable(tableDescriptor); 211 assertThat(plans, empty()); 212 } 213 214 @Test 215 public void testSplitOfLargeRegion() { 216 final TableName tableName = name.getTableName(); 217 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 4); 218 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 8, 6, 10, 30); 219 setupMocksForNormalizer(regionSizes, regionInfos); 220 221 assertThat(normalizer.computePlansForTable(tableDescriptor), 222 contains(new SplitNormalizationPlan(regionInfos.get(3), 30))); 223 } 224 225 @Test 226 public void testWithTargetRegionSize() throws Exception { 227 final TableName tableName = name.getTableName(); 228 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 6); 229 final Map<byte[], Integer> regionSizes = 230 createRegionSizesMap(regionInfos, 20, 40, 60, 80, 100, 120); 231 setupMocksForNormalizer(regionSizes, regionInfos); 232 233 // test when target region size is 20 234 when(tableDescriptor.getNormalizerTargetRegionSize()).thenReturn(20L); 235 assertThat(normalizer.computePlansForTable(tableDescriptor), 236 contains(new SplitNormalizationPlan(regionInfos.get(2), 60), 237 new SplitNormalizationPlan(regionInfos.get(3), 80), 238 new SplitNormalizationPlan(regionInfos.get(4), 100), 239 new SplitNormalizationPlan(regionInfos.get(5), 120))); 240 241 // test when target region size is 200 242 when(tableDescriptor.getNormalizerTargetRegionSize()).thenReturn(200L); 243 assertThat(normalizer.computePlansForTable(tableDescriptor), 244 contains(new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(0), 20) 245 .addTarget(regionInfos.get(1), 40).addTarget(regionInfos.get(2), 60) 246 .addTarget(regionInfos.get(3), 80).build())); 247 } 248 249 @Test 250 public void testSplitWithTargetRegionCount() throws Exception { 251 final TableName tableName = name.getTableName(); 252 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 4); 253 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 20, 40, 60, 80); 254 setupMocksForNormalizer(regionSizes, regionInfos); 255 256 // test when target region count is 8 257 when(tableDescriptor.getNormalizerTargetRegionCount()).thenReturn(8); 258 assertThat(normalizer.computePlansForTable(tableDescriptor), 259 contains(new SplitNormalizationPlan(regionInfos.get(2), 60), 260 new SplitNormalizationPlan(regionInfos.get(3), 80))); 261 262 // test when target region count is 3 263 when(tableDescriptor.getNormalizerTargetRegionCount()).thenReturn(3); 264 assertThat(normalizer.computePlansForTable(tableDescriptor), 265 contains(new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(0), 20) 266 .addTarget(regionInfos.get(1), 40).build())); 267 } 268 269 @Test 270 public void testHonorsSplitEnabled() { 271 conf.setBoolean(SPLIT_ENABLED_KEY, true); 272 final TableName tableName = name.getTableName(); 273 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 5); 274 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 5, 5, 20, 5, 5); 275 setupMocksForNormalizer(regionSizes, regionInfos); 276 assertThat(normalizer.computePlansForTable(tableDescriptor), 277 contains(instanceOf(SplitNormalizationPlan.class))); 278 279 conf.setBoolean(SPLIT_ENABLED_KEY, false); 280 setupMocksForNormalizer(regionSizes, regionInfos); 281 assertThat(normalizer.computePlansForTable(tableDescriptor), empty()); 282 } 283 284 @Test 285 public void testHonorsSplitEnabledInTD() { 286 conf.setBoolean(SPLIT_ENABLED_KEY, true); 287 final TableName tableName = name.getTableName(); 288 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 5); 289 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 5, 5, 20, 5, 5); 290 setupMocksForNormalizer(regionSizes, regionInfos); 291 assertThat(normalizer.computePlansForTable(tableDescriptor), 292 contains(instanceOf(SplitNormalizationPlan.class))); 293 294 // When hbase.normalizer.split.enabled is true in configuration, but false in table descriptor 295 when(tableDescriptor.getValue(SPLIT_ENABLED_KEY)).thenReturn("false"); 296 assertThat(normalizer.computePlansForTable(tableDescriptor), empty()); 297 298 // When hbase.normalizer.split.enabled is false in configuration, but true in table descriptor 299 conf.setBoolean(SPLIT_ENABLED_KEY, false); 300 setupMocksForNormalizer(regionSizes, regionInfos); 301 when(tableDescriptor.getValue(SPLIT_ENABLED_KEY)).thenReturn("true"); 302 assertThat(normalizer.computePlansForTable(tableDescriptor), 303 contains(instanceOf(SplitNormalizationPlan.class))); 304 } 305 306 @Test 307 public void testHonorsMergeEnabled() { 308 conf.setBoolean(MERGE_ENABLED_KEY, true); 309 final TableName tableName = name.getTableName(); 310 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 5); 311 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 20, 5, 5, 20, 20); 312 setupMocksForNormalizer(regionSizes, regionInfos); 313 assertThat(normalizer.computePlansForTable(tableDescriptor), 314 contains(instanceOf(MergeNormalizationPlan.class))); 315 316 conf.setBoolean(MERGE_ENABLED_KEY, false); 317 setupMocksForNormalizer(regionSizes, regionInfos); 318 assertThat(normalizer.computePlansForTable(tableDescriptor), empty()); 319 } 320 321 @Test 322 public void testHonorsMergeEnabledInTD() { 323 conf.setBoolean(MERGE_ENABLED_KEY, true); 324 final TableName tableName = name.getTableName(); 325 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 5); 326 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 20, 5, 5, 20, 20); 327 setupMocksForNormalizer(regionSizes, regionInfos); 328 assertThat(normalizer.computePlansForTable(tableDescriptor), 329 contains(instanceOf(MergeNormalizationPlan.class))); 330 331 // When hbase.normalizer.merge.enabled is true in configuration, but false in table descriptor 332 when(tableDescriptor.getValue(MERGE_ENABLED_KEY)).thenReturn("false"); 333 assertThat(normalizer.computePlansForTable(tableDescriptor), empty()); 334 335 // When hbase.normalizer.merge.enabled is false in configuration, but true in table descriptor 336 conf.setBoolean(MERGE_ENABLED_KEY, false); 337 setupMocksForNormalizer(regionSizes, regionInfos); 338 when(tableDescriptor.getValue(MERGE_ENABLED_KEY)).thenReturn("true"); 339 assertThat(normalizer.computePlansForTable(tableDescriptor), 340 contains(instanceOf(MergeNormalizationPlan.class))); 341 } 342 343 @Test 344 public void testHonorsMinimumRegionCount() { 345 honorsMinimumRegionCount(MERGE_MIN_REGION_COUNT_KEY); 346 } 347 348 /** 349 * Test the backward compatibility of the deprecated MIN_REGION_COUNT_KEY configuration. 350 */ 351 @Test 352 public void testHonorsOldMinimumRegionCount() { 353 honorsMinimumRegionCount(MIN_REGION_COUNT_KEY); 354 } 355 356 private void honorsMinimumRegionCount(String confKey) { 357 conf.setInt(confKey, 1); 358 final TableName tableName = name.getTableName(); 359 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 3); 360 // create a table topology that results in both a merge plan and a split plan. Assert that the 361 // merge is only created when the when the number of table regions is above the region count 362 // threshold, and that the split plan is create in both cases. 363 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 1, 1, 10); 364 setupMocksForNormalizer(regionSizes, regionInfos); 365 assertEquals(1, normalizer.getMergeMinRegionCount()); 366 367 List<NormalizationPlan> plans = normalizer.computePlansForTable(tableDescriptor); 368 assertThat(plans, 369 contains(new SplitNormalizationPlan(regionInfos.get(2), 10), 370 new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(0), 1) 371 .addTarget(regionInfos.get(1), 1).build())); 372 373 // have to call setupMocks again because we don't have dynamic config update on normalizer. 374 conf.setInt(confKey, 4); 375 setupMocksForNormalizer(regionSizes, regionInfos); 376 assertEquals(4, normalizer.getMergeMinRegionCount()); 377 assertThat(normalizer.computePlansForTable(tableDescriptor), 378 contains(new SplitNormalizationPlan(regionInfos.get(2), 10))); 379 } 380 381 @Test 382 public void testHonorsMinimumRegionCountInTD() { 383 honorsOldMinimumRegionCountInTD(MERGE_MIN_REGION_COUNT_KEY); 384 } 385 386 /** 387 * Test the backward compatibility of the deprecated MIN_REGION_COUNT_KEY configuration in table 388 * descriptor. 389 */ 390 @Test 391 public void testHonorsOldMinimumRegionCountInTD() { 392 honorsOldMinimumRegionCountInTD(MIN_REGION_COUNT_KEY); 393 } 394 395 private void honorsOldMinimumRegionCountInTD(String confKey) { 396 conf.setInt(confKey, 1); 397 final TableName tableName = name.getTableName(); 398 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 3); 399 // create a table topology that results in both a merge plan and a split plan. Assert that the 400 // merge is only created when the when the number of table regions is above the region count 401 // threshold, and that the split plan is create in both cases. 402 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 1, 1, 10); 403 setupMocksForNormalizer(regionSizes, regionInfos); 404 assertEquals(1, normalizer.getMergeMinRegionCount()); 405 406 List<NormalizationPlan> plans = normalizer.computePlansForTable(tableDescriptor); 407 assertThat(plans, 408 contains(new SplitNormalizationPlan(regionInfos.get(2), 10), 409 new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(0), 1) 410 .addTarget(regionInfos.get(1), 1).build())); 411 412 when(tableDescriptor.getValue(confKey)).thenReturn("4"); 413 assertThat(normalizer.computePlansForTable(tableDescriptor), 414 contains(new SplitNormalizationPlan(regionInfos.get(2), 10))); 415 } 416 417 @Test 418 public void testHonorsMergeMinRegionAge() { 419 conf.setInt(MERGE_MIN_REGION_AGE_DAYS_KEY, 7); 420 final TableName tableName = name.getTableName(); 421 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 4); 422 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 1, 1, 10, 10); 423 setupMocksForNormalizer(regionSizes, regionInfos); 424 assertEquals(Period.ofDays(7), normalizer.getMergeMinRegionAge()); 425 assertThat(normalizer.computePlansForTable(tableDescriptor), 426 everyItem(not(instanceOf(MergeNormalizationPlan.class)))); 427 428 // have to call setupMocks again because we don't have dynamic config update on normalizer. 429 conf.unset(MERGE_MIN_REGION_AGE_DAYS_KEY); 430 setupMocksForNormalizer(regionSizes, regionInfos); 431 assertEquals(Period.ofDays(DEFAULT_MERGE_MIN_REGION_AGE_DAYS), 432 normalizer.getMergeMinRegionAge()); 433 final List<NormalizationPlan> plans = normalizer.computePlansForTable(tableDescriptor); 434 assertThat(plans, not(empty())); 435 assertThat(plans, everyItem(instanceOf(MergeNormalizationPlan.class))); 436 } 437 438 @Test 439 public void testHonorsMergeMinRegionAgeInTD() { 440 conf.setInt(MERGE_MIN_REGION_AGE_DAYS_KEY, 7); 441 final TableName tableName = name.getTableName(); 442 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 4); 443 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 1, 1, 10, 10); 444 setupMocksForNormalizer(regionSizes, regionInfos); 445 assertEquals(Period.ofDays(7), normalizer.getMergeMinRegionAge()); 446 assertThat(normalizer.computePlansForTable(tableDescriptor), 447 everyItem(not(instanceOf(MergeNormalizationPlan.class)))); 448 449 conf.unset(MERGE_MIN_REGION_AGE_DAYS_KEY); 450 setupMocksForNormalizer(regionSizes, regionInfos); 451 when(tableDescriptor.getValue(MERGE_MIN_REGION_AGE_DAYS_KEY)).thenReturn("-1"); 452 List<NormalizationPlan> plans = normalizer.computePlansForTable(tableDescriptor); 453 assertThat(plans, not(empty())); 454 assertThat(plans, everyItem(instanceOf(MergeNormalizationPlan.class))); 455 456 when(tableDescriptor.getValue(MERGE_MIN_REGION_AGE_DAYS_KEY)).thenReturn("5"); 457 plans = normalizer.computePlansForTable(tableDescriptor); 458 assertThat(plans, empty()); 459 assertThat(plans, everyItem(not(instanceOf(MergeNormalizationPlan.class)))); 460 } 461 462 @Test 463 public void testHonorsMergeMinRegionSize() { 464 conf.setBoolean(SPLIT_ENABLED_KEY, false); 465 final TableName tableName = name.getTableName(); 466 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 5); 467 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 1, 2, 0, 10, 10); 468 setupMocksForNormalizer(regionSizes, regionInfos); 469 470 assertFalse(normalizer.isSplitEnabled()); 471 assertEquals(1, normalizer.getMergeMinRegionSizeMb()); 472 assertThat(normalizer.computePlansForTable(tableDescriptor), 473 contains(new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(0), 1) 474 .addTarget(regionInfos.get(1), 2).build())); 475 476 conf.setInt(MERGE_MIN_REGION_SIZE_MB_KEY, 3); 477 setupMocksForNormalizer(regionSizes, regionInfos); 478 assertEquals(3, normalizer.getMergeMinRegionSizeMb()); 479 assertThat(normalizer.computePlansForTable(tableDescriptor), empty()); 480 } 481 482 @Test 483 public void testHonorsMergeMinRegionSizeInTD() { 484 conf.setBoolean(SPLIT_ENABLED_KEY, false); 485 final TableName tableName = name.getTableName(); 486 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 5); 487 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 1, 2, 0, 10, 10); 488 setupMocksForNormalizer(regionSizes, regionInfos); 489 490 assertFalse(normalizer.isSplitEnabled()); 491 assertEquals(1, normalizer.getMergeMinRegionSizeMb()); 492 assertThat(normalizer.computePlansForTable(tableDescriptor), 493 contains(new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(0), 1) 494 .addTarget(regionInfos.get(1), 2).build())); 495 496 when(tableDescriptor.getValue(MERGE_MIN_REGION_SIZE_MB_KEY)).thenReturn("3"); 497 assertThat(normalizer.computePlansForTable(tableDescriptor), empty()); 498 } 499 500 @Test 501 public void testMergeEmptyRegions0() { 502 conf.setBoolean(SPLIT_ENABLED_KEY, false); 503 conf.setInt(MERGE_MIN_REGION_SIZE_MB_KEY, 0); 504 final TableName tableName = name.getTableName(); 505 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 7); 506 final Map<byte[], Integer> regionSizes = 507 createRegionSizesMap(regionInfos, 0, 1, 10, 0, 9, 10, 0); 508 setupMocksForNormalizer(regionSizes, regionInfos); 509 510 assertFalse(normalizer.isSplitEnabled()); 511 assertEquals(0, normalizer.getMergeMinRegionSizeMb()); 512 assertThat(normalizer.computePlansForTable(tableDescriptor), 513 contains( 514 new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(0), 0) 515 .addTarget(regionInfos.get(1), 1).build(), 516 new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(2), 10) 517 .addTarget(regionInfos.get(3), 0).build(), 518 new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(5), 10) 519 .addTarget(regionInfos.get(6), 0).build())); 520 } 521 522 @Test 523 public void testMergeEmptyRegions1() { 524 conf.setBoolean(SPLIT_ENABLED_KEY, false); 525 conf.setInt(MERGE_MIN_REGION_SIZE_MB_KEY, 0); 526 final TableName tableName = name.getTableName(); 527 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 8); 528 final Map<byte[], Integer> regionSizes = 529 createRegionSizesMap(regionInfos, 0, 1, 10, 0, 9, 0, 10, 0); 530 setupMocksForNormalizer(regionSizes, regionInfos); 531 532 assertFalse(normalizer.isSplitEnabled()); 533 assertEquals(0, normalizer.getMergeMinRegionSizeMb()); 534 assertThat(normalizer.computePlansForTable(tableDescriptor), 535 contains( 536 new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(0), 0) 537 .addTarget(regionInfos.get(1), 1).build(), 538 new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(2), 10) 539 .addTarget(regionInfos.get(3), 0).build(), 540 new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(4), 9) 541 .addTarget(regionInfos.get(5), 0).build(), 542 new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(6), 10) 543 .addTarget(regionInfos.get(7), 0).build())); 544 } 545 546 @Test 547 public void testMergeEmptyRegions2() { 548 conf.setBoolean(SPLIT_ENABLED_KEY, false); 549 conf.setInt(MERGE_MIN_REGION_SIZE_MB_KEY, 0); 550 final TableName tableName = name.getTableName(); 551 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 8); 552 final Map<byte[], Integer> regionSizes = 553 createRegionSizesMap(regionInfos, 0, 10, 1, 0, 9, 0, 10, 0); 554 setupMocksForNormalizer(regionSizes, regionInfos); 555 556 assertFalse(normalizer.isSplitEnabled()); 557 assertEquals(0, normalizer.getMergeMinRegionSizeMb()); 558 List<NormalizationPlan> plans = normalizer.computePlansForTable(tableDescriptor); 559 assertThat(plans, 560 contains( 561 new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(0), 0) 562 .addTarget(regionInfos.get(1), 10).build(), 563 new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(2), 1) 564 .addTarget(regionInfos.get(3), 0).build(), 565 new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(4), 9) 566 .addTarget(regionInfos.get(5), 0).build(), 567 new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(6), 10) 568 .addTarget(regionInfos.get(7), 0).build())); 569 } 570 571 @Test 572 public void testSplitAndMultiMerge() { 573 conf.setInt(MERGE_MIN_REGION_SIZE_MB_KEY, 0); 574 final TableName tableName = name.getTableName(); 575 final List<RegionInfo> regionInfos = createRegionInfos(tableName, 8); 576 final Map<byte[], Integer> regionSizes = 577 createRegionSizesMap(regionInfos, 3, 1, 1, 30, 9, 3, 1, 0); 578 setupMocksForNormalizer(regionSizes, regionInfos); 579 580 assertTrue(normalizer.isMergeEnabled()); 581 assertTrue(normalizer.isSplitEnabled()); 582 assertEquals(0, normalizer.getMergeMinRegionSizeMb()); 583 assertThat(normalizer.computePlansForTable(tableDescriptor), 584 contains(new SplitNormalizationPlan(regionInfos.get(3), 30), 585 new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(0), 3) 586 .addTarget(regionInfos.get(1), 1).addTarget(regionInfos.get(2), 1).build(), 587 new MergeNormalizationPlan.Builder().addTarget(regionInfos.get(5), 3) 588 .addTarget(regionInfos.get(6), 1).addTarget(regionInfos.get(7), 0).build())); 589 } 590 591 // This test is to make sure that normalizer is only going to merge adjacent regions. 592 @Test 593 public void testNormalizerCannotMergeNonAdjacentRegions() { 594 final TableName tableName = name.getTableName(); 595 // create 5 regions with sizes to trigger merge of small regions. region ranges are: 596 // [, "aa"), ["aa", "aa1"), ["aa1", "aa1!"), ["aa1!", "aa2"), ["aa2", ) 597 // Region ["aa", "aa1") and ["aa1!", "aa2") are not adjacent, they are not supposed to 598 // merged. 599 final byte[][] keys = { null, Bytes.toBytes("aa"), Bytes.toBytes("aa1!"), Bytes.toBytes("aa1"), 600 Bytes.toBytes("aa2"), null, }; 601 final List<RegionInfo> regionInfos = createRegionInfos(tableName, keys); 602 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 3, 1, 1, 3, 5); 603 setupMocksForNormalizer(regionSizes, regionInfos); 604 605 // Compute the plan, no merge plan returned as they are not adjacent. 606 List<NormalizationPlan> plans = normalizer.computePlansForTable(tableDescriptor); 607 assertThat(plans, empty()); 608 } 609 610 @SuppressWarnings("MockitoCast") 611 private void setupMocksForNormalizer(Map<byte[], Integer> regionSizes, 612 List<RegionInfo> regionInfoList) { 613 masterServices = Mockito.mock(MasterServices.class, RETURNS_DEEP_STUBS); 614 tableDescriptor = Mockito.mock(TableDescriptor.class, RETURNS_DEEP_STUBS); 615 616 // for simplicity all regions are assumed to be on one server; doesn't matter to us 617 ServerName sn = ServerName.valueOf("localhost", 0, 0L); 618 when(masterServices.getAssignmentManager().getRegionStates().getRegionsOfTable(any())) 619 .thenReturn(regionInfoList); 620 when(masterServices.getAssignmentManager().getRegionStates().getRegionServerOfRegion(any())) 621 .thenReturn(sn); 622 when( 623 masterServices.getAssignmentManager().getRegionStates().getRegionState(any(RegionInfo.class))) 624 .thenReturn(RegionState.createForTesting(null, RegionState.State.OPEN)); 625 626 for (Map.Entry<byte[], Integer> region : regionSizes.entrySet()) { 627 RegionMetrics regionLoad = Mockito.mock(RegionMetrics.class); 628 when(regionLoad.getRegionName()).thenReturn(region.getKey()); 629 when(regionLoad.getStoreFileSize()) 630 .thenReturn(new Size(region.getValue(), Size.Unit.MEGABYTE)); 631 632 // this is possibly broken with jdk9, unclear if false positive or not 633 // suppress it for now, fix it when we get to running tests on 9 634 // see: http://errorprone.info/bugpattern/MockitoCast 635 when((Object) masterServices.getServerManager().getLoad(sn).getRegionMetrics() 636 .get(region.getKey())).thenReturn(regionLoad); 637 } 638 639 when(masterServices.isSplitOrMergeEnabled(any())).thenReturn(true); 640 when(tableDescriptor.getTableName()).thenReturn(name.getTableName()); 641 642 normalizer = new SimpleRegionNormalizer(); 643 normalizer.setConf(conf); 644 normalizer.setMasterServices(masterServices); 645 } 646 647 /** 648 * Create a list of {@link RegionInfo}s that represent a region chain of the specified length. 649 */ 650 private static List<RegionInfo> createRegionInfos(final TableName tableName, final int length) { 651 if (length < 1) { 652 throw new IllegalStateException("length must be greater than or equal to 1."); 653 } 654 655 final byte[] startKey = Bytes.toBytes("aaaaa"); 656 final byte[] endKey = Bytes.toBytes("zzzzz"); 657 if (length == 1) { 658 return Collections.singletonList(createRegionInfo(tableName, startKey, endKey)); 659 } 660 661 final byte[][] splitKeys = Bytes.split(startKey, endKey, length - 1); 662 final List<RegionInfo> ret = new ArrayList<>(length); 663 for (int i = 0; i < splitKeys.length - 1; i++) { 664 ret.add(createRegionInfo(tableName, splitKeys[i], splitKeys[i + 1])); 665 } 666 return ret; 667 } 668 669 private static RegionInfo createRegionInfo(final TableName tableName, final byte[] startKey, 670 final byte[] endKey) { 671 return RegionInfoBuilder.newBuilder(tableName).setStartKey(startKey).setEndKey(endKey) 672 .setRegionId(generateRegionId()).build(); 673 } 674 675 private static long generateRegionId() { 676 return Instant.ofEpochMilli(EnvironmentEdgeManager.currentTime()) 677 .minus(Period.ofDays(DEFAULT_MERGE_MIN_REGION_AGE_DAYS + 1)).toEpochMilli(); 678 } 679 680 private static List<RegionInfo> createRegionInfos(final TableName tableName, 681 final byte[][] splitKeys) { 682 final List<RegionInfo> ret = new ArrayList<>(splitKeys.length); 683 for (int i = 0; i < splitKeys.length - 1; i++) { 684 ret.add(createRegionInfo(tableName, splitKeys[i], splitKeys[i + 1])); 685 } 686 return ret; 687 } 688 689 private static Map<byte[], Integer> createRegionSizesMap(final List<RegionInfo> regionInfos, 690 int... sizes) { 691 if (regionInfos.size() != sizes.length) { 692 throw new IllegalStateException("Parameter lengths must match."); 693 } 694 695 final Map<byte[], Integer> ret = new HashMap<>(regionInfos.size()); 696 for (int i = 0; i < regionInfos.size(); i++) { 697 ret.put(regionInfos.get(i).getRegionName(), sizes[i]); 698 } 699 return ret; 700 } 701}