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.HBaseTestingUtility;
36  import org.apache.hadoop.hbase.HColumnDescriptor;
37  import org.apache.hadoop.hbase.HRegionInfo;
38  import org.apache.hadoop.hbase.HTableDescriptor;
39  import org.apache.hadoop.hbase.Stoppable;
40  import org.apache.hadoop.hbase.TableName;
41  import org.apache.hadoop.hbase.client.Put;
42  import org.apache.hadoop.hbase.client.Scan;
43  import org.apache.hadoop.hbase.regionserver.CompactedHFilesDischarger;
44  import org.apache.hadoop.hbase.regionserver.HRegion;
45  import org.apache.hadoop.hbase.regionserver.HStore;
46  import org.apache.hadoop.hbase.regionserver.Region;
47  import org.apache.hadoop.hbase.regionserver.RegionScanner;
48  import org.apache.hadoop.hbase.regionserver.RegionServerServices;
49  import org.apache.hadoop.hbase.regionserver.Store;
50  import org.apache.hadoop.hbase.regionserver.StoreFile;
51  import org.apache.hadoop.hbase.testclassification.MediumTests;
52  import org.apache.hadoop.hbase.testclassification.RegionServerTests;
53  import org.apache.hadoop.hbase.util.Bytes;
54  import org.junit.After;
55  import org.junit.Before;
56  import org.junit.Test;
57  import org.junit.experimental.categories.Category;
58  
59  @Category({ MediumTests.class, RegionServerTests.class })
60  public class TestCompactedHFilesDischarger {
61    private final HBaseTestingUtility testUtil = new HBaseTestingUtility();
62    private Region region;
63    private final static byte[] fam = Bytes.toBytes("cf_1");
64    private final static byte[] qual1 = Bytes.toBytes("qf_1");
65    private final static byte[] val = Bytes.toBytes("val");
66    private static CountDownLatch latch = new CountDownLatch(3);
67    private static AtomicInteger counter = new AtomicInteger(0);
68    private static AtomicInteger scanCompletedCounter = new AtomicInteger(0);
69    private RegionServerServices rss;
70  
71    @Before
72    public void setUp() throws Exception {
73      TableName tableName = TableName.valueOf(getClass().getSimpleName());
74      HTableDescriptor htd = new HTableDescriptor(tableName);
75      htd.addFamily(new HColumnDescriptor(fam));
76      HRegionInfo info = new HRegionInfo(tableName, null, null, false);
77      Path path = testUtil.getDataTestDir(getClass().getSimpleName());
78      region = HBaseTestingUtility.createRegionAndWAL(info, path, testUtil.getConfiguration(), htd);
79      rss = mock(RegionServerServices.class);
80      List<Region> regions = new ArrayList<Region>();
81      regions.add(region);
82      when(rss.getOnlineRegions()).thenReturn(regions);
83    }
84  
85    @After
86    public void tearDown() throws IOException {
87      counter.set(0);
88      scanCompletedCounter.set(0);
89      latch = new CountDownLatch(3);
90      HBaseTestingUtility.closeRegionAndWAL(region);
91      testUtil.cleanupTestDir();
92    }
93  
94    @Test
95    public void testCompactedHFilesCleaner() throws Exception {
96      // Create the cleaner object
97      CompactedHFilesDischarger cleaner =
98          new CompactedHFilesDischarger(1000, (Stoppable) null, rss, false);
99      // Add some data to the region and do some flushes
100     for (int i = 1; i < 10; i++) {
101       Put p = new Put(Bytes.toBytes("row" + i));
102       p.addColumn(fam, qual1, val);
103       region.put(p);
104     }
105     // flush them
106     region.flush(true);
107     for (int i = 11; i < 20; i++) {
108       Put p = new Put(Bytes.toBytes("row" + i));
109       p.addColumn(fam, qual1, val);
110       region.put(p);
111     }
112     // flush them
113     region.flush(true);
114     for (int i = 21; i < 30; i++) {
115       Put p = new Put(Bytes.toBytes("row" + i));
116       p.addColumn(fam, qual1, val);
117       region.put(p);
118     }
119     // flush them
120     region.flush(true);
121 
122     Store store = region.getStore(fam);
123     assertEquals(3, store.getStorefilesCount());
124 
125     Collection<StoreFile> storefiles = store.getStorefiles();
126     Collection<StoreFile> compactedfiles =
127         ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles();
128     // None of the files should be in compacted state.
129     for (StoreFile file : storefiles) {
130       assertFalse(file.isCompactedAway());
131     }
132     // Try to run the cleaner without compaction. there should not be any change
133     cleaner.chore();
134     storefiles = store.getStorefiles();
135     // None of the files should be in compacted state.
136     for (StoreFile file : storefiles) {
137       assertFalse(file.isCompactedAway());
138     }
139     // now do some compaction
140     region.compact(true);
141     // Still the flushed files should be present until the cleaner runs. But the state of it should
142     // be in COMPACTED state
143     assertEquals(1, store.getStorefilesCount());
144     assertEquals(3,
145       ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles().size());
146 
147     // Run the cleaner
148     cleaner.chore();
149     assertEquals(1, store.getStorefilesCount());
150     storefiles = store.getStorefiles();
151     for (StoreFile file : storefiles) {
152       // Should not be in compacted state
153       assertFalse(file.isCompactedAway());
154     }
155     compactedfiles = ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles();
156     assertTrue(compactedfiles.size() == 0);
157     
158   }
159 
160   @Test
161   public void testCleanerWithParallelScannersAfterCompaction() throws Exception {
162     // Create the cleaner object
163     CompactedHFilesDischarger cleaner =
164         new CompactedHFilesDischarger(1000, (Stoppable) null, rss, false);
165     // Add some data to the region and do some flushes
166     for (int i = 1; i < 10; i++) {
167       Put p = new Put(Bytes.toBytes("row" + i));
168       p.addColumn(fam, qual1, val);
169       region.put(p);
170     }
171     // flush them
172     region.flush(true);
173     for (int i = 11; i < 20; i++) {
174       Put p = new Put(Bytes.toBytes("row" + i));
175       p.addColumn(fam, qual1, val);
176       region.put(p);
177     }
178     // flush them
179     region.flush(true);
180     for (int i = 21; i < 30; i++) {
181       Put p = new Put(Bytes.toBytes("row" + i));
182       p.addColumn(fam, qual1, val);
183       region.put(p);
184     }
185     // flush them
186     region.flush(true);
187 
188     Store store = region.getStore(fam);
189     assertEquals(3, store.getStorefilesCount());
190 
191     Collection<StoreFile> storefiles = store.getStorefiles();
192     Collection<StoreFile> compactedfiles =
193         ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles();
194     // None of the files should be in compacted state.
195     for (StoreFile file : storefiles) {
196       assertFalse(file.isCompactedAway());
197     }
198     // Do compaction
199     region.compact(true);
200     startScannerThreads();
201 
202     storefiles = store.getStorefiles();
203     int usedReaderCount = 0;
204     int unusedReaderCount = 0;
205     for (StoreFile file : storefiles) {
206       if (file.getRefCount() == 3) {
207         usedReaderCount++;
208       }
209     }
210     compactedfiles = ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles();
211     for(StoreFile file : compactedfiles) {
212       assertEquals("Refcount should be 3", 0, file.getRefCount());
213       unusedReaderCount++;
214     }
215     // Though there are files we are not using them for reads
216     assertEquals("unused reader count should be 3", 3, unusedReaderCount);
217     assertEquals("used reader count should be 1", 1, usedReaderCount);
218     // now run the cleaner
219     cleaner.chore();
220     countDown();
221     assertEquals(1, store.getStorefilesCount());
222     storefiles = store.getStorefiles();
223     for (StoreFile file : storefiles) {
224       // Should not be in compacted state
225       assertFalse(file.isCompactedAway());
226     }
227     compactedfiles = ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles();
228     assertTrue(compactedfiles.size() == 0);
229   }
230 
231   @Test
232   public void testCleanerWithParallelScanners() throws Exception {
233     // Create the cleaner object
234     CompactedHFilesDischarger cleaner =
235         new CompactedHFilesDischarger(1000, (Stoppable) null, rss, false);
236     // Add some data to the region and do some flushes
237     for (int i = 1; i < 10; i++) {
238       Put p = new Put(Bytes.toBytes("row" + i));
239       p.addColumn(fam, qual1, val);
240       region.put(p);
241     }
242     // flush them
243     region.flush(true);
244     for (int i = 11; i < 20; i++) {
245       Put p = new Put(Bytes.toBytes("row" + i));
246       p.addColumn(fam, qual1, val);
247       region.put(p);
248     }
249     // flush them
250     region.flush(true);
251     for (int i = 21; i < 30; i++) {
252       Put p = new Put(Bytes.toBytes("row" + i));
253       p.addColumn(fam, qual1, val);
254       region.put(p);
255     }
256     // flush them
257     region.flush(true);
258 
259     Store store = region.getStore(fam);
260     assertEquals(3, store.getStorefilesCount());
261 
262     Collection<StoreFile> storefiles = store.getStorefiles();
263     Collection<StoreFile> compactedfiles =
264         ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles();
265     // None of the files should be in compacted state.
266     for (StoreFile file : storefiles) {
267       assertFalse(file.isCompactedAway());
268     }
269     startScannerThreads();
270     // Do compaction
271     region.compact(true);
272 
273     storefiles = store.getStorefiles();
274     int usedReaderCount = 0;
275     int unusedReaderCount = 0;
276     for (StoreFile file : storefiles) {
277       if (file.getRefCount() == 0) {
278         unusedReaderCount++;
279       }
280     }
281     compactedfiles =
282         ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles();
283     for(StoreFile file : compactedfiles) {
284       assertEquals("Refcount should be 3", 3, file.getRefCount());
285       usedReaderCount++;
286     }
287     // The newly compacted file will not be used by any scanner
288     assertEquals("unused reader count should be 1", 1, unusedReaderCount);
289     assertEquals("used reader count should be 3", 3, usedReaderCount);
290     // now run the cleaner
291     cleaner.chore();
292     countDown();
293     // No change in the number of store files as none of the compacted files could be cleaned up
294     assertEquals(1, store.getStorefilesCount());
295     assertEquals(3,
296       ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles().size());
297     while (scanCompletedCounter.get() != 3) {
298       Thread.sleep(100);
299     }
300     // reset
301     latch = new CountDownLatch(3);
302     scanCompletedCounter.set(0);
303     counter.set(0);
304     // Try creating a new scanner and it should use only the new file created after compaction
305     startScannerThreads();
306     storefiles = store.getStorefiles();
307     usedReaderCount = 0;
308     unusedReaderCount = 0;
309     for (StoreFile file : storefiles) {
310       if (file.getRefCount() == 3) {
311         usedReaderCount++;
312       }
313     }
314     compactedfiles = ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles();
315     for(StoreFile file : compactedfiles) {
316       assertEquals("Refcount should be 0", 0, file.getRefCount());
317       unusedReaderCount++;
318     }
319     // Though there are files we are not using them for reads
320     assertEquals("unused reader count should be 3", 3, unusedReaderCount);
321     assertEquals("used reader count should be 1", 1, usedReaderCount);
322     countDown();
323     while (scanCompletedCounter.get() != 3) {
324       Thread.sleep(100);
325     }
326     // Run the cleaner again
327     cleaner.chore();
328     // Now the cleaner should be able to clear it up because there are no active readers
329     assertEquals(1, store.getStorefilesCount());
330     storefiles = store.getStorefiles();
331     for (StoreFile file : storefiles) {
332       // Should not be in compacted state
333       assertFalse(file.isCompactedAway());
334     }
335     compactedfiles = ((HStore) store).getStoreEngine().getStoreFileManager().getCompactedfiles();
336     assertTrue(compactedfiles.size() == 0);
337   }
338 
339   protected void countDown() {
340     // count down 3 times
341     latch.countDown();
342     latch.countDown();
343     latch.countDown();
344   }
345 
346   protected void startScannerThreads() throws InterruptedException {
347     // Start parallel scan threads
348     ScanThread[] scanThreads = new ScanThread[3];
349     for (int i = 0; i < 3; i++) {
350       scanThreads[i] = new ScanThread((HRegion) region);
351     }
352     for (ScanThread thread : scanThreads) {
353       thread.start();
354     }
355     while (counter.get() != 3) {
356       Thread.sleep(100);
357     }
358   }
359 
360   private static class ScanThread extends Thread {
361     private final HRegion region;
362 
363     public ScanThread(HRegion region) {
364       this.region = region;
365     }
366 
367     @Override
368     public void run() {
369       try {
370         initiateScan(region);
371       } catch (IOException e) {
372         // do nothing
373       }
374     }
375 
376     private void initiateScan(HRegion region) throws IOException {
377       Scan scan = new Scan();
378       scan.setCaching(1);
379       RegionScanner resScanner = null;
380       try {
381         resScanner = region.getScanner(scan);
382         List<Cell> results = new ArrayList<Cell>();
383         boolean next = resScanner.next(results);
384         try {
385           counter.incrementAndGet();
386           latch.await();
387         } catch (InterruptedException e) {
388         }
389         while (!next) {
390           resScanner.next(results);
391         }
392       } finally {
393         scanCompletedCounter.incrementAndGet();
394         resScanner.close();
395       }
396     }
397   }
398 }