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.mob.filecompactions;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.Date;
26  import java.util.List;
27  import java.util.Random;
28  import java.util.UUID;
29  import java.util.concurrent.ExecutorService;
30  import java.util.concurrent.RejectedExecutionException;
31  import java.util.concurrent.RejectedExecutionHandler;
32  import java.util.concurrent.SynchronousQueue;
33  import java.util.concurrent.ThreadPoolExecutor;
34  import java.util.concurrent.TimeUnit;
35  
36  import org.apache.hadoop.conf.Configuration;
37  import org.apache.hadoop.fs.FileStatus;
38  import org.apache.hadoop.fs.FileSystem;
39  import org.apache.hadoop.fs.Path;
40  import org.apache.hadoop.hbase.Cell;
41  import org.apache.hadoop.hbase.HBaseTestingUtility;
42  import org.apache.hadoop.hbase.HColumnDescriptor;
43  import org.apache.hadoop.hbase.HConstants;
44  import org.apache.hadoop.hbase.KeyValue;
45  import org.apache.hadoop.hbase.KeyValue.Type;
46  import org.apache.hadoop.hbase.testclassification.LargeTests;
47  import org.apache.hadoop.hbase.TableName;
48  import org.apache.hadoop.hbase.client.Scan;
49  import org.apache.hadoop.hbase.io.hfile.CacheConfig;
50  import org.apache.hadoop.hbase.io.hfile.HFileContext;
51  import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder;
52  import org.apache.hadoop.hbase.mob.MobConstants;
53  import org.apache.hadoop.hbase.mob.MobFileName;
54  import org.apache.hadoop.hbase.mob.MobUtils;
55  import org.apache.hadoop.hbase.mob.filecompactions.MobFileCompactionRequest.CompactionType;
56  import org.apache.hadoop.hbase.mob.filecompactions.PartitionedMobFileCompactionRequest.CompactionPartition;
57  import org.apache.hadoop.hbase.regionserver.BloomType;
58  import org.apache.hadoop.hbase.regionserver.HStore;
59  import org.apache.hadoop.hbase.regionserver.ScanInfo;
60  import org.apache.hadoop.hbase.regionserver.ScanType;
61  import org.apache.hadoop.hbase.regionserver.StoreFile;
62  import org.apache.hadoop.hbase.regionserver.StoreFileScanner;
63  import org.apache.hadoop.hbase.regionserver.StoreScanner;
64  import org.apache.hadoop.hbase.util.Bytes;
65  import org.apache.hadoop.hbase.util.FSUtils;
66  import org.apache.hadoop.hbase.util.Threads;
67  import org.junit.AfterClass;
68  import org.junit.Assert;
69  import org.junit.BeforeClass;
70  import org.junit.Test;
71  import org.junit.experimental.categories.Category;
72  
73  @Category(LargeTests.class)
74  public class TestPartitionedMobFileCompactor {
75    private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
76    private final static String family = "family";
77    private final static String qf = "qf";
78    private HColumnDescriptor hcd = new HColumnDescriptor(family);
79    private Configuration conf = TEST_UTIL.getConfiguration();
80    private CacheConfig cacheConf = new CacheConfig(conf);
81    private FileSystem fs;
82    private List<FileStatus> mobFiles = new ArrayList<>();
83    private List<FileStatus> delFiles = new ArrayList<>();
84    private List<FileStatus> allFiles = new ArrayList<>();
85    private Path basePath;
86    private String mobSuffix;
87    private String delSuffix;
88    private static ExecutorService pool;
89  
90    @BeforeClass
91    public static void setUpBeforeClass() throws Exception {
92      TEST_UTIL.getConfiguration().setInt("hbase.master.info.port", 0);
93      TEST_UTIL.getConfiguration().setBoolean("hbase.regionserver.info.port.auto", true);
94      TEST_UTIL.getConfiguration().setInt("hfile.format.version", 3);
95      TEST_UTIL.startMiniCluster(1);
96      pool = createThreadPool(TEST_UTIL.getConfiguration());
97    }
98  
99    @AfterClass
100   public static void tearDownAfterClass() throws Exception {
101     pool.shutdown();
102     TEST_UTIL.shutdownMiniCluster();
103   }
104 
105   private void init(String tableName) throws Exception {
106     fs = FileSystem.get(conf);
107     Path testDir = FSUtils.getRootDir(conf);
108     Path mobTestDir = new Path(testDir, MobConstants.MOB_DIR_NAME);
109     basePath = new Path(new Path(mobTestDir, tableName), family);
110     mobSuffix = UUID.randomUUID().toString().replaceAll("-", "");
111     delSuffix = UUID.randomUUID().toString().replaceAll("-", "") + "_del";
112   }
113 
114   @Test
115   public void testCompactionSelectWithAllFiles() throws Exception {
116     resetConf();
117     String tableName = "testCompactionSelectWithAllFiles";
118     init(tableName);
119     int count = 10;
120     // create 10 mob files.
121     createStoreFiles(basePath, family, qf, count, Type.Put);
122     // create 10 del files
123     createStoreFiles(basePath, family, qf, count, Type.Delete);
124     listFiles();
125     long mergeSize = MobConstants.DEFAULT_MOB_FILE_COMPACTION_MERGEABLE_THRESHOLD;
126     List<String> expectedStartKeys = new ArrayList<>();
127     for(FileStatus file : mobFiles) {
128       if(file.getLen() < mergeSize) {
129         String fileName = file.getPath().getName();
130         String startKey = fileName.substring(0, 32);
131         expectedStartKeys.add(startKey);
132       }
133     }
134     testSelectFiles(tableName, CompactionType.ALL_FILES, false, expectedStartKeys);
135   }
136 
137   @Test
138   public void testCompactionSelectWithPartFiles() throws Exception {
139     resetConf();
140     String tableName = "testCompactionSelectWithPartFiles";
141     init(tableName);
142     int count = 10;
143     // create 10 mob files.
144     createStoreFiles(basePath, family, qf, count, Type.Put);
145     // create 10 del files
146     createStoreFiles(basePath, family, qf, count, Type.Delete);
147     listFiles();
148     long mergeSize = 4000;
149     List<String> expectedStartKeys = new ArrayList<>();
150     for(FileStatus file : mobFiles) {
151       if(file.getLen() < 4000) {
152         String fileName = file.getPath().getName();
153         String startKey = fileName.substring(0, 32);
154         expectedStartKeys.add(startKey);
155       }
156     }
157     // set the mob file compaction mergeable threshold
158     conf.setLong(MobConstants.MOB_FILE_COMPACTION_MERGEABLE_THRESHOLD, mergeSize);
159     testSelectFiles(tableName, CompactionType.PART_FILES, false, expectedStartKeys);
160   }
161 
162   @Test
163   public void testCompactionSelectWithForceAllFiles() throws Exception {
164     resetConf();
165     String tableName = "testCompactionSelectWithForceAllFiles";
166     init(tableName);
167     int count = 10;
168     // create 10 mob files.
169     createStoreFiles(basePath, family, qf, count, Type.Put);
170     // create 10 del files
171     createStoreFiles(basePath, family, qf, count, Type.Delete);
172     listFiles();
173     long mergeSize = 4000;
174     List<String> expectedStartKeys = new ArrayList<>();
175     for(FileStatus file : mobFiles) {
176       String fileName = file.getPath().getName();
177       String startKey = fileName.substring(0, 32);
178       expectedStartKeys.add(startKey);
179     }
180     // set the mob file compaction mergeable threshold
181     conf.setLong(MobConstants.MOB_FILE_COMPACTION_MERGEABLE_THRESHOLD, mergeSize);
182     testSelectFiles(tableName, CompactionType.ALL_FILES, true, expectedStartKeys);
183   }
184 
185   @Test
186   public void testCompactDelFilesWithDefaultBatchSize() throws Exception {
187     resetConf();
188     String tableName = "testCompactDelFilesWithDefaultBatchSize";
189     init(tableName);
190     // create 20 mob files.
191     createStoreFiles(basePath, family, qf, 20, Type.Put);
192     // create 13 del files
193     createStoreFiles(basePath, family, qf, 13, Type.Delete);
194     listFiles();
195     testCompactDelFiles(tableName, 1, 13, false);
196   }
197 
198   @Test
199   public void testCompactDelFilesWithSmallBatchSize() throws Exception {
200     resetConf();
201     String tableName = "testCompactDelFilesWithSmallBatchSize";
202     init(tableName);
203     // create 20 mob files.
204     createStoreFiles(basePath, family, qf, 20, Type.Put);
205     // create 13 del files
206     createStoreFiles(basePath, family, qf, 13, Type.Delete);
207     listFiles();
208 
209     // set the mob file compaction batch size
210     conf.setInt(MobConstants.MOB_FILE_COMPACTION_BATCH_SIZE, 4);
211     testCompactDelFiles(tableName, 1, 13, false);
212   }
213 
214   @Test
215   public void testCompactDelFilesChangeMaxDelFileCount() throws Exception {
216     resetConf();
217     String tableName = "testCompactDelFilesWithSmallBatchSize";
218     init(tableName);
219     // create 20 mob files.
220     createStoreFiles(basePath, family, qf, 20, Type.Put);
221     // create 13 del files
222     createStoreFiles(basePath, family, qf, 13, Type.Delete);
223     listFiles();
224 
225     // set the max del file count
226     conf.setInt(MobConstants.MOB_DELFILE_MAX_COUNT, 5);
227     // set the mob file compaction batch size
228     conf.setInt(MobConstants.MOB_FILE_COMPACTION_BATCH_SIZE, 2);
229     testCompactDelFiles(tableName, 4, 13, false);
230   }
231 
232   /**
233    * Tests the selectFiles
234    * @param tableName the table name
235    * @param type the expected compaction type
236    * @param expected the expected start keys
237    */
238   private void testSelectFiles(String tableName, final CompactionType type,
239     final boolean isForceAllFiles, final List<String> expected) throws IOException {
240     PartitionedMobFileCompactor compactor = new PartitionedMobFileCompactor(conf, fs,
241       TableName.valueOf(tableName), hcd, pool) {
242       @Override
243       public List<Path> compact(List<FileStatus> files, boolean isForceAllFiles)
244         throws IOException {
245         if (files == null || files.isEmpty()) {
246           return null;
247         }
248         PartitionedMobFileCompactionRequest request = select(files, isForceAllFiles);
249         // assert the compaction type
250         Assert.assertEquals(type, request.type);
251         // assert get the right partitions
252         compareCompactedPartitions(expected, request.compactionPartitions);
253         // assert get the right del files
254         compareDelFiles(request.delFiles);
255         return null;
256       }
257     };
258     compactor.compact(allFiles, isForceAllFiles);
259   }
260 
261   /**
262    * Tests the compacteDelFile
263    * @param tableName the table name
264    * @param expectedFileCount the expected file count
265    * @param expectedCellCount the expected cell count
266    */
267   private void testCompactDelFiles(String tableName, final int expectedFileCount,
268       final int expectedCellCount, boolean isForceAllFiles) throws IOException {
269     PartitionedMobFileCompactor compactor = new PartitionedMobFileCompactor(conf, fs,
270       TableName.valueOf(tableName), hcd, pool) {
271       @Override
272       protected List<Path> performCompaction(PartitionedMobFileCompactionRequest request)
273           throws IOException {
274         List<Path> delFilePaths = new ArrayList<Path>();
275         for (FileStatus delFile : request.delFiles) {
276           delFilePaths.add(delFile.getPath());
277         }
278         List<Path> newDelPaths = compactDelFiles(request, delFilePaths);
279         // assert the del files are merged.
280         Assert.assertEquals(expectedFileCount, newDelPaths.size());
281         Assert.assertEquals(expectedCellCount, countDelCellsInDelFiles(newDelPaths));
282         return null;
283       }
284     };
285     compactor.compact(allFiles, isForceAllFiles);
286   }
287 
288   /**
289    * Lists the files in the path
290    */
291   private void listFiles() throws IOException {
292     for (FileStatus file : fs.listStatus(basePath)) {
293       allFiles.add(file);
294       if (file.getPath().getName().endsWith("_del")) {
295         delFiles.add(file);
296       } else {
297         mobFiles.add(file);
298       }
299     }
300   }
301 
302   /**
303    * Compares the compacted partitions.
304    * @param partitions the collection of CompactedPartitions
305    */
306   private void compareCompactedPartitions(List<String> expected,
307       Collection<CompactionPartition> partitions) {
308     List<String> actualKeys = new ArrayList<>();
309     for (CompactionPartition partition : partitions) {
310       actualKeys.add(partition.getPartitionId().getStartKey());
311     }
312     Collections.sort(expected);
313     Collections.sort(actualKeys);
314     Assert.assertEquals(expected.size(), actualKeys.size());
315     for (int i = 0; i < expected.size(); i++) {
316       Assert.assertEquals(expected.get(i), actualKeys.get(i));
317     }
318   }
319 
320   /**
321    * Compares the del files.
322    * @param allDelFiles all the del files
323    */
324   private void compareDelFiles(Collection<FileStatus> allDelFiles) {
325     int i = 0;
326     for (FileStatus file : allDelFiles) {
327       Assert.assertEquals(delFiles.get(i), file);
328       i++;
329     }
330   }
331 
332   /**
333    * Creates store files.
334    * @param basePath the path to create file
335    * @family the family name
336    * @qualifier the column qualifier
337    * @count the store file number
338    * @type the key type
339    */
340   private void createStoreFiles(Path basePath, String family, String qualifier, int count,
341       Type type) throws IOException {
342     HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build();
343     String startKey = "row_";
344     MobFileName mobFileName = null;
345     for (int i = 0; i < count; i++) {
346       byte[] startRow = Bytes.toBytes(startKey + i) ;
347       if(type.equals(Type.Delete)) {
348         mobFileName = MobFileName.create(startRow, MobUtils.formatDate(
349             new Date()), delSuffix);
350       }
351       if(type.equals(Type.Put)){
352         mobFileName = MobFileName.create(Bytes.toBytes(startKey + i), MobUtils.formatDate(
353             new Date()), mobSuffix);
354       }
355       StoreFile.Writer mobFileWriter = new StoreFile.WriterBuilder(conf, cacheConf, fs)
356       .withFileContext(meta).withFilePath(new Path(basePath, mobFileName.getFileName())).build();
357       writeStoreFile(mobFileWriter, startRow, Bytes.toBytes(family), Bytes.toBytes(qualifier),
358           type, (i+1)*1000);
359     }
360   }
361 
362   /**
363    * Writes data to store file.
364    * @param writer the store file writer
365    * @param row the row key
366    * @param family the family name
367    * @param qualifier the column qualifier
368    * @param type the key type
369    * @param size the size of value
370    */
371   private static void writeStoreFile(final StoreFile.Writer writer, byte[]row, byte[] family,
372       byte[] qualifier, Type type, int size) throws IOException {
373     long now = System.currentTimeMillis();
374     try {
375       byte[] dummyData = new byte[size];
376       new Random().nextBytes(dummyData);
377       writer.append(new KeyValue(row, family, qualifier, now, type, dummyData));
378     } finally {
379       writer.close();
380     }
381   }
382 
383   /**
384    * Gets the number of del cell in the del files
385    * @param paths the del file paths
386    * @return the cell size
387    */
388   private int countDelCellsInDelFiles(List<Path> paths) throws IOException {
389     List<StoreFile> sfs = new ArrayList<StoreFile>();
390     int size = 0;
391     for(Path path : paths) {
392       StoreFile sf = new StoreFile(fs, path, conf, cacheConf, BloomType.NONE);
393       sfs.add(sf);
394     }
395     List scanners = StoreFileScanner.getScannersForStoreFiles(sfs, false, true,
396         false, false, HConstants.LATEST_TIMESTAMP);
397     Scan scan = new Scan();
398     scan.setMaxVersions(hcd.getMaxVersions());
399     long timeToPurgeDeletes = Math.max(conf.getLong("hbase.hstore.time.to.purge.deletes", 0), 0);
400     long ttl = HStore.determineTTLFromFamily(hcd);
401     ScanInfo scanInfo = new ScanInfo(conf, hcd, ttl, timeToPurgeDeletes, KeyValue.COMPARATOR);
402     StoreScanner scanner = new StoreScanner(scan, scanInfo, ScanType.COMPACT_RETAIN_DELETES, null,
403         scanners, 0L, HConstants.LATEST_TIMESTAMP);
404     List<Cell> results = new ArrayList<>();
405     boolean hasMore = true;
406     while (hasMore) {
407       hasMore = scanner.next(results);
408       size += results.size();
409       results.clear();
410     }
411     scanner.close();
412     return size;
413   }
414 
415   private static ExecutorService createThreadPool(Configuration conf) {
416     int maxThreads = 10;
417     long keepAliveTime = 60;
418     final SynchronousQueue<Runnable> queue = new SynchronousQueue<Runnable>();
419     ThreadPoolExecutor pool = new ThreadPoolExecutor(1, maxThreads, keepAliveTime,
420       TimeUnit.SECONDS, queue, Threads.newDaemonThreadFactory("MobFileCompactionChore"),
421       new RejectedExecutionHandler() {
422         @Override
423         public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
424           try {
425             // waiting for a thread to pick up instead of throwing exceptions.
426             queue.put(r);
427           } catch (InterruptedException e) {
428             throw new RejectedExecutionException(e);
429           }
430         }
431       });
432     ((ThreadPoolExecutor) pool).allowCoreThreadTimeOut(true);
433     return pool;
434   }
435 
436   /**
437    * Resets the configuration.
438    */
439   private void resetConf() {
440     conf.setLong(MobConstants.MOB_FILE_COMPACTION_MERGEABLE_THRESHOLD,
441       MobConstants.DEFAULT_MOB_FILE_COMPACTION_MERGEABLE_THRESHOLD);
442     conf.setInt(MobConstants.MOB_DELFILE_MAX_COUNT, MobConstants.DEFAULT_MOB_DELFILE_MAX_COUNT);
443     conf.setInt(MobConstants.MOB_FILE_COMPACTION_BATCH_SIZE,
444       MobConstants.DEFAULT_MOB_FILE_COMPACTION_BATCH_SIZE);
445   }
446 }