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.regionserver; 019 020import static org.apache.hadoop.hbase.regionserver.StripeStoreFileManager.OPEN_KEY; 021import static org.junit.jupiter.api.Assertions.assertArrayEquals; 022import static org.junit.jupiter.api.Assertions.assertEquals; 023import static org.junit.jupiter.api.Assertions.assertFalse; 024import static org.junit.jupiter.api.Assertions.assertThrows; 025import static org.junit.jupiter.api.Assertions.assertTrue; 026 027import java.io.IOException; 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.Collection; 031import java.util.Collections; 032import java.util.Iterator; 033import java.util.List; 034import org.apache.hadoop.conf.Configuration; 035import org.apache.hadoop.fs.FileSystem; 036import org.apache.hadoop.fs.Path; 037import org.apache.hadoop.hbase.CellComparatorImpl; 038import org.apache.hadoop.hbase.HBaseConfiguration; 039import org.apache.hadoop.hbase.HBaseTestingUtil; 040import org.apache.hadoop.hbase.HConstants; 041import org.apache.hadoop.hbase.KeyValue; 042import org.apache.hadoop.hbase.testclassification.MediumTests; 043import org.apache.hadoop.hbase.testclassification.RegionServerTests; 044import org.apache.hadoop.hbase.util.Bytes; 045import org.junit.jupiter.api.AfterEach; 046import org.junit.jupiter.api.BeforeEach; 047import org.junit.jupiter.api.Tag; 048import org.junit.jupiter.api.Test; 049import org.mockito.Mockito; 050 051@Tag(RegionServerTests.TAG) 052@Tag(MediumTests.TAG) 053public class TestStripeStoreFileManager { 054 055 private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 056 private static final Path BASEDIR = 057 TEST_UTIL.getDataTestDir(TestStripeStoreFileManager.class.getSimpleName()); 058 private static final Path CFDIR = 059 HRegionFileSystem.getStoreHomedir(BASEDIR, "region", Bytes.toBytes("cf")); 060 061 private static final byte[] KEY_A = Bytes.toBytes("aaa"); 062 private static final byte[] KEY_B = Bytes.toBytes("aab"); 063 private static final byte[] KEY_C = Bytes.toBytes("aac"); 064 private static final byte[] KEY_D = Bytes.toBytes("aad"); 065 066 private static final KeyValue KV_A = new KeyValue(KEY_A, 0L); 067 private static final KeyValue KV_B = new KeyValue(KEY_B, 0L); 068 private static final KeyValue KV_C = new KeyValue(KEY_C, 0L); 069 private static final KeyValue KV_D = new KeyValue(KEY_D, 0L); 070 071 @BeforeEach 072 public void setUp() throws Exception { 073 FileSystem fs = TEST_UTIL.getTestFileSystem(); 074 if (!fs.mkdirs(CFDIR)) { 075 throw new IOException("Cannot create test directory " + CFDIR); 076 } 077 } 078 079 @AfterEach 080 public void tearDown() throws Exception { 081 FileSystem fs = TEST_UTIL.getTestFileSystem(); 082 if (fs.exists(CFDIR) && !fs.delete(CFDIR, true)) { 083 throw new IOException("Cannot delete test directory " + CFDIR); 084 } 085 } 086 087 @Test 088 public void testInsertFilesIntoL0() throws Exception { 089 StripeStoreFileManager manager = createManager(); 090 MockHStoreFile sf = createFile(); 091 manager.insertNewFiles(al(sf)); 092 assertEquals(1, manager.getStorefileCount()); 093 Collection<HStoreFile> filesForGet = manager.getFilesForScan(KEY_A, true, KEY_A, true, false); 094 assertEquals(1, filesForGet.size()); 095 assertTrue(filesForGet.contains(sf)); 096 097 // Add some stripes and make sure we get this file for every stripe. 098 manager.addCompactionResults(al(), 099 al(createFile(OPEN_KEY, KEY_B), createFile(KEY_B, OPEN_KEY))); 100 assertTrue(manager.getFilesForScan(KEY_A, true, KEY_A, true, false).contains(sf)); 101 assertTrue(manager.getFilesForScan(KEY_C, true, KEY_C, true, false).contains(sf)); 102 } 103 104 @Test 105 public void testClearFiles() throws Exception { 106 StripeStoreFileManager manager = createManager(); 107 manager.insertNewFiles(al(createFile())); 108 manager.insertNewFiles(al(createFile())); 109 manager.addCompactionResults(al(), 110 al(createFile(OPEN_KEY, KEY_B), createFile(KEY_B, OPEN_KEY))); 111 assertEquals(4, manager.getStorefileCount()); 112 Collection<HStoreFile> allFiles = manager.clearFiles(); 113 assertEquals(4, allFiles.size()); 114 assertEquals(0, manager.getStorefileCount()); 115 assertEquals(0, manager.getStoreFiles().size()); 116 } 117 118 private static ArrayList<HStoreFile> dumpIterator(Iterator<HStoreFile> iter) { 119 ArrayList<HStoreFile> result = new ArrayList<>(); 120 for (; iter.hasNext(); result.add(iter.next())) { 121 continue; 122 } 123 return result; 124 } 125 126 @Test 127 public void testRowKeyBefore() throws Exception { 128 StripeStoreFileManager manager = createManager(); 129 HStoreFile l0File = createFile(), l0File2 = createFile(); 130 manager.insertNewFiles(al(l0File)); 131 manager.insertNewFiles(al(l0File2)); 132 // Get candidate files. 133 Iterator<HStoreFile> sfs = manager.getCandidateFilesForRowKeyBefore(KV_B); 134 sfs.next(); 135 sfs.remove(); 136 // Suppose we found a candidate in this file... make sure L0 file remaining is not removed. 137 sfs = manager.updateCandidateFilesForRowKeyBefore(sfs, KV_B, KV_A); 138 assertTrue(sfs.hasNext()); 139 // Now add some stripes (remove L0 file too) 140 MockHStoreFile stripe0a = createFile(0, 100, OPEN_KEY, KEY_B), 141 stripe1 = createFile(KEY_B, OPEN_KEY); 142 manager.addCompactionResults(al(l0File), al(stripe0a, stripe1)); 143 manager.removeCompactedFiles(al(l0File)); 144 // If we want a key <= KEY_A, we should get everything except stripe1. 145 ArrayList<HStoreFile> sfsDump = dumpIterator(manager.getCandidateFilesForRowKeyBefore(KV_A)); 146 assertEquals(2, sfsDump.size()); 147 assertTrue(sfsDump.contains(stripe0a)); 148 assertFalse(sfsDump.contains(stripe1)); 149 // If we want a key <= KEY_B, we should get everything since lower bound is inclusive. 150 sfsDump = dumpIterator(manager.getCandidateFilesForRowKeyBefore(KV_B)); 151 assertEquals(3, sfsDump.size()); 152 assertTrue(sfsDump.contains(stripe1)); 153 // For KEY_D, we should also get everything. 154 sfsDump = dumpIterator(manager.getCandidateFilesForRowKeyBefore(KV_D)); 155 assertEquals(3, sfsDump.size()); 156 // Suppose in the first file we found candidate with KEY_C. 157 // Then, stripe0 no longer matters and should be removed, but stripe1 should stay. 158 sfs = manager.getCandidateFilesForRowKeyBefore(KV_D); 159 sfs.next(); // Skip L0 file. 160 sfs.remove(); 161 sfs = manager.updateCandidateFilesForRowKeyBefore(sfs, KV_D, KV_C); 162 assertEquals(stripe1, sfs.next()); 163 assertFalse(sfs.hasNext()); 164 // Add one more, later, file to stripe0, remove the last annoying L0 file. 165 // This file should be returned in preference to older L0 file; also, after we get 166 // a candidate from the first file, the old one should not be removed. 167 HStoreFile stripe0b = createFile(0, 101, OPEN_KEY, KEY_B); 168 manager.addCompactionResults(al(l0File2), al(stripe0b)); 169 manager.removeCompactedFiles(al(l0File2)); 170 sfs = manager.getCandidateFilesForRowKeyBefore(KV_A); 171 assertEquals(stripe0b, sfs.next()); 172 sfs.remove(); 173 sfs = manager.updateCandidateFilesForRowKeyBefore(sfs, KV_A, KV_A); 174 assertEquals(stripe0a, sfs.next()); 175 } 176 177 @Test 178 public void testGetSplitPointEdgeCases() throws Exception { 179 StripeStoreFileManager manager = createManager(); 180 // No files => no split. 181 assertFalse(manager.getSplitPoint().isPresent()); 182 183 // If there are no stripes, should pick midpoint from the biggest file in L0. 184 MockHStoreFile sf5 = createFile(5, 0); 185 sf5.splitPoint = new byte[] { 1 }; 186 manager.insertNewFiles(al(sf5)); 187 manager.insertNewFiles(al(createFile(1, 0))); 188 assertArrayEquals(sf5.splitPoint, manager.getSplitPoint().get()); 189 190 // Same if there's one stripe but the biggest file is still in L0. 191 manager.addCompactionResults(al(), al(createFile(2, 0, OPEN_KEY, OPEN_KEY))); 192 assertArrayEquals(sf5.splitPoint, manager.getSplitPoint().get()); 193 194 // If the biggest file is in the stripe, should get from it. 195 MockHStoreFile sf6 = createFile(6, 0, OPEN_KEY, OPEN_KEY); 196 sf6.splitPoint = new byte[] { 2 }; 197 manager.addCompactionResults(al(), al(sf6)); 198 assertArrayEquals(sf6.splitPoint, manager.getSplitPoint().get()); 199 } 200 201 @Test 202 public void testGetStripeBoundarySplits() throws Exception { 203 /* First number - split must be after this stripe; further numbers - stripes */ 204 verifySplitPointScenario(5, false, 0f, 2, 1, 1, 1, 1, 1, 10); 205 verifySplitPointScenario(0, false, 0f, 6, 3, 1, 1, 2); 206 verifySplitPointScenario(2, false, 0f, 1, 1, 1, 1, 2); 207 verifySplitPointScenario(0, false, 0f, 5, 4); 208 verifySplitPointScenario(2, false, 0f, 5, 2, 5, 5, 5); 209 } 210 211 @Test 212 public void testGetUnbalancedSplits() throws Exception { 213 /* First number - split must be inside/after this stripe; further numbers - stripes */ 214 verifySplitPointScenario(0, false, 2.1f, 4, 4, 4); // 8/4 is less than 2.1f 215 verifySplitPointScenario(1, true, 1.5f, 4, 4, 4); // 8/4 > 6/6 216 verifySplitPointScenario(1, false, 1.1f, 3, 4, 1, 1, 2, 2); // 7/6 < 8/5 217 verifySplitPointScenario(1, false, 1.1f, 3, 6, 1, 1, 2, 2); // 9/6 == 9/6 218 verifySplitPointScenario(1, true, 1.1f, 3, 8, 1, 1, 2, 2); // 11/6 > 10/7 219 verifySplitPointScenario(3, false, 1.1f, 2, 2, 1, 1, 4, 3); // reverse order 220 verifySplitPointScenario(4, true, 1.1f, 2, 2, 1, 1, 8, 3); // reverse order 221 verifySplitPointScenario(0, true, 1.5f, 10, 4); // 10/4 > 9/5 222 verifySplitPointScenario(0, false, 1.4f, 6, 4); // 6/4 == 6/4 223 verifySplitPointScenario(1, true, 1.5f, 4, 10); // reverse just in case 224 verifySplitPointScenario(0, false, 1.4f, 4, 6); // reverse just in case 225 } 226 227 /** 228 * Verifies scenario for finding a split point. 229 * @param splitPointAfter Stripe to expect the split point at/after. 230 * @param shouldSplitStripe If true, the split point is expected in the middle of the above 231 * stripe; if false, should be at the end. 232 * @param splitRatioToVerify Maximum split imbalance ratio. 233 * @param sizes Stripe sizes. 234 */ 235 private void verifySplitPointScenario(int splitPointAfter, boolean shouldSplitStripe, 236 float splitRatioToVerify, int... sizes) throws Exception { 237 assertTrue(sizes.length > 1); 238 ArrayList<HStoreFile> sfs = new ArrayList<>(); 239 for (int sizeIx = 0; sizeIx < sizes.length; ++sizeIx) { 240 byte[] startKey = (sizeIx == 0) ? OPEN_KEY : Bytes.toBytes(sizeIx - 1); 241 byte[] endKey = (sizeIx == sizes.length - 1) ? OPEN_KEY : Bytes.toBytes(sizeIx); 242 MockHStoreFile sf = createFile(sizes[sizeIx], 0, startKey, endKey); 243 sf.splitPoint = Bytes.toBytes(-sizeIx); // set split point to the negative index 244 sfs.add(sf); 245 } 246 247 Configuration conf = HBaseConfiguration.create(); 248 if (splitRatioToVerify != 0) { 249 conf.setFloat(StripeStoreConfig.MAX_REGION_SPLIT_IMBALANCE_KEY, splitRatioToVerify); 250 } 251 StripeStoreFileManager manager = createManager(al(), conf); 252 manager.addCompactionResults(al(), sfs); 253 int result = Bytes.toInt(manager.getSplitPoint().get()); 254 // Either end key and thus positive index, or "middle" of the file and thus negative index. 255 assertEquals(splitPointAfter * (shouldSplitStripe ? -1 : 1), result); 256 } 257 258 private static byte[] keyAfter(byte[] key) { 259 return Arrays.copyOf(key, key.length + 1); 260 } 261 262 @Test 263 public void testGetFilesForGetAndScan() throws Exception { 264 StripeStoreFileManager manager = createManager(); 265 verifyGetAndScanScenario(manager, null, null); 266 verifyGetAndScanScenario(manager, KEY_B, KEY_C); 267 268 // Populate one L0 file. 269 MockHStoreFile sf0 = createFile(); 270 manager.insertNewFiles(al(sf0)); 271 verifyGetAndScanScenario(manager, null, null, sf0); 272 verifyGetAndScanScenario(manager, null, KEY_C, sf0); 273 verifyGetAndScanScenario(manager, KEY_B, null, sf0); 274 verifyGetAndScanScenario(manager, KEY_B, KEY_C, sf0); 275 276 // Populate a bunch of files for stripes, keep L0. 277 MockHStoreFile sfA = createFile(OPEN_KEY, KEY_A); 278 MockHStoreFile sfB = createFile(KEY_A, KEY_B); 279 MockHStoreFile sfC = createFile(KEY_B, KEY_C); 280 MockHStoreFile sfD = createFile(KEY_C, KEY_D); 281 MockHStoreFile sfE = createFile(KEY_D, OPEN_KEY); 282 manager.addCompactionResults(al(), al(sfA, sfB, sfC, sfD, sfE)); 283 284 verifyGetAndScanScenario(manager, null, null, sf0, sfA, sfB, sfC, sfD, sfE); 285 verifyGetAndScanScenario(manager, keyAfter(KEY_A), null, sf0, sfB, sfC, sfD, sfE); 286 verifyGetAndScanScenario(manager, null, keyAfter(KEY_C), sf0, sfA, sfB, sfC, sfD); 287 verifyGetAndScanScenario(manager, KEY_B, null, sf0, sfC, sfD, sfE); 288 verifyGetAndScanScenario(manager, null, KEY_C, sf0, sfA, sfB, sfC, sfD); 289 verifyGetAndScanScenario(manager, KEY_B, keyAfter(KEY_B), sf0, sfC); 290 verifyGetAndScanScenario(manager, keyAfter(KEY_A), KEY_B, sf0, sfB, sfC); 291 verifyGetAndScanScenario(manager, KEY_D, KEY_D, sf0, sfE); 292 verifyGetAndScanScenario(manager, keyAfter(KEY_B), keyAfter(KEY_C), sf0, sfC, sfD); 293 } 294 295 private void verifyGetAndScanScenario(StripeStoreFileManager manager, byte[] start, byte[] end, 296 HStoreFile... results) throws Exception { 297 verifyGetOrScanScenario(manager, start, end, results); 298 } 299 300 @Test 301 @SuppressWarnings("unchecked") 302 public void testLoadFilesWithRecoverableBadFiles() throws Exception { 303 // In L0, there will be file w/o metadata (real L0, 3 files with invalid metadata, and 3 304 // files that overlap valid stripes in various ways). Note that the 4th way to overlap the 305 // stripes will cause the structure to be mostly scraped, and is tested separately. 306 ArrayList<HStoreFile> validStripeFiles = al(createFile(OPEN_KEY, KEY_B), 307 createFile(KEY_B, KEY_C), createFile(KEY_C, OPEN_KEY), createFile(KEY_C, OPEN_KEY)); 308 ArrayList<HStoreFile> filesToGoToL0 = al(createFile(), createFile(null, KEY_A), 309 createFile(KEY_D, null), createFile(KEY_D, KEY_A), createFile(keyAfter(KEY_A), KEY_C), 310 createFile(OPEN_KEY, KEY_D), createFile(KEY_D, keyAfter(KEY_D))); 311 ArrayList<HStoreFile> allFilesToGo = flattenLists(validStripeFiles, filesToGoToL0); 312 Collections.shuffle(allFilesToGo); 313 StripeStoreFileManager manager = createManager(allFilesToGo); 314 List<HStoreFile> l0Files = manager.getLevel0Files(); 315 assertEquals(filesToGoToL0.size(), l0Files.size()); 316 for (HStoreFile sf : filesToGoToL0) { 317 assertTrue(l0Files.contains(sf)); 318 } 319 verifyAllFiles(manager, allFilesToGo); 320 } 321 322 @Test 323 public void testLoadFilesWithBadStripe() throws Exception { 324 // Current "algorithm" will see the after-B key before C key, add it as valid stripe, 325 // and then fail all other stripes. So everything would end up in L0. 326 ArrayList<HStoreFile> allFilesToGo = al(createFile(OPEN_KEY, KEY_B), createFile(KEY_B, KEY_C), 327 createFile(KEY_C, OPEN_KEY), createFile(KEY_B, keyAfter(KEY_B))); 328 Collections.shuffle(allFilesToGo); 329 StripeStoreFileManager manager = createManager(allFilesToGo); 330 assertEquals(allFilesToGo.size(), manager.getLevel0Files().size()); 331 } 332 333 @Test 334 public void testLoadFilesWithGaps() throws Exception { 335 // Stripes must not have gaps. If they do, everything goes to L0. 336 StripeStoreFileManager manager = 337 createManager(al(createFile(OPEN_KEY, KEY_B), createFile(KEY_C, OPEN_KEY))); 338 assertEquals(2, manager.getLevel0Files().size()); 339 // Just one open stripe should be ok. 340 manager = createManager(al(createFile(OPEN_KEY, OPEN_KEY))); 341 assertEquals(0, manager.getLevel0Files().size()); 342 assertEquals(1, manager.getStorefileCount()); 343 } 344 345 @Test 346 public void testLoadFilesAfterSplit() throws Exception { 347 // If stripes are good but have non-open ends, they must be treated as open ends. 348 MockHStoreFile sf = createFile(KEY_B, KEY_C); 349 StripeStoreFileManager manager = createManager(al(createFile(OPEN_KEY, KEY_B), sf)); 350 assertEquals(0, manager.getLevel0Files().size()); 351 // Here, [B, C] is logically [B, inf), so we should be able to compact it to that only. 352 verifyInvalidCompactionScenario(manager, al(sf), al(createFile(KEY_B, KEY_C))); 353 manager.addCompactionResults(al(sf), al(createFile(KEY_B, OPEN_KEY))); 354 manager.removeCompactedFiles(al(sf)); 355 // Do the same for other variants. 356 manager = createManager(al(sf, createFile(KEY_C, OPEN_KEY))); 357 verifyInvalidCompactionScenario(manager, al(sf), al(createFile(KEY_B, KEY_C))); 358 manager.addCompactionResults(al(sf), al(createFile(OPEN_KEY, KEY_C))); 359 manager.removeCompactedFiles(al(sf)); 360 manager = createManager(al(sf)); 361 verifyInvalidCompactionScenario(manager, al(sf), al(createFile(KEY_B, KEY_C))); 362 manager.addCompactionResults(al(sf), al(createFile(OPEN_KEY, OPEN_KEY))); 363 } 364 365 @Test 366 public void testAddingCompactionResults() throws Exception { 367 StripeStoreFileManager manager = createManager(); 368 // First, add some L0 files and "compact" one with new stripe creation. 369 HStoreFile sf_L0_0a = createFile(), sf_L0_0b = createFile(); 370 manager.insertNewFiles(al(sf_L0_0a, sf_L0_0b)); 371 372 // Try compacting with invalid new branches (gaps, overlaps) - no effect. 373 verifyInvalidCompactionScenario(manager, al(sf_L0_0a), al(createFile(OPEN_KEY, KEY_B))); 374 verifyInvalidCompactionScenario(manager, al(sf_L0_0a), 375 al(createFile(OPEN_KEY, KEY_B), createFile(KEY_C, OPEN_KEY))); 376 verifyInvalidCompactionScenario(manager, al(sf_L0_0a), 377 al(createFile(OPEN_KEY, KEY_B), createFile(KEY_B, OPEN_KEY), createFile(KEY_A, KEY_D))); 378 verifyInvalidCompactionScenario(manager, al(sf_L0_0a), 379 al(createFile(OPEN_KEY, KEY_B), createFile(KEY_A, KEY_B), createFile(KEY_B, OPEN_KEY))); 380 381 HStoreFile sf_i2B_0 = createFile(OPEN_KEY, KEY_B); 382 HStoreFile sf_B2C_0 = createFile(KEY_B, KEY_C); 383 HStoreFile sf_C2i_0 = createFile(KEY_C, OPEN_KEY); 384 manager.addCompactionResults(al(sf_L0_0a), al(sf_i2B_0, sf_B2C_0, sf_C2i_0)); 385 manager.removeCompactedFiles(al(sf_L0_0a)); 386 verifyAllFiles(manager, al(sf_L0_0b, sf_i2B_0, sf_B2C_0, sf_C2i_0)); 387 388 // Add another l0 file, "compact" both L0 into two stripes 389 HStoreFile sf_L0_1 = createFile(); 390 HStoreFile sf_i2B_1 = createFile(OPEN_KEY, KEY_B); 391 HStoreFile sf_B2C_1 = createFile(KEY_B, KEY_C); 392 manager.insertNewFiles(al(sf_L0_1)); 393 manager.addCompactionResults(al(sf_L0_0b, sf_L0_1), al(sf_i2B_1, sf_B2C_1)); 394 manager.removeCompactedFiles(al(sf_L0_0b, sf_L0_1)); 395 verifyAllFiles(manager, al(sf_i2B_0, sf_B2C_0, sf_C2i_0, sf_i2B_1, sf_B2C_1)); 396 397 // Try compacting with invalid file (no metadata) - should add files to L0. 398 HStoreFile sf_L0_2 = createFile(null, null); 399 manager.addCompactionResults(al(), al(sf_L0_2)); 400 manager.removeCompactedFiles(al()); 401 verifyAllFiles(manager, al(sf_i2B_0, sf_B2C_0, sf_C2i_0, sf_i2B_1, sf_B2C_1, sf_L0_2)); 402 // Remove it... 403 manager.addCompactionResults(al(sf_L0_2), al()); 404 manager.removeCompactedFiles(al(sf_L0_2)); 405 406 // Do regular compaction in the first stripe. 407 HStoreFile sf_i2B_3 = createFile(OPEN_KEY, KEY_B); 408 manager.addCompactionResults(al(sf_i2B_0, sf_i2B_1), al(sf_i2B_3)); 409 manager.removeCompactedFiles(al(sf_i2B_0, sf_i2B_1)); 410 verifyAllFiles(manager, al(sf_B2C_0, sf_C2i_0, sf_B2C_1, sf_i2B_3)); 411 412 // Rebalance two stripes. 413 HStoreFile sf_B2D_4 = createFile(KEY_B, KEY_D); 414 HStoreFile sf_D2i_4 = createFile(KEY_D, OPEN_KEY); 415 manager.addCompactionResults(al(sf_B2C_0, sf_C2i_0, sf_B2C_1), al(sf_B2D_4, sf_D2i_4)); 416 manager.removeCompactedFiles(al(sf_B2C_0, sf_C2i_0, sf_B2C_1)); 417 verifyAllFiles(manager, al(sf_i2B_3, sf_B2D_4, sf_D2i_4)); 418 419 // Split the first stripe. 420 HStoreFile sf_i2A_5 = createFile(OPEN_KEY, KEY_A); 421 HStoreFile sf_A2B_5 = createFile(KEY_A, KEY_B); 422 manager.addCompactionResults(al(sf_i2B_3), al(sf_i2A_5, sf_A2B_5)); 423 manager.removeCompactedFiles(al(sf_i2B_3)); 424 verifyAllFiles(manager, al(sf_B2D_4, sf_D2i_4, sf_i2A_5, sf_A2B_5)); 425 426 // Split the middle stripe. 427 HStoreFile sf_B2C_6 = createFile(KEY_B, KEY_C); 428 HStoreFile sf_C2D_6 = createFile(KEY_C, KEY_D); 429 manager.addCompactionResults(al(sf_B2D_4), al(sf_B2C_6, sf_C2D_6)); 430 manager.removeCompactedFiles(al(sf_B2D_4)); 431 verifyAllFiles(manager, al(sf_D2i_4, sf_i2A_5, sf_A2B_5, sf_B2C_6, sf_C2D_6)); 432 433 // Merge two different middle stripes. 434 HStoreFile sf_A2C_7 = createFile(KEY_A, KEY_C); 435 manager.addCompactionResults(al(sf_A2B_5, sf_B2C_6), al(sf_A2C_7)); 436 manager.removeCompactedFiles(al(sf_A2B_5, sf_B2C_6)); 437 verifyAllFiles(manager, al(sf_D2i_4, sf_i2A_5, sf_C2D_6, sf_A2C_7)); 438 439 // Merge lower half. 440 HStoreFile sf_i2C_8 = createFile(OPEN_KEY, KEY_C); 441 manager.addCompactionResults(al(sf_i2A_5, sf_A2C_7), al(sf_i2C_8)); 442 manager.removeCompactedFiles(al(sf_i2A_5, sf_A2C_7)); 443 verifyAllFiles(manager, al(sf_D2i_4, sf_C2D_6, sf_i2C_8)); 444 445 // Merge all. 446 HStoreFile sf_i2i_9 = createFile(OPEN_KEY, OPEN_KEY); 447 manager.addCompactionResults(al(sf_D2i_4, sf_C2D_6, sf_i2C_8), al(sf_i2i_9)); 448 manager.removeCompactedFiles(al(sf_D2i_4, sf_C2D_6, sf_i2C_8)); 449 verifyAllFiles(manager, al(sf_i2i_9)); 450 } 451 452 @Test 453 public void testCompactionAndFlushConflict() throws Exception { 454 // Add file flush into stripes 455 StripeStoreFileManager sfm = createManager(); 456 assertEquals(0, sfm.getStripeCount()); 457 HStoreFile sf_i2c = createFile(OPEN_KEY, KEY_C), sf_c2i = createFile(KEY_C, OPEN_KEY); 458 sfm.insertNewFiles(al(sf_i2c, sf_c2i)); 459 assertEquals(2, sfm.getStripeCount()); 460 // Now try to add conflicting flush - should throw. 461 HStoreFile sf_i2d = createFile(OPEN_KEY, KEY_D), sf_d2i = createFile(KEY_D, OPEN_KEY); 462 sfm.insertNewFiles(al(sf_i2d, sf_d2i)); 463 assertEquals(2, sfm.getStripeCount()); 464 assertEquals(2, sfm.getLevel0Files().size()); 465 verifyGetAndScanScenario(sfm, KEY_C, KEY_C, sf_i2d, sf_d2i, sf_c2i); 466 // Remove these files. 467 sfm.addCompactionResults(al(sf_i2d, sf_d2i), al()); 468 sfm.removeCompactedFiles(al(sf_i2d, sf_d2i)); 469 assertEquals(0, sfm.getLevel0Files().size()); 470 // Add another file to stripe; then "rebalance" stripes w/o it - the file, which was 471 // presumably flushed during compaction, should go to L0. 472 HStoreFile sf_i2c_2 = createFile(OPEN_KEY, KEY_C); 473 sfm.insertNewFiles(al(sf_i2c_2)); 474 sfm.addCompactionResults(al(sf_i2c, sf_c2i), al(sf_i2d, sf_d2i)); 475 sfm.removeCompactedFiles(al(sf_i2c, sf_c2i)); 476 assertEquals(1, sfm.getLevel0Files().size()); 477 verifyGetAndScanScenario(sfm, KEY_C, KEY_C, sf_i2d, sf_i2c_2); 478 } 479 480 @Test 481 public void testEmptyResultsForStripes() throws Exception { 482 // Test that we can compact L0 into a subset of stripes. 483 StripeStoreFileManager manager = createManager(); 484 HStoreFile sf0a = createFile(); 485 HStoreFile sf0b = createFile(); 486 manager.insertNewFiles(al(sf0a)); 487 manager.insertNewFiles(al(sf0b)); 488 ArrayList<HStoreFile> compacted = 489 al(createFile(OPEN_KEY, KEY_B), createFile(KEY_B, KEY_C), createFile(KEY_C, OPEN_KEY)); 490 manager.addCompactionResults(al(sf0a), compacted); 491 manager.removeCompactedFiles(al(sf0a)); 492 // Next L0 compaction only produces file for the first and last stripe. 493 ArrayList<HStoreFile> compacted2 = al(createFile(OPEN_KEY, KEY_B), createFile(KEY_C, OPEN_KEY)); 494 manager.addCompactionResults(al(sf0b), compacted2); 495 manager.removeCompactedFiles(al(sf0b)); 496 compacted.addAll(compacted2); 497 verifyAllFiles(manager, compacted); 498 } 499 500 @Test 501 public void testPriority() throws Exception { 502 // Expected priority, file limit, stripe count, files per stripe, l0 files. 503 testPriorityScenario(5, 5, 0, 0, 0); 504 testPriorityScenario(2, 5, 0, 0, 3); 505 testPriorityScenario(4, 25, 5, 1, 0); // example case. 506 testPriorityScenario(3, 25, 5, 1, 1); // L0 files counts for all stripes. 507 testPriorityScenario(3, 25, 5, 2, 0); // file to each stripe - same as one L0 file. 508 testPriorityScenario(2, 25, 5, 4, 0); // 1 is priority user, so 2 is returned. 509 testPriorityScenario(2, 25, 5, 4, 4); // don't return higher than user unless over limit. 510 testPriorityScenario(2, 25, 5, 1, 10); // same. 511 testPriorityScenario(0, 25, 5, 4, 5); // at limit. 512 testPriorityScenario(-5, 25, 5, 6, 0); // over limit! 513 testPriorityScenario(-1, 25, 0, 0, 26); // over limit with just L0 514 } 515 516 private void testPriorityScenario(int expectedPriority, int limit, int stripes, int filesInStripe, 517 int l0Files) throws Exception { 518 final byte[][] keys = { KEY_A, KEY_B, KEY_C, KEY_D }; 519 assertTrue(stripes <= keys.length + 1); 520 Configuration conf = TEST_UTIL.getConfiguration(); 521 conf.setInt("hbase.hstore.blockingStoreFiles", limit); 522 StripeStoreFileManager sfm = createManager(al(), conf); 523 for (int i = 0; i < l0Files; ++i) { 524 sfm.insertNewFiles(al(createFile())); 525 } 526 for (int i = 0; i < filesInStripe; ++i) { 527 ArrayList<HStoreFile> stripe = new ArrayList<>(); 528 for (int j = 0; j < stripes; ++j) { 529 stripe.add( 530 createFile((j == 0) ? OPEN_KEY : keys[j - 1], (j == stripes - 1) ? OPEN_KEY : keys[j])); 531 } 532 sfm.addCompactionResults(al(), stripe); 533 } 534 assertEquals(expectedPriority, sfm.getStoreCompactionPriority()); 535 } 536 537 private void verifyInvalidCompactionScenario(StripeStoreFileManager manager, 538 ArrayList<HStoreFile> filesToCompact, ArrayList<HStoreFile> filesToInsert) throws Exception { 539 Collection<HStoreFile> allFiles = manager.getStoreFiles(); 540 assertThrows(IllegalStateException.class, 541 () -> manager.addCompactionResults(filesToCompact, filesToInsert)); 542 verifyAllFiles(manager, allFiles); // must have the same files. 543 } 544 545 private void verifyGetOrScanScenario(StripeStoreFileManager manager, byte[] start, byte[] end, 546 HStoreFile... results) throws Exception { 547 verifyGetOrScanScenario(manager, start, end, Arrays.asList(results)); 548 } 549 550 private void verifyGetOrScanScenario(StripeStoreFileManager manager, byte[] start, byte[] end, 551 Collection<HStoreFile> results) throws Exception { 552 start = start != null ? start : HConstants.EMPTY_START_ROW; 553 end = end != null ? end : HConstants.EMPTY_END_ROW; 554 Collection<HStoreFile> sfs = manager.getFilesForScan(start, true, end, false, false); 555 assertEquals(results.size(), sfs.size()); 556 for (HStoreFile result : results) { 557 assertTrue(sfs.contains(result)); 558 } 559 } 560 561 private void verifyAllFiles(StripeStoreFileManager manager, Collection<HStoreFile> results) 562 throws Exception { 563 verifyGetOrScanScenario(manager, null, null, results); 564 } 565 566 // TODO: replace with Mockito? 567 private static MockHStoreFile createFile(long size, long seqNum, byte[] startKey, byte[] endKey) 568 throws Exception { 569 FileSystem fs = TEST_UTIL.getTestFileSystem(); 570 Path testFilePath = StoreFileWriter.getUniqueFile(fs, CFDIR); 571 fs.create(testFilePath).close(); 572 StoreFileInfo storeFileInfo = StoreFileInfo 573 .createStoreFileInfoForHFile(TEST_UTIL.getConfiguration(), fs, testFilePath, true); 574 MockHStoreFile sf = 575 new MockHStoreFile(TEST_UTIL, testFilePath, size, 0, false, seqNum, storeFileInfo); 576 if (startKey != null) { 577 sf.setMetadataValue(StripeStoreFileManager.STRIPE_START_KEY, startKey); 578 } 579 if (endKey != null) { 580 sf.setMetadataValue(StripeStoreFileManager.STRIPE_END_KEY, endKey); 581 } 582 return sf; 583 } 584 585 private static MockHStoreFile createFile(long size, long seqNum) throws Exception { 586 return createFile(size, seqNum, null, null); 587 } 588 589 private static MockHStoreFile createFile(byte[] startKey, byte[] endKey) throws Exception { 590 return createFile(0, 0, startKey, endKey); 591 } 592 593 private static MockHStoreFile createFile() throws Exception { 594 return createFile(null, null); 595 } 596 597 private static StripeStoreFileManager createManager() throws Exception { 598 return createManager(new ArrayList<>()); 599 } 600 601 private static StripeStoreFileManager createManager(ArrayList<HStoreFile> sfs) throws Exception { 602 return createManager(sfs, TEST_UTIL.getConfiguration()); 603 } 604 605 private static StripeStoreFileManager createManager(ArrayList<HStoreFile> sfs, Configuration conf) 606 throws Exception { 607 StripeStoreConfig config = 608 new StripeStoreConfig(conf, Mockito.mock(StoreConfigInformation.class)); 609 StripeStoreFileManager result = 610 new StripeStoreFileManager(CellComparatorImpl.COMPARATOR, conf, config); 611 result.loadFiles(sfs); 612 return result; 613 } 614 615 private static ArrayList<HStoreFile> al(HStoreFile... sfs) { 616 return new ArrayList<>(Arrays.asList(sfs)); 617 } 618 619 private static ArrayList<HStoreFile> flattenLists(ArrayList<HStoreFile>... sfls) { 620 ArrayList<HStoreFile> result = new ArrayList<>(); 621 for (ArrayList<HStoreFile> sfl : sfls) { 622 result.addAll(sfl); 623 } 624 return result; 625 } 626}