View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.regionserver;
20  
21  import static org.apache.hadoop.hbase.regionserver.StripeStoreFileManager.OPEN_KEY;
22  import static org.junit.Assert.assertArrayEquals;
23  import static org.junit.Assert.assertEquals;
24  import static org.junit.Assert.assertFalse;
25  import static org.junit.Assert.assertNull;
26  import static org.junit.Assert.assertTrue;
27  import static org.junit.Assert.fail;
28  
29  import java.io.IOException;
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.Collection;
33  import java.util.Collections;
34  import java.util.Iterator;
35  import java.util.List;
36  
37  import org.apache.hadoop.conf.Configuration;
38  import org.apache.hadoop.fs.FileSystem;
39  import org.apache.hadoop.fs.Path;
40  import org.apache.hadoop.hbase.HBaseConfiguration;
41  import org.apache.hadoop.hbase.HBaseTestingUtility;
42  import org.apache.hadoop.hbase.HConstants;
43  import org.apache.hadoop.hbase.KeyValue;
44  import org.apache.hadoop.hbase.KeyValue.KVComparator;
45  import org.apache.hadoop.hbase.testclassification.SmallTests;
46  import org.apache.hadoop.hbase.util.Bytes;
47  import org.junit.After;
48  import org.junit.Before;
49  import org.junit.Test;
50  import org.junit.experimental.categories.Category;
51  import org.mockito.Mockito;
52  
53  
54  @Category(SmallTests.class)
55  public class TestStripeStoreFileManager {
56    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
57    private static final Path BASEDIR =
58        TEST_UTIL.getDataTestDir(TestStripeStoreFileManager.class.getSimpleName());
59    private static final Path CFDIR = HStore.getStoreHomedir(BASEDIR, "region", Bytes.toBytes("cf"));
60  
61    private static final byte[] KEY_A = Bytes.toBytes("aaa");
62    private static final byte[] KEY_B = Bytes.toBytes("bbb");
63    private static final byte[] KEY_C = Bytes.toBytes("ccc");
64    private static final byte[] KEY_D = Bytes.toBytes("ddd");
65  
66    private static final KeyValue KV_A = new KeyValue(KEY_A, 0L);
67    private static final KeyValue KV_B = new KeyValue(KEY_B, 0L);
68    private static final KeyValue KV_C = new KeyValue(KEY_C, 0L);
69    private static final KeyValue KV_D = new KeyValue(KEY_D, 0L);
70  
71    @Before
72    public void setUp() throws Exception {
73      FileSystem fs = TEST_UTIL.getTestFileSystem();
74      if (!fs.mkdirs(CFDIR)) {
75        throw new IOException("Cannot create test directory " + CFDIR);
76      }
77    }
78  
79    @After
80    public void tearDown() throws Exception {
81      FileSystem fs = TEST_UTIL.getTestFileSystem();
82      if (fs.exists(CFDIR) && !fs.delete(CFDIR, true)) {
83        throw new IOException("Cannot delete test directory " + CFDIR);
84      }
85    }
86  
87    @Test
88    public void testInsertFilesIntoL0() throws Exception {
89      StripeStoreFileManager manager = createManager();
90      MockStoreFile sf = createFile();
91      manager.insertNewFiles(al(sf));
92      assertEquals(1, manager.getStorefileCount());
93      Collection<StoreFile> filesForGet = manager.getFilesForScanOrGet(KEY_A, true, KEY_A, true);
94      assertEquals(1, filesForGet.size());
95      assertTrue(filesForGet.contains(sf));
96  
97      // Add some stripes and make sure we get this file for every stripe.
98      manager.addCompactionResults(al(), al(createFile(OPEN_KEY, KEY_B),
99          createFile(KEY_B, OPEN_KEY)));
100     assertTrue(manager.getFilesForScanOrGet(KEY_A, true, KEY_A, true).contains(sf));
101     assertTrue(manager.getFilesForScanOrGet(KEY_C, true, KEY_C, true).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(), al(createFile(OPEN_KEY, KEY_B),
110         createFile(KEY_B, OPEN_KEY)));
111     assertEquals(4, manager.getStorefileCount());
112     Collection<StoreFile> 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<StoreFile> dumpIterator(Iterator<StoreFile> iter) {
119     ArrayList<StoreFile> result = new ArrayList<StoreFile>();
120     for (; iter.hasNext(); result.add(iter.next()));
121     return result;
122   }
123 
124   @Test
125   public void testRowKeyBefore() throws Exception {
126     StripeStoreFileManager manager = createManager();
127     StoreFile l0File = createFile(), l0File2 = createFile();
128     manager.insertNewFiles(al(l0File));
129     manager.insertNewFiles(al(l0File2));
130     // Get candidate files.
131     Iterator<StoreFile> sfs = manager.getCandidateFilesForRowKeyBefore(KV_B);
132     sfs.next();
133     sfs.remove();
134     // Suppose we found a candidate in this file... make sure L0 file remaining is not removed.
135     sfs = manager.updateCandidateFilesForRowKeyBefore(sfs, KV_B, KV_A);
136     assertTrue(sfs.hasNext());
137     // Now add some stripes (remove L0 file too)
138     MockStoreFile stripe0a = createFile(0, 100, OPEN_KEY, KEY_B),
139         stripe1 = createFile(KEY_B, OPEN_KEY);
140     manager.addCompactionResults(al(l0File), al(stripe0a, stripe1));
141     manager.removeCompactedFiles(al(l0File));
142     // If we want a key <= KEY_A, we should get everything except stripe1.
143     ArrayList<StoreFile> sfsDump = dumpIterator(manager.getCandidateFilesForRowKeyBefore(KV_A));
144     assertEquals(2, sfsDump.size());
145     assertTrue(sfsDump.contains(stripe0a));
146     assertFalse(sfsDump.contains(stripe1));
147     // If we want a key <= KEY_B, we should get everything since lower bound is inclusive.
148     sfsDump = dumpIterator(manager.getCandidateFilesForRowKeyBefore(KV_B));
149     assertEquals(3, sfsDump.size());
150     assertTrue(sfsDump.contains(stripe1));
151     // For KEY_D, we should also get everything.
152     sfsDump = dumpIterator(manager.getCandidateFilesForRowKeyBefore(KV_D));
153     assertEquals(3, sfsDump.size());
154     // Suppose in the first file we found candidate with KEY_C.
155     // Then, stripe0 no longer matters and should be removed, but stripe1 should stay.
156     sfs = manager.getCandidateFilesForRowKeyBefore(KV_D);
157     sfs.next(); // Skip L0 file.
158     sfs.remove();
159     sfs = manager.updateCandidateFilesForRowKeyBefore(sfs, KV_D, KV_C);
160     assertEquals(stripe1, sfs.next());
161     assertFalse(sfs.hasNext());
162     // Add one more, later, file to stripe0, remove the last annoying L0 file.
163     // This file should be returned in preference to older L0 file; also, after we get
164     // a candidate from the first file, the old one should not be removed.
165     StoreFile stripe0b = createFile(0, 101, OPEN_KEY, KEY_B);
166     manager.addCompactionResults(al(l0File2), al(stripe0b));
167     manager.removeCompactedFiles(al(l0File2));
168     sfs = manager.getCandidateFilesForRowKeyBefore(KV_A);
169     assertEquals(stripe0b, sfs.next());
170     sfs.remove();
171     sfs = manager.updateCandidateFilesForRowKeyBefore(sfs, KV_A, KV_A);
172     assertEquals(stripe0a, sfs.next());
173   }
174 
175   @Test
176   public void testGetSplitPointEdgeCases() throws Exception {
177     StripeStoreFileManager manager = createManager();
178     // No files => no split.
179     assertNull(manager.getSplitPoint());
180 
181     // If there are no stripes, should pick midpoint from the biggest file in L0.
182     MockStoreFile sf5 = createFile(5, 0);
183     sf5.splitPoint = new byte[1];
184     manager.insertNewFiles(al(sf5));
185     manager.insertNewFiles(al(createFile(1, 0)));
186     assertArrayEquals(sf5.splitPoint, manager.getSplitPoint());
187 
188     // Same if there's one stripe but the biggest file is still in L0.
189     manager.addCompactionResults(al(), al(createFile(2, 0, OPEN_KEY, OPEN_KEY)));
190     assertArrayEquals(sf5.splitPoint, manager.getSplitPoint());
191 
192     // If the biggest file is in the stripe, should get from it.
193     MockStoreFile sf6 = createFile(6, 0, OPEN_KEY, OPEN_KEY);
194     sf6.splitPoint = new byte[1];
195     manager.addCompactionResults(al(), al(sf6));
196     assertArrayEquals(sf6.splitPoint, manager.getSplitPoint());
197   }
198 
199   @Test
200   public void testGetStripeBoundarySplits() throws Exception {
201     /* First number - split must be after this stripe; further numbers - stripes */
202     verifySplitPointScenario(5, false, 0f,     2, 1, 1, 1, 1, 1, 10);
203     verifySplitPointScenario(0, false, 0f,     6, 3, 1, 1, 2);
204     verifySplitPointScenario(2, false, 0f,     1, 1, 1, 1, 2);
205     verifySplitPointScenario(0, false, 0f,     5, 4);
206     verifySplitPointScenario(2, false, 0f,     5, 2, 5, 5, 5);
207   }
208 
209   @Test
210   public void testGetUnbalancedSplits() throws Exception {
211     /* First number - split must be inside/after this stripe; further numbers - stripes */
212     verifySplitPointScenario(0, false, 2.1f,      4, 4, 4); // 8/4 is less than 2.1f
213     verifySplitPointScenario(1, true,  1.5f,      4, 4, 4); // 8/4 > 6/6
214     verifySplitPointScenario(1, false, 1.1f,      3, 4, 1, 1, 2, 2); // 7/6 < 8/5
215     verifySplitPointScenario(1, false, 1.1f,      3, 6, 1, 1, 2, 2); // 9/6 == 9/6
216     verifySplitPointScenario(1, true,  1.1f,      3, 8, 1, 1, 2, 2); // 11/6 > 10/7
217     verifySplitPointScenario(3, false, 1.1f,      2, 2, 1, 1, 4, 3); // reverse order
218     verifySplitPointScenario(4, true,  1.1f,      2, 2, 1, 1, 8, 3); // reverse order
219     verifySplitPointScenario(0, true,  1.5f,      10, 4); // 10/4 > 9/5
220     verifySplitPointScenario(0, false, 1.4f,      6, 4);  // 6/4 == 6/4
221     verifySplitPointScenario(1, true,  1.5f,      4, 10); // reverse just in case
222     verifySplitPointScenario(0, false, 1.4f,      4, 6);  // reverse just in case
223   }
224 
225 
226   /**
227    * Verifies scenario for finding a split point.
228    * @param splitPointAfter Stripe to expect the split point at/after.
229    * @param shouldSplitStripe If true, the split point is expected in the middle of the above
230    *                          stripe; if false, should be at the end.
231    * @param splitRatioToVerify Maximum split imbalance ratio.
232    * @param sizes Stripe sizes.
233    */
234   private void verifySplitPointScenario(int splitPointAfter, boolean shouldSplitStripe,
235       float splitRatioToVerify, int... sizes) throws Exception {
236     assertTrue(sizes.length > 1);
237     ArrayList<StoreFile> sfs = new ArrayList<StoreFile>();
238     for (int sizeIx = 0; sizeIx < sizes.length; ++sizeIx) {
239       byte[] startKey = (sizeIx == 0) ? OPEN_KEY : Bytes.toBytes(sizeIx - 1);
240       byte[] endKey = (sizeIx == sizes.length - 1) ? OPEN_KEY : Bytes.toBytes(sizeIx);
241       MockStoreFile sf = createFile(sizes[sizeIx], 0, startKey, endKey);
242       sf.splitPoint = Bytes.toBytes(-sizeIx); // set split point to the negative index
243       sfs.add(sf);
244     }
245 
246     Configuration conf = HBaseConfiguration.create();
247     if (splitRatioToVerify != 0) {
248       conf.setFloat(StripeStoreConfig.MAX_REGION_SPLIT_IMBALANCE_KEY, splitRatioToVerify);
249     }
250     StripeStoreFileManager manager = createManager(al(), conf);
251     manager.addCompactionResults(al(), sfs);
252     int result = Bytes.toInt(manager.getSplitPoint());
253     // Either end key and thus positive index, or "middle" of the file and thus negative index.
254     assertEquals(splitPointAfter * (shouldSplitStripe ? -1 : 1), result);
255   }
256 
257   private static byte[] keyAfter(byte[] key) {
258     return Arrays.copyOf(key, key.length + 1);
259   }
260 
261   @Test
262   public void testGetFilesForGetAndScan() throws Exception {
263     StripeStoreFileManager manager = createManager();
264     verifyGetAndScanScenario(manager, null, null);
265     verifyGetAndScanScenario(manager, KEY_B, KEY_C);
266 
267     // Populate one L0 file.
268     MockStoreFile sf0 = createFile();
269     manager.insertNewFiles(al(sf0));
270     verifyGetAndScanScenario(manager, null, null,   sf0);
271     verifyGetAndScanScenario(manager, null, KEY_C,  sf0);
272     verifyGetAndScanScenario(manager, KEY_B, null,  sf0);
273     verifyGetAndScanScenario(manager, KEY_B, KEY_C, sf0);
274 
275     // Populate a bunch of files for stripes, keep L0.
276     MockStoreFile sfA = createFile(OPEN_KEY, KEY_A);
277     MockStoreFile sfB = createFile(KEY_A, KEY_B);
278     MockStoreFile sfC = createFile(KEY_B, KEY_C);
279     MockStoreFile sfD = createFile(KEY_C, KEY_D);
280     MockStoreFile sfE = createFile(KEY_D, OPEN_KEY);
281     manager.addCompactionResults(al(), al(sfA, sfB, sfC, sfD, sfE));
282 
283     verifyGetAndScanScenario(manager, null, null,              sf0, sfA, sfB, sfC, sfD, sfE);
284     verifyGetAndScanScenario(manager, keyAfter(KEY_A), null,   sf0, sfB, sfC, sfD, sfE);
285     verifyGetAndScanScenario(manager, null, keyAfter(KEY_C),   sf0, sfA, sfB, sfC, sfD);
286     verifyGetAndScanScenario(manager, KEY_B, null,             sf0, sfC, sfD, sfE);
287     verifyGetAndScanScenario(manager, null, KEY_C,             sf0, sfA, sfB, sfC, sfD);
288     verifyGetAndScanScenario(manager, KEY_B, keyAfter(KEY_B),  sf0, sfC);
289     verifyGetAndScanScenario(manager, keyAfter(KEY_A), KEY_B,  sf0, sfB, sfC);
290     verifyGetAndScanScenario(manager, KEY_D, KEY_D,            sf0, sfE);
291     verifyGetAndScanScenario(manager, keyAfter(KEY_B), keyAfter(KEY_C), sf0, sfC, sfD);
292   }
293 
294   private void verifyGetAndScanScenario(StripeStoreFileManager manager,
295       byte[] start, byte[] end, StoreFile... results) throws Exception {
296     verifyGetOrScanScenario(manager, true, start, end, results);
297     verifyGetOrScanScenario(manager, false, 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<StoreFile> validStripeFiles = al(createFile(OPEN_KEY, KEY_B),
307         createFile(KEY_B, KEY_C), createFile(KEY_C, OPEN_KEY),
308         createFile(KEY_C, OPEN_KEY));
309     ArrayList<StoreFile> filesToGoToL0 = al(createFile(), createFile(null, KEY_A),
310         createFile(KEY_D, null), createFile(KEY_D, KEY_A), createFile(keyAfter(KEY_A), KEY_C),
311         createFile(OPEN_KEY, KEY_D), createFile(KEY_D, keyAfter(KEY_D)));
312     ArrayList<StoreFile> allFilesToGo = flattenLists(validStripeFiles, filesToGoToL0);
313     Collections.shuffle(allFilesToGo);
314     StripeStoreFileManager manager = createManager(allFilesToGo);
315     List<StoreFile> l0Files = manager.getLevel0Files();
316     assertEquals(filesToGoToL0.size(), l0Files.size());
317     for (StoreFile sf : filesToGoToL0) {
318       assertTrue(l0Files.contains(sf));
319     }
320     verifyAllFiles(manager, allFilesToGo);
321   }
322 
323   @Test
324   public void testLoadFilesWithBadStripe() throws Exception {
325     // Current "algorithm" will see the after-B key before C key, add it as valid stripe,
326     // and then fail all other stripes. So everything would end up in L0.
327     ArrayList<StoreFile> allFilesToGo = al(createFile(OPEN_KEY, KEY_B),
328         createFile(KEY_B, KEY_C), createFile(KEY_C, OPEN_KEY),
329         createFile(KEY_B, keyAfter(KEY_B)));
330     Collections.shuffle(allFilesToGo);
331     StripeStoreFileManager manager = createManager(allFilesToGo);
332     assertEquals(allFilesToGo.size(), manager.getLevel0Files().size());
333   }
334 
335   @Test
336   public void testLoadFilesWithGaps() throws Exception {
337     // Stripes must not have gaps. If they do, everything goes to L0.
338     StripeStoreFileManager manager =
339       createManager(al(createFile(OPEN_KEY, KEY_B), createFile(KEY_C, OPEN_KEY)));
340     assertEquals(2, manager.getLevel0Files().size());
341     // Just one open stripe should be ok.
342     manager = createManager(al(createFile(OPEN_KEY, OPEN_KEY)));
343     assertEquals(0, manager.getLevel0Files().size());
344     assertEquals(1, manager.getStorefileCount());
345   }
346 
347   @Test
348   public void testLoadFilesAfterSplit() throws Exception {
349     // If stripes are good but have non-open ends, they must be treated as open ends.
350     MockStoreFile sf = createFile(KEY_B, KEY_C);
351     StripeStoreFileManager manager = createManager(al(createFile(OPEN_KEY, KEY_B), sf));
352     assertEquals(0, manager.getLevel0Files().size());
353     // Here, [B, C] is logically [B, inf), so we should be able to compact it to that only.
354     verifyInvalidCompactionScenario(manager, al(sf), al(createFile(KEY_B, KEY_C)));
355     manager.addCompactionResults(al(sf), al(createFile(KEY_B, OPEN_KEY)));
356     manager.removeCompactedFiles(al(sf));
357     // Do the same for other variants.
358     manager = createManager(al(sf, createFile(KEY_C, OPEN_KEY)));
359     verifyInvalidCompactionScenario(manager, al(sf), al(createFile(KEY_B, KEY_C)));
360     manager.addCompactionResults(al(sf), al(createFile(OPEN_KEY, KEY_C)));
361     manager.removeCompactedFiles(al(sf));
362     manager = createManager(al(sf));
363     verifyInvalidCompactionScenario(manager, al(sf), al(createFile(KEY_B, KEY_C)));
364     manager.addCompactionResults(al(sf), al(createFile(OPEN_KEY, OPEN_KEY)));
365   }
366 
367   @Test
368   public void testAddingCompactionResults() throws Exception {
369     StripeStoreFileManager manager = createManager();
370     // First, add some L0 files and "compact" one with new stripe creation.
371     StoreFile sf_L0_0a = createFile(), sf_L0_0b = createFile();
372     manager.insertNewFiles(al(sf_L0_0a, sf_L0_0b));
373 
374     // Try compacting with invalid new branches (gaps, overlaps) - no effect.
375     verifyInvalidCompactionScenario(manager, al(sf_L0_0a), al(createFile(OPEN_KEY, KEY_B)));
376     verifyInvalidCompactionScenario(manager, al(sf_L0_0a), al(createFile(OPEN_KEY, KEY_B),
377         createFile(KEY_C, OPEN_KEY)));
378     verifyInvalidCompactionScenario(manager, al(sf_L0_0a), al(createFile(OPEN_KEY, KEY_B),
379         createFile(KEY_B, OPEN_KEY), createFile(KEY_A, KEY_D)));
380     verifyInvalidCompactionScenario(manager, al(sf_L0_0a), al(createFile(OPEN_KEY, KEY_B),
381         createFile(KEY_A, KEY_B), createFile(KEY_B, OPEN_KEY)));
382 
383     StoreFile sf_i2B_0 = createFile(OPEN_KEY, KEY_B);
384     StoreFile sf_B2C_0 = createFile(KEY_B, KEY_C);
385     StoreFile sf_C2i_0 = createFile(KEY_C, OPEN_KEY);
386     manager.addCompactionResults(al(sf_L0_0a), al(sf_i2B_0, sf_B2C_0, sf_C2i_0));
387     manager.removeCompactedFiles(al(sf_L0_0a));
388     verifyAllFiles(manager, al(sf_L0_0b, sf_i2B_0, sf_B2C_0, sf_C2i_0));
389 
390     // Add another l0 file, "compact" both L0 into two stripes
391     StoreFile sf_L0_1 = createFile();
392     StoreFile sf_i2B_1 = createFile(OPEN_KEY, KEY_B);
393     StoreFile sf_B2C_1 = createFile(KEY_B, KEY_C);
394     manager.insertNewFiles(al(sf_L0_1));
395     manager.addCompactionResults(al(sf_L0_0b, sf_L0_1), al(sf_i2B_1, sf_B2C_1));
396     manager.removeCompactedFiles(al(sf_L0_0b, sf_L0_1));
397     verifyAllFiles(manager, al(sf_i2B_0, sf_B2C_0, sf_C2i_0, sf_i2B_1, sf_B2C_1));
398 
399     // Try compacting with invalid file (no metadata) - should add files to L0.
400     StoreFile sf_L0_2 = createFile(null, null);
401     manager.addCompactionResults(al(), al(sf_L0_2));
402     manager.removeCompactedFiles(al());
403     verifyAllFiles(manager, al(sf_i2B_0, sf_B2C_0, sf_C2i_0, sf_i2B_1, sf_B2C_1, sf_L0_2));
404     // Remove it...
405     manager.addCompactionResults(al(sf_L0_2), al());
406     manager.removeCompactedFiles(al(sf_L0_2));
407 
408     // Do regular compaction in the first stripe.
409     StoreFile sf_i2B_3 = createFile(OPEN_KEY, KEY_B);
410     manager.addCompactionResults(al(sf_i2B_0, sf_i2B_1), al(sf_i2B_3));
411     manager.removeCompactedFiles(al(sf_i2B_0, sf_i2B_1));
412     verifyAllFiles(manager, al(sf_B2C_0, sf_C2i_0, sf_B2C_1, sf_i2B_3));
413 
414     // Rebalance two stripes.
415     StoreFile sf_B2D_4 = createFile(KEY_B, KEY_D);
416     StoreFile sf_D2i_4 = createFile(KEY_D, OPEN_KEY);
417     manager.addCompactionResults(al(sf_B2C_0, sf_C2i_0, sf_B2C_1), al(sf_B2D_4, sf_D2i_4));
418     manager.removeCompactedFiles(al(sf_B2C_0, sf_C2i_0, sf_B2C_1));
419     verifyAllFiles(manager, al(sf_i2B_3, sf_B2D_4, sf_D2i_4));
420 
421     // Split the first stripe.
422     StoreFile sf_i2A_5 = createFile(OPEN_KEY, KEY_A);
423     StoreFile sf_A2B_5 = createFile(KEY_A, KEY_B);
424     manager.addCompactionResults(al(sf_i2B_3), al(sf_i2A_5, sf_A2B_5));
425     manager.removeCompactedFiles(al(sf_i2B_3));
426     verifyAllFiles(manager, al(sf_B2D_4, sf_D2i_4, sf_i2A_5, sf_A2B_5));
427 
428     // Split the middle stripe.
429     StoreFile sf_B2C_6 = createFile(KEY_B, KEY_C);
430     StoreFile sf_C2D_6 = createFile(KEY_C, KEY_D);
431     manager.addCompactionResults(al(sf_B2D_4), al(sf_B2C_6, sf_C2D_6));
432     manager.removeCompactedFiles(al(sf_B2D_4));
433     verifyAllFiles(manager, al(sf_D2i_4, sf_i2A_5, sf_A2B_5, sf_B2C_6, sf_C2D_6));
434 
435     // Merge two different middle stripes.
436     StoreFile sf_A2C_7 = createFile(KEY_A, KEY_C);
437     manager.addCompactionResults(al(sf_A2B_5, sf_B2C_6), al(sf_A2C_7));
438     manager.removeCompactedFiles(al(sf_A2B_5, sf_B2C_6));
439     verifyAllFiles(manager, al(sf_D2i_4, sf_i2A_5, sf_C2D_6, sf_A2C_7));
440 
441     // Merge lower half.
442     StoreFile sf_i2C_8 = createFile(OPEN_KEY, KEY_C);
443     manager.addCompactionResults(al(sf_i2A_5, sf_A2C_7), al(sf_i2C_8));
444     manager.removeCompactedFiles(al(sf_i2A_5, sf_A2C_7));
445     verifyAllFiles(manager, al(sf_D2i_4, sf_C2D_6, sf_i2C_8));
446 
447     // Merge all.
448     StoreFile sf_i2i_9 = createFile(OPEN_KEY, OPEN_KEY);
449     manager.addCompactionResults(al(sf_D2i_4, sf_C2D_6, sf_i2C_8), al(sf_i2i_9));
450     manager.removeCompactedFiles(al(sf_D2i_4, sf_C2D_6, sf_i2C_8));
451     verifyAllFiles(manager, al(sf_i2i_9));
452   }
453 
454   @Test
455   public void testCompactionAndFlushConflict() throws Exception {
456     // Add file flush into stripes
457     StripeStoreFileManager sfm = createManager();
458     assertEquals(0, sfm.getStripeCount());
459     StoreFile sf_i2c = createFile(OPEN_KEY, KEY_C), sf_c2i = createFile(KEY_C, OPEN_KEY);
460     sfm.insertNewFiles(al(sf_i2c, sf_c2i));
461     assertEquals(2, sfm.getStripeCount());
462     // Now try to add conflicting flush - should throw.
463     StoreFile sf_i2d = createFile(OPEN_KEY, KEY_D), sf_d2i = createFile(KEY_D, OPEN_KEY);
464     sfm.insertNewFiles(al(sf_i2d, sf_d2i));
465     assertEquals(2, sfm.getStripeCount());
466     assertEquals(2, sfm.getLevel0Files().size());
467     verifyGetAndScanScenario(sfm, KEY_C, KEY_C, sf_i2d, sf_d2i, sf_c2i);
468     // Remove these files.
469     sfm.addCompactionResults(al(sf_i2d, sf_d2i), al());
470     sfm.removeCompactedFiles(al(sf_i2d, sf_d2i));
471     assertEquals(0, sfm.getLevel0Files().size());
472     // Add another file to stripe; then "rebalance" stripes w/o it - the file, which was
473     // presumably flushed during compaction, should go to L0.
474     StoreFile sf_i2c_2 = createFile(OPEN_KEY, KEY_C);
475     sfm.insertNewFiles(al(sf_i2c_2));
476     sfm.addCompactionResults(al(sf_i2c, sf_c2i), al(sf_i2d, sf_d2i));
477     sfm.removeCompactedFiles(al(sf_i2c, sf_c2i));
478     assertEquals(1, sfm.getLevel0Files().size());
479     verifyGetAndScanScenario(sfm, KEY_C, KEY_C, sf_i2d, sf_i2c_2);
480   }
481 
482   @Test
483   public void testEmptyResultsForStripes() throws Exception {
484     // Test that we can compact L0 into a subset of stripes.
485     StripeStoreFileManager manager = createManager();
486     StoreFile sf0a = createFile();
487     StoreFile sf0b = createFile();
488     manager.insertNewFiles(al(sf0a));
489     manager.insertNewFiles(al(sf0b));
490     ArrayList<StoreFile> compacted = al(createFile(OPEN_KEY, KEY_B),
491         createFile(KEY_B, KEY_C), createFile(KEY_C, OPEN_KEY));
492     manager.addCompactionResults(al(sf0a), compacted);
493     manager.removeCompactedFiles(al(sf0a));
494     // Next L0 compaction only produces file for the first and last stripe.
495     ArrayList<StoreFile> compacted2 = al(createFile(OPEN_KEY, KEY_B), createFile(KEY_C, OPEN_KEY));
496     manager.addCompactionResults(al(sf0b), compacted2);
497     manager.removeCompactedFiles(al(sf0b));
498     compacted.addAll(compacted2);
499     verifyAllFiles(manager, compacted);
500   }
501 
502   @Test
503   public void testPriority() throws Exception {
504     // Expected priority, file limit, stripe count, files per stripe, l0 files.
505     testPriorityScenario(5,    5, 0, 0, 0);
506     testPriorityScenario(2,    5, 0, 0, 3);
507     testPriorityScenario(4,   25, 5, 1, 0); // example case.
508     testPriorityScenario(3,   25, 5, 1, 1); // L0 files counts for all stripes.
509     testPriorityScenario(3,   25, 5, 2, 0); // file to each stripe - same as one L0 file.
510     testPriorityScenario(2,   25, 5, 4, 0); // 1 is priority user, so 2 is returned.
511     testPriorityScenario(2,   25, 5, 4, 4); // don't return higher than user unless over limit.
512     testPriorityScenario(2,   25, 5, 1, 10); // same.
513     testPriorityScenario(0,   25, 5, 4, 5); // at limit.
514     testPriorityScenario(-5,  25, 5, 6, 0); // over limit!
515     testPriorityScenario(-1,  25, 0, 0, 26); // over limit with just L0
516   }
517 
518   private void testPriorityScenario(int expectedPriority,
519       int limit, int stripes, int filesInStripe, int l0Files) throws Exception
520   {
521     final byte[][] keys = { KEY_A, KEY_B, KEY_C, KEY_D };
522     assertTrue(stripes <= keys.length + 1);
523     Configuration conf = TEST_UTIL.getConfiguration();
524     conf.setInt("hbase.hstore.blockingStoreFiles", limit);
525     StripeStoreFileManager sfm = createManager(al(), conf);
526     for (int i = 0; i < l0Files; ++i) {
527       sfm.insertNewFiles(al(createFile()));
528     }
529     for (int i = 0; i < filesInStripe; ++i) {
530       ArrayList<StoreFile> stripe = new ArrayList<StoreFile>();
531       for (int j = 0; j < stripes; ++j) {
532         stripe.add(createFile(
533             (j == 0) ? OPEN_KEY : keys[j - 1], (j == stripes - 1) ? OPEN_KEY : keys[j]));
534       }
535       sfm.addCompactionResults(al(), stripe);
536     }
537     assertEquals(expectedPriority, sfm.getStoreCompactionPriority());
538   }
539 
540   private void verifyInvalidCompactionScenario(StripeStoreFileManager manager,
541       ArrayList<StoreFile> filesToCompact, ArrayList<StoreFile> filesToInsert) throws Exception {
542     Collection<StoreFile> allFiles = manager.getStorefiles();
543     try {
544        manager.addCompactionResults(filesToCompact, filesToInsert);
545        fail("Should have thrown");
546     } catch (IOException ex) {
547       // Ignore it.
548     }
549     verifyAllFiles(manager, allFiles); // must have the same files.
550   }
551 
552   private void verifyGetOrScanScenario(StripeStoreFileManager manager, boolean isGet,
553       byte[] start, byte[] end, StoreFile... results) throws Exception {
554     verifyGetOrScanScenario(manager, isGet, start, end, Arrays.asList(results));
555   }
556 
557   private void verifyGetOrScanScenario(StripeStoreFileManager manager, boolean isGet,
558       byte[] start, byte[] end, Collection<StoreFile> results) throws Exception {
559     start = start != null ? start : HConstants.EMPTY_START_ROW;
560     end = end != null ? end : HConstants.EMPTY_END_ROW;
561     Collection<StoreFile> sfs = manager.getFilesForScanOrGet(start, true, end, true);
562     assertEquals(results.size(), sfs.size());
563     for (StoreFile result : results) {
564       assertTrue(sfs.contains(result));
565     }
566   }
567 
568   private void verifyAllFiles(
569       StripeStoreFileManager manager, Collection<StoreFile> results) throws Exception {
570     verifyGetOrScanScenario(manager, false, null, null, results);
571   }
572 
573   // TODO: replace with Mockito?
574   private static MockStoreFile createFile(
575       long size, long seqNum, byte[] startKey, byte[] endKey) throws Exception {
576     FileSystem fs = TEST_UTIL.getTestFileSystem();
577     Path testFilePath = StoreFile.getUniqueFile(fs, CFDIR);
578     fs.create(testFilePath).close();
579     MockStoreFile sf = new MockStoreFile(TEST_UTIL, testFilePath, size, 0, false, seqNum);
580     if (startKey != null) {
581       sf.setMetadataValue(StripeStoreFileManager.STRIPE_START_KEY, startKey);
582     }
583     if (endKey != null) {
584       sf.setMetadataValue(StripeStoreFileManager.STRIPE_END_KEY, endKey);
585     }
586     return sf;
587   }
588 
589   private static MockStoreFile createFile(long size, long seqNum) throws Exception {
590     return createFile(size, seqNum, null, null);
591   }
592 
593   private static MockStoreFile createFile(byte[] startKey, byte[] endKey) throws Exception {
594     return createFile(0, 0, startKey, endKey);
595   }
596 
597   private static MockStoreFile createFile() throws Exception {
598     return createFile(null, null);
599   }
600 
601   private static StripeStoreFileManager createManager() throws Exception {
602     return createManager(new ArrayList<StoreFile>());
603   }
604 
605   private static StripeStoreFileManager createManager(ArrayList<StoreFile> sfs) throws Exception {
606     return createManager(sfs, TEST_UTIL.getConfiguration());
607   }
608 
609   private static StripeStoreFileManager createManager(
610       ArrayList<StoreFile> sfs, Configuration conf) throws Exception {
611     StripeStoreConfig config = new StripeStoreConfig(
612         conf, Mockito.mock(StoreConfigInformation.class));
613     StripeStoreFileManager result = new StripeStoreFileManager(new KVComparator(), conf, config);
614     result.loadFiles(sfs);
615     return result;
616   }
617 
618   private static ArrayList<StoreFile> al(StoreFile... sfs) {
619     return new ArrayList<StoreFile>(Arrays.asList(sfs));
620   }
621 
622   private static ArrayList<StoreFile> flattenLists(ArrayList<StoreFile>... sfls) {
623     ArrayList<StoreFile> result = new ArrayList<StoreFile>();
624     for (ArrayList<StoreFile> sfl : sfls) {
625       result.addAll(sfl);
626     }
627     return result;
628   }
629 }