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