View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.regionserver.compactions;
19  
20  import static org.junit.Assert.assertEquals;
21  import static org.junit.Assert.assertFalse;
22  import static org.junit.Assert.assertTrue;
23  import static org.mockito.Mockito.mock;
24  import static org.mockito.Mockito.when;
25  
26  import java.io.IOException;
27  import java.util.ArrayList;
28  import java.util.Collection;
29  import java.util.List;
30  import java.util.concurrent.CountDownLatch;
31  import java.util.concurrent.atomic.AtomicInteger;
32  
33  import org.apache.hadoop.fs.Path;
34  import org.apache.hadoop.hbase.Cell;
35  import org.apache.hadoop.hbase.CellUtil;
36  import org.apache.hadoop.hbase.HBaseTestingUtility;
37  import org.apache.hadoop.hbase.HColumnDescriptor;
38  import org.apache.hadoop.hbase.HRegionInfo;
39  import org.apache.hadoop.hbase.HTableDescriptor;
40  import org.apache.hadoop.hbase.Stoppable;
41  import org.apache.hadoop.hbase.TableName;
42  import org.apache.hadoop.hbase.client.Put;
43  import org.apache.hadoop.hbase.client.Scan;
44  import org.apache.hadoop.hbase.regionserver.CompactedHFilesDischarger;
45  import org.apache.hadoop.hbase.regionserver.HRegion;
46  import org.apache.hadoop.hbase.regionserver.HStore;
47  import org.apache.hadoop.hbase.regionserver.Region;
48  import org.apache.hadoop.hbase.regionserver.RegionScanner;
49  import org.apache.hadoop.hbase.regionserver.RegionServerServices;
50  import org.apache.hadoop.hbase.regionserver.Store;
51  import org.apache.hadoop.hbase.regionserver.StoreFile;
52  import org.apache.hadoop.hbase.testclassification.MediumTests;
53  import org.apache.hadoop.hbase.testclassification.RegionServerTests;
54  import org.apache.hadoop.hbase.util.Bytes;
55  import org.junit.After;
56  import org.junit.Before;
57  import org.junit.Test;
58  import org.junit.experimental.categories.Category;
59  
60  @Category({ MediumTests.class, RegionServerTests.class })
61  public class TestCompactedHFilesDischarger {
62    private final HBaseTestingUtility testUtil = new HBaseTestingUtility();
63    private Region region;
64    private final static byte[] fam = Bytes.toBytes("cf_1");
65    private final static byte[] qual1 = Bytes.toBytes("qf_1");
66    private final static byte[] val = Bytes.toBytes("val");
67    private static CountDownLatch latch = new CountDownLatch(3);
68    private static AtomicInteger counter = new AtomicInteger(0);
69    private static AtomicInteger scanCompletedCounter = new AtomicInteger(0);
70    private RegionServerServices rss;
71  
72    @Before
73    public void setUp() throws Exception {
74      TableName tableName = TableName.valueOf(getClass().getSimpleName());
75      HTableDescriptor htd = new HTableDescriptor(tableName);
76      htd.addFamily(new HColumnDescriptor(fam));
77      HRegionInfo info = new HRegionInfo(tableName, null, null, false);
78      Path path = testUtil.getDataTestDir(getClass().getSimpleName());
79      region = HBaseTestingUtility.createRegionAndWAL(info, path, path, testUtil.getConfiguration(), htd);
80      rss = mock(RegionServerServices.class);
81      List<Region> regions = new ArrayList<Region>();
82      regions.add(region);
83      when(rss.getOnlineRegions()).thenReturn(regions);
84    }
85  
86    @After
87    public void tearDown() throws IOException {
88      counter.set(0);
89      scanCompletedCounter.set(0);
90      latch = new CountDownLatch(3);
91      HBaseTestingUtility.closeRegionAndWAL(region);
92      testUtil.cleanupTestDir();
93    }
94  
95    @Test
96    public void testCompactedHFilesCleaner() throws Exception {
97      // Create the cleaner object
98      CompactedHFilesDischarger cleaner =
99          new CompactedHFilesDischarger(1000, (Stoppable) null, rss, false);
100     // Add some data to the region and do some flushes
101     for (int i = 1; i < 10; i++) {
102       Put p = new Put(Bytes.toBytes("row" + i));
103       p.addColumn(fam, qual1, val);
104       region.put(p);
105     }
106     // flush them
107     region.flush(true);
108     for (int i = 11; i < 20; i++) {
109       Put p = new Put(Bytes.toBytes("row" + i));
110       p.addColumn(fam, qual1, val);
111       region.put(p);
112     }
113     // flush them
114     region.flush(true);
115     for (int i = 21; i < 30; i++) {
116       Put p = new Put(Bytes.toBytes("row" + i));
117       p.addColumn(fam, qual1, val);
118       region.put(p);
119     }
120     // flush them
121     region.flush(true);
122 
123     Store store = region.getStore(fam);
124     assertEquals(3, store.getStorefilesCount());
125 
126     Collection<StoreFile> storefiles = store.getStorefiles();
127     Collection<StoreFile> compactedfiles =
128         ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles();
129     // None of the files should be in compacted state.
130     for (StoreFile file : storefiles) {
131       assertFalse(file.isCompactedAway());
132     }
133     // Try to run the cleaner without compaction. there should not be any change
134     cleaner.chore();
135     storefiles = store.getStorefiles();
136     // None of the files should be in compacted state.
137     for (StoreFile file : storefiles) {
138       assertFalse(file.isCompactedAway());
139     }
140     // now do some compaction
141     region.compact(true);
142     // Still the flushed files should be present until the cleaner runs. But the state of it should
143     // be in COMPACTED state
144     assertEquals(1, store.getStorefilesCount());
145     assertEquals(3,
146       ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles().size());
147 
148     // Run the cleaner
149     cleaner.chore();
150     assertEquals(1, store.getStorefilesCount());
151     storefiles = store.getStorefiles();
152     for (StoreFile file : storefiles) {
153       // Should not be in compacted state
154       assertFalse(file.isCompactedAway());
155     }
156     compactedfiles = ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles();
157     assertTrue(compactedfiles.size() == 0);
158     
159   }
160 
161   @Test
162   public void testCleanerWithParallelScannersAfterCompaction() throws Exception {
163     // Create the cleaner object
164     CompactedHFilesDischarger cleaner =
165         new CompactedHFilesDischarger(1000, (Stoppable) null, rss, false);
166     // Add some data to the region and do some flushes
167     for (int i = 1; i < 10; i++) {
168       Put p = new Put(Bytes.toBytes("row" + i));
169       p.addColumn(fam, qual1, val);
170       region.put(p);
171     }
172     // flush them
173     region.flush(true);
174     for (int i = 11; i < 20; i++) {
175       Put p = new Put(Bytes.toBytes("row" + i));
176       p.addColumn(fam, qual1, val);
177       region.put(p);
178     }
179     // flush them
180     region.flush(true);
181     for (int i = 21; i < 30; i++) {
182       Put p = new Put(Bytes.toBytes("row" + i));
183       p.addColumn(fam, qual1, val);
184       region.put(p);
185     }
186     // flush them
187     region.flush(true);
188 
189     Store store = region.getStore(fam);
190     assertEquals(3, store.getStorefilesCount());
191 
192     Collection<StoreFile> storefiles = store.getStorefiles();
193     Collection<StoreFile> compactedfiles =
194         ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles();
195     // None of the files should be in compacted state.
196     for (StoreFile file : storefiles) {
197       assertFalse(file.isCompactedAway());
198     }
199     // Do compaction
200     region.compact(true);
201     startScannerThreads();
202 
203     storefiles = store.getStorefiles();
204     int usedReaderCount = 0;
205     int unusedReaderCount = 0;
206     for (StoreFile file : storefiles) {
207       if (file.getRefCount() == 3) {
208         usedReaderCount++;
209       }
210     }
211     compactedfiles = ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles();
212     for(StoreFile file : compactedfiles) {
213       assertEquals("Refcount should be 3", 0, file.getRefCount());
214       unusedReaderCount++;
215     }
216     // Though there are files we are not using them for reads
217     assertEquals("unused reader count should be 3", 3, unusedReaderCount);
218     assertEquals("used reader count should be 1", 1, usedReaderCount);
219     // now run the cleaner
220     cleaner.chore();
221     countDown();
222     assertEquals(1, store.getStorefilesCount());
223     storefiles = store.getStorefiles();
224     for (StoreFile file : storefiles) {
225       // Should not be in compacted state
226       assertFalse(file.isCompactedAway());
227     }
228     compactedfiles = ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles();
229     assertTrue(compactedfiles.size() == 0);
230   }
231 
232   @Test
233   public void testCleanerWithParallelScanners() throws Exception {
234     // Create the cleaner object
235     CompactedHFilesDischarger cleaner =
236         new CompactedHFilesDischarger(1000, (Stoppable) null, rss, false);
237     // Add some data to the region and do some flushes
238     for (int i = 1; i < 10; i++) {
239       Put p = new Put(Bytes.toBytes("row" + i));
240       p.addColumn(fam, qual1, val);
241       region.put(p);
242     }
243     // flush them
244     region.flush(true);
245     for (int i = 11; i < 20; i++) {
246       Put p = new Put(Bytes.toBytes("row" + i));
247       p.addColumn(fam, qual1, val);
248       region.put(p);
249     }
250     // flush them
251     region.flush(true);
252     for (int i = 21; i < 30; i++) {
253       Put p = new Put(Bytes.toBytes("row" + i));
254       p.addColumn(fam, qual1, val);
255       region.put(p);
256     }
257     // flush them
258     region.flush(true);
259 
260     Store store = region.getStore(fam);
261     assertEquals(3, store.getStorefilesCount());
262 
263     Collection<StoreFile> storefiles = store.getStorefiles();
264     Collection<StoreFile> compactedfiles =
265         ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles();
266     // None of the files should be in compacted state.
267     for (StoreFile file : storefiles) {
268       assertFalse(file.isCompactedAway());
269     }
270     startScannerThreads();
271     // Do compaction
272     region.compact(true);
273 
274     storefiles = store.getStorefiles();
275     int usedReaderCount = 0;
276     int unusedReaderCount = 0;
277     for (StoreFile file : storefiles) {
278       if (file.getRefCount() == 0) {
279         unusedReaderCount++;
280       }
281     }
282     compactedfiles =
283         ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles();
284     for(StoreFile file : compactedfiles) {
285       assertEquals("Refcount should be 3", 3, file.getRefCount());
286       usedReaderCount++;
287     }
288     // The newly compacted file will not be used by any scanner
289     assertEquals("unused reader count should be 1", 1, unusedReaderCount);
290     assertEquals("used reader count should be 3", 3, usedReaderCount);
291     // now run the cleaner
292     cleaner.chore();
293     countDown();
294     // No change in the number of store files as none of the compacted files could be cleaned up
295     assertEquals(1, store.getStorefilesCount());
296     assertEquals(3,
297       ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles().size());
298     while (scanCompletedCounter.get() != 3) {
299       Thread.sleep(100);
300     }
301     // reset
302     latch = new CountDownLatch(3);
303     scanCompletedCounter.set(0);
304     counter.set(0);
305     // Try creating a new scanner and it should use only the new file created after compaction
306     startScannerThreads();
307     storefiles = store.getStorefiles();
308     usedReaderCount = 0;
309     unusedReaderCount = 0;
310     for (StoreFile file : storefiles) {
311       if (file.getRefCount() == 3) {
312         usedReaderCount++;
313       }
314     }
315     compactedfiles = ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles();
316     for(StoreFile file : compactedfiles) {
317       assertEquals("Refcount should be 0", 0, file.getRefCount());
318       unusedReaderCount++;
319     }
320     // Though there are files we are not using them for reads
321     assertEquals("unused reader count should be 3", 3, unusedReaderCount);
322     assertEquals("used reader count should be 1", 1, usedReaderCount);
323     countDown();
324     while (scanCompletedCounter.get() != 3) {
325       Thread.sleep(100);
326     }
327     // Run the cleaner again
328     cleaner.chore();
329     // Now the cleaner should be able to clear it up because there are no active readers
330     assertEquals(1, store.getStorefilesCount());
331     storefiles = store.getStorefiles();
332     for (StoreFile file : storefiles) {
333       // Should not be in compacted state
334       assertFalse(file.isCompactedAway());
335     }
336     compactedfiles = ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles();
337     assertTrue(compactedfiles.size() == 0);
338   }
339 
340   @Test
341   public void testStoreFileMissing() throws Exception {
342     // Write 3 records and create 3 store files.
343     write("row1");
344     region.flush(true);
345     write("row2");
346     region.flush(true);
347     write("row3");
348     region.flush(true);
349 
350     Scan scan = new Scan();
351     scan.setCaching(1);
352     RegionScanner scanner = region.getScanner(scan);
353     List<Cell> res = new ArrayList<Cell>();
354     // Read first item
355     scanner.next(res);
356     assertEquals("row1", Bytes.toString(CellUtil.cloneRow(res.get(0))));
357     res.clear();
358     // Create a new file in between scan nexts
359     write("row4");
360     region.flush(true);
361 
362     // Compact the table
363     region.compact(true);
364 
365     // Create the cleaner object
366     CompactedHFilesDischarger cleaner =
367         new CompactedHFilesDischarger(1000, (Stoppable) null, rss, false);
368     cleaner.chore();
369     // This issues scan next
370     scanner.next(res);
371     assertEquals("row2", Bytes.toString(CellUtil.cloneRow(res.get(0))));
372 
373     scanner.close();
374   }
375 
376   private void write(String row1) throws IOException {
377     byte[] row = Bytes.toBytes(row1);
378     Put put = new Put(row);
379     put.addColumn(fam, qual1, row);
380     region.put(put);
381   }
382 
383   protected void countDown() {
384     // count down 3 times
385     latch.countDown();
386     latch.countDown();
387     latch.countDown();
388   }
389 
390   protected void startScannerThreads() throws InterruptedException {
391     // Start parallel scan threads
392     ScanThread[] scanThreads = new ScanThread[3];
393     for (int i = 0; i < 3; i++) {
394       scanThreads[i] = new ScanThread((HRegion) region);
395     }
396     for (ScanThread thread : scanThreads) {
397       thread.start();
398     }
399     while (counter.get() != 3) {
400       Thread.sleep(100);
401     }
402   }
403 
404   private static class ScanThread extends Thread {
405     private final HRegion region;
406 
407     public ScanThread(HRegion region) {
408       this.region = region;
409     }
410 
411     @Override
412     public void run() {
413       try {
414         initiateScan(region);
415       } catch (IOException e) {
416         e.printStackTrace();
417       }
418     }
419 
420     private void initiateScan(HRegion region) throws IOException {
421       Scan scan = new Scan();
422       scan.setCaching(1);
423       RegionScanner resScanner = null;
424       try {
425         resScanner = region.getScanner(scan);
426         List<Cell> results = new ArrayList<Cell>();
427         boolean next = resScanner.next(results);
428         try {
429           counter.incrementAndGet();
430           latch.await();
431         } catch (InterruptedException e) {
432         }
433         while (next) {
434           next = resScanner.next(results);
435         }
436       } finally {
437         scanCompletedCounter.incrementAndGet();
438         if (resScanner != null) {
439           resScanner.close();
440         }
441       }
442     }
443   }
444 }