1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
121 createStoreFiles(basePath, family, qf, count, Type.Put);
122
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
144 createStoreFiles(basePath, family, qf, count, Type.Put);
145
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
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
169 createStoreFiles(basePath, family, qf, count, Type.Put);
170
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
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
191 createStoreFiles(basePath, family, qf, 20, Type.Put);
192
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
204 createStoreFiles(basePath, family, qf, 20, Type.Put);
205
206 createStoreFiles(basePath, family, qf, 13, Type.Delete);
207 listFiles();
208
209
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
220 createStoreFiles(basePath, family, qf, 20, Type.Put);
221
222 createStoreFiles(basePath, family, qf, 13, Type.Delete);
223 listFiles();
224
225
226 conf.setInt(MobConstants.MOB_DELFILE_MAX_COUNT, 5);
227
228 conf.setInt(MobConstants.MOB_FILE_COMPACTION_BATCH_SIZE, 2);
229 testCompactDelFiles(tableName, 4, 13, false);
230 }
231
232
233
234
235
236
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
250 Assert.assertEquals(type, request.type);
251
252 compareCompactedPartitions(expected, request.compactionPartitions);
253
254 compareDelFiles(request.delFiles);
255 return null;
256 }
257 };
258 compactor.compact(allFiles, isForceAllFiles);
259 }
260
261
262
263
264
265
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
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
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
304
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
322
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
334
335
336
337
338
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
364
365
366
367
368
369
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
385
386
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
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
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 }