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}