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.HBaseTestingUtility.START_KEY;
22  import static org.apache.hadoop.hbase.HBaseTestingUtility.fam1;
23  import static org.junit.Assert.assertEquals;
24  import static org.junit.Assert.assertTrue;
25  
26  import java.io.IOException;
27  import java.util.ArrayList;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Random;
32  import java.util.Set;
33  
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
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.CellUtil;
42  import org.apache.hadoop.hbase.HBaseTestCase.HRegionIncommon;
43  import org.apache.hadoop.hbase.HBaseTestingUtility;
44  import org.apache.hadoop.hbase.HColumnDescriptor;
45  import org.apache.hadoop.hbase.HConstants;
46  import org.apache.hadoop.hbase.HTableDescriptor;
47  import org.apache.hadoop.hbase.KeyValue;
48  import org.apache.hadoop.hbase.KeyValueUtil;
49  import org.apache.hadoop.hbase.testclassification.MediumTests;
50  import org.apache.hadoop.hbase.client.Delete;
51  import org.apache.hadoop.hbase.client.Durability;
52  import org.apache.hadoop.hbase.client.Put;
53  import org.apache.hadoop.hbase.client.Scan;
54  import org.apache.hadoop.hbase.io.hfile.CacheConfig;
55  import org.apache.hadoop.hbase.io.hfile.HFile;
56  import org.apache.hadoop.hbase.io.hfile.HFileContext;
57  import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder;
58  import org.apache.hadoop.hbase.mob.MobConstants;
59  import org.apache.hadoop.hbase.mob.MobUtils;
60  import org.apache.hadoop.hbase.util.Bytes;
61  import org.apache.hadoop.hbase.util.FSUtils;
62  import org.apache.hadoop.hbase.util.Pair;
63  import org.junit.After;
64  import org.junit.AfterClass;
65  import org.junit.BeforeClass;
66  import org.junit.Rule;
67  import org.junit.Test;
68  import org.junit.experimental.categories.Category;
69  import org.junit.rules.TestName;
70  
71  /**
72   * Test mob compaction
73   */
74  @Category(MediumTests.class)
75  public class TestMobCompaction {
76    @Rule
77    public TestName name = new TestName();
78    static final Log LOG = LogFactory.getLog(TestMobCompaction.class.getName());
79    private final static HBaseTestingUtility UTIL = new HBaseTestingUtility();
80    private Configuration conf = null;
81  
82    private HRegion region = null;
83    private HTableDescriptor htd = null;
84    private HColumnDescriptor hcd = null;
85    private long mobCellThreshold = 1000;
86  
87    private FileSystem fs;
88  
89    private static final byte[] COLUMN_FAMILY = fam1;
90    private final byte[] STARTROW = Bytes.toBytes(START_KEY);
91    private int compactionThreshold;
92  
93    @BeforeClass
94    public static void setUpBeforeClass() throws Exception {
95      UTIL.getConfiguration().setInt("hfile.format.version", 3);
96      UTIL.getConfiguration().setInt("hbase.master.info.port", 0);
97      UTIL.getConfiguration().setBoolean("hbase.regionserver.info.port.auto", true);
98      UTIL.startMiniCluster(1);
99    }
100 
101   @AfterClass
102   public static void tearDownAfterClass() throws Exception {
103     UTIL.shutdownMiniCluster();
104   }
105 
106   private void init(Configuration conf, long mobThreshold) throws Exception {
107     this.conf = conf;
108     this.mobCellThreshold = mobThreshold;
109     HBaseTestingUtility UTIL = new HBaseTestingUtility(conf);
110 
111     compactionThreshold = conf.getInt("hbase.hstore.compactionThreshold", 3);
112     htd = UTIL.createTableDescriptor(name.getMethodName());
113     hcd = htd.getFamily(COLUMN_FAMILY);
114     hcd.setMobEnabled(true);
115     hcd.setMobThreshold(mobThreshold);
116     hcd.setMaxVersions(1);
117 
118     region = UTIL.createLocalHRegion(htd, null, null);
119     fs = FileSystem.get(conf);
120   }
121 
122   @After
123   public void tearDown() throws Exception {
124     region.close();
125     fs.delete(UTIL.getDataTestDir(), true);
126   }
127 
128   /**
129    * During compaction, cells smaller than the threshold won't be affected.
130    */
131   @Test
132   public void testSmallerValue() throws Exception {
133     init(UTIL.getConfiguration(), 500);
134     byte[] dummyData = makeDummyData(300); // smaller than mob threshold
135     HRegionIncommon loader = new HRegionIncommon(region);
136     // one hfile per row
137     for (int i = 0; i < compactionThreshold; i++) {
138       Put p = createPut(i, dummyData);
139       loader.put(p);
140       loader.flushcache();
141     }
142     assertEquals("Before compaction: store files", compactionThreshold, countStoreFiles());
143     assertEquals("Before compaction: mob file count", 0, countMobFiles());
144     assertEquals("Before compaction: rows", compactionThreshold, countRows());
145     assertEquals("Before compaction: mob rows", 0, countMobRows());
146 
147     region.compactStores();
148 
149     assertEquals("After compaction: store files", 1, countStoreFiles());
150     assertEquals("After compaction: mob file count", 0, countMobFiles());
151     assertEquals("After compaction: referenced mob file count", 0, countReferencedMobFiles());
152     assertEquals("After compaction: rows", compactionThreshold, countRows());
153     assertEquals("After compaction: mob rows", 0, countMobRows());
154   }
155 
156   /**
157    * During compaction, the mob threshold size is changed.
158    */
159   @Test
160   public void testLargerValue() throws Exception {
161     init(UTIL.getConfiguration(), 200);
162     byte[] dummyData = makeDummyData(300); // larger than mob threshold
163     HRegionIncommon loader = new HRegionIncommon(region);
164     for (int i = 0; i < compactionThreshold; i++) {
165       Put p = createPut(i, dummyData);
166       loader.put(p);
167       loader.flushcache();
168     }
169     assertEquals("Before compaction: store files", compactionThreshold, countStoreFiles());
170     assertEquals("Before compaction: mob file count", compactionThreshold, countMobFiles());
171     assertEquals("Before compaction: rows", compactionThreshold, countRows());
172     assertEquals("Before compaction: mob rows", compactionThreshold, countMobRows());
173     assertEquals("Before compaction: number of mob cells", compactionThreshold,
174         countMobCellsInMetadata());
175     // Change the threshold larger than the data size
176     region.getTableDesc().getFamily(COLUMN_FAMILY).setMobThreshold(500);
177     region.initialize();
178     region.compactStores();
179 
180     assertEquals("After compaction: store files", 1, countStoreFiles());
181     assertEquals("After compaction: mob file count", compactionThreshold, countMobFiles());
182     assertEquals("After compaction: referenced mob file count", 0, countReferencedMobFiles());
183     assertEquals("After compaction: rows", compactionThreshold, countRows());
184     assertEquals("After compaction: mob rows", 0, countMobRows());
185   }
186 
187   /**
188    * This test will first generate store files, then bulk load them and trigger the compaction. When
189    * compaction, the cell value will be larger than the threshold.
190    */
191   @Test
192   public void testMobCompactionWithBulkload() throws Exception {
193     // The following will produce store files of 600.
194     init(UTIL.getConfiguration(), 300);
195     byte[] dummyData = makeDummyData(600);
196 
197     Path hbaseRootDir = FSUtils.getRootDir(conf);
198     Path basedir = new Path(hbaseRootDir, htd.getNameAsString());
199     List<Pair<byte[], String>> hfiles = new ArrayList<Pair<byte[], String>>(1);
200     for (int i = 0; i < compactionThreshold; i++) {
201       Path hpath = new Path(basedir, "hfile" + i);
202       hfiles.add(Pair.newPair(COLUMN_FAMILY, hpath.toString()));
203       createHFile(hpath, i, dummyData);
204     }
205 
206     // The following will bulk load the above generated store files and compact, with 600(fileSize)
207     // > 300(threshold)
208     boolean result = region.bulkLoadHFiles(hfiles, true, null);
209     assertTrue("Bulkload result:", result);
210     assertEquals("Before compaction: store files", compactionThreshold, countStoreFiles());
211     assertEquals("Before compaction: mob file count", 0, countMobFiles());
212     assertEquals("Before compaction: rows", compactionThreshold, countRows());
213     assertEquals("Before compaction: mob rows", 0, countMobRows());
214     assertEquals("Before compaction: referenced mob file count", 0, countReferencedMobFiles());
215 
216     region.compactStores();
217 
218     assertEquals("After compaction: store files", 1, countStoreFiles());
219     assertEquals("After compaction: mob file count:", 1, countMobFiles());
220     assertEquals("After compaction: rows", compactionThreshold, countRows());
221     assertEquals("After compaction: mob rows", compactionThreshold, countMobRows());
222     assertEquals("After compaction: referenced mob file count", 1, countReferencedMobFiles());
223     assertEquals("After compaction: number of mob cells", compactionThreshold,
224         countMobCellsInMetadata());
225   }
226 
227   @Test
228   public void testMajorCompactionAfterDelete() throws Exception {
229     init(UTIL.getConfiguration(), 100);
230     byte[] dummyData = makeDummyData(200); // larger than mob threshold
231     HRegionIncommon loader = new HRegionIncommon(region);
232     // create hfiles and mob hfiles but don't trigger compaction
233     int numHfiles = compactionThreshold - 1;
234     byte[] deleteRow = Bytes.add(STARTROW, Bytes.toBytes(0));
235     for (int i = 0; i < numHfiles; i++) {
236       Put p = createPut(i, dummyData);
237       loader.put(p);
238       loader.flushcache();
239     }
240     assertEquals("Before compaction: store files", numHfiles, countStoreFiles());
241     assertEquals("Before compaction: mob file count", numHfiles, countMobFiles());
242     assertEquals("Before compaction: rows", numHfiles, countRows());
243     assertEquals("Before compaction: mob rows", numHfiles, countMobRows());
244     assertEquals("Before compaction: number of mob cells", numHfiles, countMobCellsInMetadata());
245     // now let's delete some cells that contain mobs
246     Delete delete = new Delete(deleteRow);
247     delete.deleteFamily(COLUMN_FAMILY);
248     region.delete(delete);
249     loader.flushcache();
250 
251     assertEquals("Before compaction: store files", numHfiles + 1, countStoreFiles());
252     assertEquals("Before compaction: mob files", numHfiles, countMobFiles());
253     region.compact(true);
254     assertEquals("After compaction: store files", 1, countStoreFiles());
255     // still have original mob hfiles and now added a mob del file
256     assertEquals("After compaction: mob files", numHfiles + 1, countMobFiles());
257 
258     Scan scan = new Scan();
259     scan.setRaw(true);
260     InternalScanner scanner = region.getScanner(scan);
261     List<Cell> results = new ArrayList<Cell>();
262     scanner.next(results);
263     int deleteCount = 0;
264     while (!results.isEmpty()) {
265       for (Cell c : results) {
266         if (c.getTypeByte() == KeyValue.Type.DeleteFamily.getCode()) {
267           deleteCount++;
268           assertTrue(Bytes.equals(CellUtil.cloneRow(c), deleteRow));
269         }
270       }
271       results.clear();
272       scanner.next(results);
273     }
274     // assert the delete mark is not retained after the major compaction
275     assertEquals(0, deleteCount);
276     scanner.close();
277     // assert the deleted cell is not counted
278     assertEquals("The cells in mob files", numHfiles - 1, countMobCellsInMobFiles(1));
279   }
280 
281   private int countStoreFiles() throws IOException {
282     Store store = region.getStore(COLUMN_FAMILY);
283     return store.getStorefilesCount();
284   }
285 
286   private int countMobFiles() throws IOException {
287     Path mobDirPath = new Path(MobUtils.getMobRegionPath(conf, htd.getTableName()),
288         hcd.getNameAsString());
289     if (fs.exists(mobDirPath)) {
290       FileStatus[] files = UTIL.getTestFileSystem().listStatus(mobDirPath);
291       return files.length;
292     }
293     return 0;
294   }
295 
296   private long countMobCellsInMetadata() throws IOException {
297     long mobCellsCount = 0;
298     Path mobDirPath = new Path(MobUtils.getMobRegionPath(conf, htd.getTableName()),
299         hcd.getNameAsString());
300     Configuration copyOfConf = new Configuration(conf);
301     copyOfConf.setFloat(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY, 0f);
302     CacheConfig cacheConfig = new CacheConfig(copyOfConf);
303     if (fs.exists(mobDirPath)) {
304       FileStatus[] files = UTIL.getTestFileSystem().listStatus(mobDirPath);
305       for (FileStatus file : files) {
306         StoreFile sf = new StoreFile(fs, file.getPath(), conf, cacheConfig, BloomType.NONE);
307         Map<byte[], byte[]> fileInfo = sf.createReader().loadFileInfo();
308         byte[] count = fileInfo.get(StoreFile.MOB_CELLS_COUNT);
309         assertTrue(count != null);
310         mobCellsCount += Bytes.toLong(count);
311       }
312     }
313     return mobCellsCount;
314   }
315 
316   private Put createPut(int rowIdx, byte[] dummyData) throws IOException {
317     Put p = new Put(Bytes.add(STARTROW, Bytes.toBytes(rowIdx)));
318     p.setDurability(Durability.SKIP_WAL);
319     p.add(COLUMN_FAMILY, Bytes.toBytes("colX"), dummyData);
320     return p;
321   }
322 
323   /**
324    * Create an HFile with the given number of bytes
325    */
326   private void createHFile(Path path, int rowIdx, byte[] dummyData) throws IOException {
327     HFileContext meta = new HFileContextBuilder().build();
328     HFile.Writer writer = HFile.getWriterFactory(conf, new CacheConfig(conf)).withPath(fs, path)
329         .withFileContext(meta).create();
330     long now = System.currentTimeMillis();
331     try {
332       KeyValue kv = new KeyValue(Bytes.add(STARTROW, Bytes.toBytes(rowIdx)), COLUMN_FAMILY,
333           Bytes.toBytes("colX"), now, dummyData);
334       writer.append(kv);
335     } finally {
336       writer.appendFileInfo(StoreFile.BULKLOAD_TIME_KEY, Bytes.toBytes(System.currentTimeMillis()));
337       writer.close();
338     }
339   }
340 
341   private int countMobRows() throws IOException {
342     Scan scan = new Scan();
343     // Do not retrieve the mob data when scanning
344     scan.setAttribute(MobConstants.MOB_SCAN_RAW, Bytes.toBytes(Boolean.TRUE));
345     InternalScanner scanner = region.getScanner(scan);
346 
347     int scannedCount = 0;
348     List<Cell> results = new ArrayList<Cell>();
349     boolean hasMore = true;
350     while (hasMore) {
351       hasMore = scanner.next(results);
352       for (Cell c : results) {
353         if (MobUtils.isMobReferenceCell(c)) {
354           scannedCount++;
355         }
356       }
357       results.clear();
358     }
359     scanner.close();
360 
361     return scannedCount;
362   }
363 
364   private int countRows() throws IOException {
365     Scan scan = new Scan();
366     // Do not retrieve the mob data when scanning
367     InternalScanner scanner = region.getScanner(scan);
368 
369     int scannedCount = 0;
370     List<Cell> results = new ArrayList<Cell>();
371     boolean hasMore = true;
372     while (hasMore) {
373       hasMore = scanner.next(results);
374       scannedCount += results.size();
375       results.clear();
376     }
377     scanner.close();
378 
379     return scannedCount;
380   }
381 
382   private byte[] makeDummyData(int size) {
383     byte[] dummyData = new byte[size];
384     new Random().nextBytes(dummyData);
385     return dummyData;
386   }
387 
388   private int countReferencedMobFiles() throws IOException {
389     Scan scan = new Scan();
390     // Do not retrieve the mob data when scanning
391     scan.setAttribute(MobConstants.MOB_SCAN_RAW, Bytes.toBytes(Boolean.TRUE));
392     InternalScanner scanner = region.getScanner(scan);
393 
394     List<Cell> kvs = new ArrayList<Cell>();
395     boolean hasMore = true;
396     String fileName;
397     Set<String> files = new HashSet<String>();
398     do {
399       kvs.clear();
400       hasMore = scanner.next(kvs);
401       for (Cell c : kvs) {
402         KeyValue kv = KeyValueUtil.ensureKeyValue(c);
403         if (!MobUtils.isMobReferenceCell(kv)) {
404           continue;
405         }
406         if (!MobUtils.hasValidMobRefCellValue(kv)) {
407           continue;
408         }
409         int size = MobUtils.getMobValueLength(kv);
410         if (size <= mobCellThreshold) {
411           continue;
412         }
413         fileName = MobUtils.getMobFileName(kv);
414         if (fileName.isEmpty()) {
415           continue;
416         }
417         files.add(fileName);
418         Path familyPath = MobUtils.getMobFamilyPath(conf, htd.getTableName(),
419             hcd.getNameAsString());
420         assertTrue(fs.exists(new Path(familyPath, fileName)));
421       }
422     } while (hasMore);
423 
424     scanner.close();
425 
426     return files.size();
427   }
428 
429   private int countMobCellsInMobFiles(int expectedNumDelfiles) throws IOException {
430     Configuration copyOfConf = new Configuration(conf);
431     copyOfConf.setFloat(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY, 0f);
432     CacheConfig cacheConfig = new CacheConfig(copyOfConf);
433     Path mobDirPath = new Path(MobUtils.getMobRegionPath(conf, htd.getTableName()),
434         hcd.getNameAsString());
435     List<StoreFile> sfs = new ArrayList<StoreFile>();
436     int numDelfiles = 0;
437     int size = 0;
438     if (fs.exists(mobDirPath)) {
439       for (FileStatus f : fs.listStatus(mobDirPath)) {
440         StoreFile sf = new StoreFile(fs, f.getPath(), conf, cacheConfig, BloomType.NONE);
441         sfs.add(sf);
442         if (StoreFileInfo.isDelFile(sf.getPath())) {
443           numDelfiles++;
444         }
445       }
446       List scanners = StoreFileScanner.getScannersForStoreFiles(sfs, false, true, false, false,
447           HConstants.LATEST_TIMESTAMP);
448       Scan scan = new Scan();
449       scan.setMaxVersions(hcd.getMaxVersions());
450       long timeToPurgeDeletes = Math.max(conf.getLong("hbase.hstore.time.to.purge.deletes", 0), 0);
451       long ttl = HStore.determineTTLFromFamily(hcd);
452       ScanInfo scanInfo = new ScanInfo(conf, hcd, ttl, timeToPurgeDeletes, KeyValue.COMPARATOR);
453       StoreScanner scanner = new StoreScanner(scan, scanInfo, ScanType.COMPACT_DROP_DELETES, null,
454           scanners, 0L, HConstants.LATEST_TIMESTAMP);
455       List<Cell> results = new ArrayList<>();
456       boolean hasMore = true;
457       while (hasMore) {
458         hasMore = scanner.next(results);
459         size += results.size();
460         results.clear();
461       }
462     }
463     // assert the number of the existing del files
464     assertEquals(expectedNumDelfiles, numDelfiles);
465     return size;
466   }
467 }