From 3e6de98856b94ce0b32dafaaa3fa4dd18331d9e8 Mon Sep 17 00:00:00 2001 From: "terence.yoo" Date: Thu, 31 Dec 2015 10:41:24 +0900 Subject: [PATCH] Do not skip large files at the periodic major compaction --- .../compactions/RatioBasedCompactionPolicy.java | 51 ++++++-- .../regionserver/TestDefaultCompactSelection.java | 27 +++- .../hbase/regionserver/TestMajorCompaction.java | 144 +++++++++++++++++++++ 3 files changed, 205 insertions(+), 17 deletions(-) diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/compactions/RatioBasedCompactionPolicy.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/compactions/RatioBasedCompactionPolicy.java index a823d7c..5b013df 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/compactions/RatioBasedCompactionPolicy.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/compactions/RatioBasedCompactionPolicy.java @@ -19,6 +19,10 @@ package org.apache.hadoop.hbase.regionserver.compactions; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; + import java.io.IOException; import java.util.ArrayList; import java.util.Collection; @@ -34,10 +38,7 @@ import org.apache.hadoop.hbase.regionserver.RSRpcServices; import org.apache.hadoop.hbase.regionserver.StoreConfigInformation; import org.apache.hadoop.hbase.regionserver.StoreFile; import org.apache.hadoop.hbase.regionserver.StoreUtils; - -import com.google.common.base.Preconditions; -import com.google.common.base.Predicate; -import com.google.common.collect.Collections2; +import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; /** * The default algorithm for selecting files for compaction. @@ -93,9 +94,11 @@ public class RatioBasedCompactionPolicy extends CompactionPolicy { filesCompacting.size() + " compacting, " + candidateSelection.size() + " eligible, " + storeConfigInfo.getBlockingFileCount() + " blocking"); + // If the major compaction period has elapsed, we should run a major compaction. + boolean isPeriodicMajorCompaction = isPeriodicMajorCompaction(candidateSelection); // If we can't have all files, we cannot do major anyway boolean isAllFiles = candidateFiles.size() == candidateSelection.size(); - if (!(forceMajor && isAllFiles)) { + if (!(forceMajor && isAllFiles || isPeriodicMajorCompaction)) { candidateSelection = skipLargeFiles(candidateSelection, mayUseOffPeak); isAllFiles = candidateFiles.size() == candidateSelection.size(); } @@ -274,6 +277,31 @@ public class RatioBasedCompactionPolicy extends CompactionPolicy { return candidates; } + /** + * Determines whether a periodic major compaction should be triggered + * @param candidates pre-filtrate + * @return True if this should be a periodic major compaction + */ + private boolean isPeriodicMajorCompaction(Collection candidates) throws IOException { + long mcTime = getNextMajorCompactTime(candidates); + if (candidates == null || candidates.isEmpty() || mcTime == 0) { + return false; + } + // TODO: Use better method for determining stamp of last major (HBASE-2990) + long lowTimestamp = StoreUtils.getLowestTimestamp(candidates); + long now = EnvironmentEdgeManager.currentTime(); + if (lowTimestamp > 0L && lowTimestamp < (now - mcTime)) { + long period = comConf.getMajorCompactionPeriod(); + LOG.debug("Major compaction period has elapsed" + + "; period " + period + "ms" + + "; time since last major compaction " + (now - lowTimestamp) + "ms" + + ". Need to run a major compaction. This might be logged more than once."); + return true; + } else { + return false; + } + } + /* * @param filesToCompact Files to compact. Can be null. * @return True if we should run a major compaction. @@ -282,16 +310,13 @@ public class RatioBasedCompactionPolicy extends CompactionPolicy { public boolean isMajorCompaction(final Collection filesToCompact) throws IOException { boolean result = false; - long mcTime = getNextMajorCompactTime(filesToCompact); - if (filesToCompact == null || filesToCompact.isEmpty() || mcTime == 0) { - return result; - } - // TODO: Use better method for determining stamp of last major (HBASE-2990) - long lowTimestamp = StoreUtils.getLowestTimestamp(filesToCompact); - long now = System.currentTimeMillis(); - if (lowTimestamp > 0l && lowTimestamp < (now - mcTime)) { + if (isPeriodicMajorCompaction(filesToCompact)) { // Major compaction time has elapsed. long cfTtl = this.storeConfigInfo.getStoreFileTtl(); + + // TODO: Use better method for determining stamp of last major (HBASE-2990) + long lowTimestamp = StoreUtils.getLowestTimestamp(filesToCompact); + long now = EnvironmentEdgeManager.currentTime(); if (filesToCompact.size() == 1) { // Single file StoreFile sf = filesToCompact.iterator().next(); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestDefaultCompactSelection.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestDefaultCompactSelection.java index d68c6b1..c3d2779 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestDefaultCompactSelection.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestDefaultCompactSelection.java @@ -78,8 +78,8 @@ public class TestDefaultCompactSelection extends TestCase { this.conf.setLong(HConstants.HREGION_MEMSTORE_FLUSH_SIZE, minSize); this.conf.setLong("hbase.hstore.compaction.max.size", maxSize); this.conf.setFloat("hbase.hstore.compaction.ratio", 1.0F); - // Test depends on this not being set to pass. Default breaks test. TODO: Revisit. - this.conf.unset("hbase.hstore.compaction.min.size"); + // Test depends on this not being set to pass. Default breaks test. + this.conf.setLong("hbase.hstore.compaction.min.size", minSize); //Setting up a Store final String id = TestDefaultCompactSelection.class.getName(); @@ -265,6 +265,7 @@ public class TestDefaultCompactSelection extends TestCase { } finally { conf.setLong(HConstants.MAJOR_COMPACTION_PERIOD, 1000*60*60*24); conf.setFloat("hbase.hregion.majorcompaction.jitter", 0.20F); + store.storeEngine.getCompactionPolicy().setConf(conf); } /* REFERENCES == file is from a region that was split */ @@ -277,8 +278,26 @@ public class TestDefaultCompactSelection extends TestCase { // empty case compactEquals(new ArrayList() /* empty */); - // empty case (because all files are too big) - compactEquals(sfCreate(tooBig, tooBig) /* empty */); + + // empty case in minor compaction because all files are too big + compactEquals(sfCreate(tooBig, tooBig) /* empty */); + // minor compaction for the single and large store file should be skipped + compactEquals(sfCreate(tooBig) /* empty */); + + // trigger an aged major compaction + // all files are too big, nevertheless all files should be compacted in a major compaction + conf.setLong(HConstants.MAJOR_COMPACTION_PERIOD, 1); + conf.setFloat("hbase.hregion.majorcompaction.jitter", 0); + store.storeEngine.getCompactionPolicy().setConf(conf); + try { + compactEquals(sfCreate(tooBig, tooBig), tooBig, tooBig); + // major compaction for the single and large store file should be skipped + compactEquals(sfCreate(tooBig) /* empty */); + } finally { + conf.setLong(HConstants.MAJOR_COMPACTION_PERIOD, 1000*60*60*24); + conf.setFloat("hbase.hregion.majorcompaction.jitter", 0.20F); + store.storeEngine.getCompactionPolicy().setConf(conf); + } } public void testOffPeakCompactionRatio() throws IOException { diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestMajorCompaction.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestMajorCompaction.java index 3ef89ad..06c132b 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestMajorCompaction.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestMajorCompaction.java @@ -18,6 +18,8 @@ */ package org.apache.hadoop.hbase.regionserver; +import static org.apache.hadoop.hbase.HBaseTestingUtility.FIRST_CHAR; +import static org.apache.hadoop.hbase.HBaseTestingUtility.LAST_CHAR; import static org.apache.hadoop.hbase.HBaseTestingUtility.START_KEY; import static org.apache.hadoop.hbase.HBaseTestingUtility.START_KEY_BYTES; import static org.apache.hadoop.hbase.HBaseTestingUtility.fam1; @@ -41,8 +43,10 @@ import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.CellUtil; import org.apache.hadoop.hbase.HBaseTestCase; import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.client.Result; @@ -58,6 +62,7 @@ import org.apache.hadoop.hbase.regionserver.compactions.RatioBasedCompactionPoli import org.apache.hadoop.hbase.testclassification.MediumTests; import org.apache.hadoop.hbase.testclassification.RegionServerTests; import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; import org.apache.hadoop.hbase.wal.WAL; import org.junit.After; import org.junit.Before; @@ -85,6 +90,8 @@ public class TestMajorCompaction { private int compactionThreshold; private byte[] secondRowBytes, thirdRowBytes; private static final long MAX_FILES_TO_COMPACT = 10; + private static int TTL = 10; // second + private static final int MAX_VERSIONS = 3; /** constructor */ public TestMajorCompaction() { @@ -478,4 +485,141 @@ public class TestMajorCompaction { s.close(); assertEquals(0, counter); } + + /** + * Test for HBASE-15055 + * Test major compaction with both of TTL and hbase.hstore.compaction.max.size with 1 store file + * The expiredRatio should be 1.0. + */ + @Test + public void testMajorCompactionWithTtlAndCompactionMaxSize1() throws Exception { + testMajorCompactionWithTtlAndCompactionMaxSize(1); + } + + /** + * Test for HBASE-15055 + * Test major compaction with both of TTL and hbase.hstore.compaction.max.size with 3 store files + * The expiredRatio should be greater than the default threshold, 0.5. + */ + @Test + public void testMajorCompactionWithTtlAndCompactionMaxSize3() throws Exception { + testMajorCompactionWithTtlAndCompactionMaxSize(3); + } + + /** + * Test for HBASE-15055 + * Test major compaction with both of TTL and hbase.hstore.compaction.max.size + */ + private void testMajorCompactionWithTtlAndCompactionMaxSize(int storeFiles) throws Exception { + int delay = (int) (TTL / 3.0 * 1000); + float jitterPct = 0.0f; + conf.setLong(HConstants.MAJOR_COMPACTION_PERIOD, 10 * 1000); + conf.setFloat("hbase.hregion.majorcompaction.jitter", jitterPct); + long verySmallThreshold = 1; + conf.setLong("hbase.hstore.compaction.max.size", verySmallThreshold); + + long notExpiredRecordCount = 0; + + recreateRegionWithTTL(); + + HStore s = ((HStore) r.getStore(COLUMN_FAMILY)); + s.storeEngine.getCompactionPolicy().setConf(conf); + try { + if (storeFiles == 1) { + notExpiredRecordCount = createStoreFile(r, true); + } else { + for (int i = 0; i < storeFiles - 1; i++) + createStoreFile(r, true); + + // The store file that does not TTL expired records should be created if the number of + // store file is greater than 1. + notExpiredRecordCount = createStoreFile(r, false); + } + + assertEquals(storeFiles, s.getStorefilesCount()); + long storefilesSizeBeforeMC = s.getStorefilesSize(); + + // Should not be major compacted yet. No TTL expiration. + Thread.sleep(delay); + r.compact(false); + assertEquals(storeFiles, s.getStorefilesCount()); + assertTrue(storefilesSizeBeforeMC == s.getStorefilesSize()); + assertTrue(notExpiredRecordCount < countRecords()); // No TTL expiration + + // Should not be major compacted yet. Some TTL expiration but need not to run MC yet. + Thread.sleep(delay); + r.compact(false); + assertEquals(storeFiles, s.getStorefilesCount()); + assertTrue(storefilesSizeBeforeMC == s.getStorefilesSize()); + assertEquals(notExpiredRecordCount, countRecords()); // TTL expiration + + // Should be major compacted. Some TTL expiration. + Thread.sleep(delay); + r.compact(false); + assertEquals(1, s.getStorefilesCount()); + assertTrue(storefilesSizeBeforeMC > s.getStorefilesSize()); + assertEquals(notExpiredRecordCount, countRecords()); // TTL expiration + } finally { + // reset the timed compaction settings + conf.setLong(HConstants.MAJOR_COMPACTION_PERIOD, 1000 * 60 * 60 * 24); + conf.setFloat("hbase.hregion.majorcompaction.jitter", 0.20F); + // run a major to reset the cache + createStoreFile(r); + r.compact(true); + assertEquals(1, s.getStorefilesCount()); + } + } + + private void recreateRegionWithTTL() throws Exception { + tearDown(); + + HTableDescriptor htd = UTIL.createTableDescriptor(TableName.valueOf(name.getMethodName()), + HColumnDescriptor.DEFAULT_MIN_VERSIONS, MAX_VERSIONS, TTL, + HColumnDescriptor.DEFAULT_KEEP_DELETED); + this.r = UTIL.createLocalHRegion(htd, null, null); + } + + /** + * The size of store file that has TTL expired records is slightly greater than + * the size of store file that does not have TTL expired records. + */ + private long createStoreFile(final Region region, boolean hasExpiredRecords) + throws IOException { + final byte [] START_KEY_BYTES_1 = {FIRST_CHAR, FIRST_CHAR, FIRST_CHAR}; + final byte [] START_KEY_BYTES_2 = {LAST_CHAR, FIRST_CHAR, FIRST_CHAR}; + Table loader = new RegionAsTable(region); + + long now = EnvironmentEdgeManager.currentTime(); + long notExpiredTimestamp = now + TTL * 2 * 1000; + long expiredTimestamp = now - (long)(TTL * 1.5 / 3.0) * 1000; + + // The data will not be expired + long notExpiredRecordCount = HBaseTestCase.addContent(loader, Bytes.toString(COLUMN_FAMILY), + null, START_KEY_BYTES_1, START_KEY_BYTES_2, notExpiredTimestamp); + + // The data will be expired + if (hasExpiredRecords) { + HBaseTestCase.addContent(loader, Bytes.toString(COLUMN_FAMILY), + null, START_KEY_BYTES_2, null, expiredTimestamp); + } + + region.flush(true); + return notExpiredRecordCount; + } + + private long countRecords() throws IOException { + long counter = 0; + RegionScanner scanner = r.getScanner(new Scan()); + do { + List cells = new ArrayList<>(); + boolean existNext = scanner.next(cells); + if (existNext || cells.size() > 0) { + counter++; + } + if (!existNext) { + break; + } + } while (true); + return counter; + } } -- 2.5.4 (Apple Git-61)