Skip to content

Commit a8a5b16

Browse files
committed
HBASE-22114 Port HBASE-15560 (TinyLFU-based BlockCache) to branch-1
HBASE-15560 W-TinyLFU based BlockCache (Ben Manes)
1 parent adc7c8d commit a8a5b16

File tree

15 files changed

+1350
-80
lines changed

15 files changed

+1350
-80
lines changed

hbase-common/src/main/java/org/apache/hadoop/hbase/io/util/HeapMemorySizeUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ public static float getL2BlockCacheHeapPercent(Configuration conf) {
180180
* @return the number of bytes to use for LRU, negative if disabled.
181181
* @throws IllegalArgumentException if HFILE_BLOCK_CACHE_SIZE_KEY is > 1.0
182182
*/
183-
public static long getLruCacheSize(final Configuration conf) {
183+
public static long getFirstLevelCacheSize(final Configuration conf, final long xmx) {
184184
float cachePercentage = conf.getFloat(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY,
185185
HConstants.HFILE_BLOCK_CACHE_SIZE_DEFAULT);
186186
if (cachePercentage <= 0.0001f) {

hbase-common/src/main/resources/hbase-default.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,15 @@ possible configurations would overwhelm and obscure the important.
806806
<description>
807807
The default thread pool size if parallel-seeking feature enabled.</description>
808808
</property>
809+
<property>
810+
<name>hfile.block.cache.policy</name>
811+
<value>LRU</value>
812+
<description>The eviction policy for the L1 block cache (LRU or TinyLFU). If you want to
813+
use TinyLFU you must build with JDK8+, run with JRE8+, and have both the
814+
hbase-tinylfu-blockcache module and its dependency the caffiene library installed into
815+
the classpath.
816+
</description>
817+
</property>
809818
<property>
810819
<name>hfile.block.cache.size</name>
811820
<value>0.4</value>

hbase-it/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,21 @@
265265
</dependencies>
266266

267267
<profiles>
268+
269+
<profile>
270+
<id>build-with-jdk8</id>
271+
<activation>
272+
<jdk>1.8</jdk>
273+
</activation>
274+
<dependencies>
275+
<dependency>
276+
<groupId>org.apache.hbase</groupId>
277+
<artifactId>hbase-tinylfu-blockcache</artifactId>
278+
<version>${project.version}</version>
279+
</dependency>
280+
</dependencies>
281+
</profile>
282+
268283
<profile>
269284
<id>rsgroup</id>
270285
<activation>

hbase-resource-bundle/src/main/resources/supplemental-models.xml

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,20 @@ under the License.
342342
</licenses>
343343
</project>
344344
</supplement>
345+
<supplement>
346+
<project>
347+
<groupId>com.github.ben-manes.caffeine</groupId>
348+
<artifactId>caffeine</artifactId>
349+
350+
<licenses>
351+
<license>
352+
<name>Apache License, Version 2.0</name>
353+
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
354+
<distribution>repo</distribution>
355+
</license>
356+
</licenses>
357+
</project>
358+
</supplement>
345359
<supplement>
346360
<project>
347361
<groupId>com.lmax</groupId>
@@ -2162,7 +2176,7 @@ Mozilla Public License Version 2.0
21622176
means any form of the work other than Source Code Form.
21632177

21642178
1.7. "Larger Work"
2165-
means a work that combines Covered Software with other material, in
2179+
means a work that combines Covered Software with other material, in
21662180
a separate file or files, that is not Covered Software.
21672181

21682182
1.8. "License"
@@ -2593,4 +2607,17 @@ Copyright (c) 2007-2011 The JRuby project
25932607
</licenses>
25942608
</project>
25952609
</supplement>
2610+
<supplement>
2611+
<project>
2612+
<groupId>com.google.errorprone</groupId>
2613+
<artifactId>error_prone_annotations</artifactId>
2614+
<licenses>
2615+
<license>
2616+
<name>Apache License, Version 2.0</name>
2617+
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
2618+
<distribution>repo</distribution>
2619+
</license>
2620+
</licenses>
2621+
</project>
2622+
</supplement>
25962623
</supplementalDataModels>

hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/CacheConfig.java

Lines changed: 78 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222

2323
import java.io.IOException;
2424
import java.lang.management.ManagementFactory;
25-
import java.lang.management.MemoryUsage;
25+
import java.util.concurrent.Executor;
26+
import java.util.concurrent.ForkJoinPool;
2627

2728
import org.apache.commons.logging.Log;
2829
import org.apache.commons.logging.LogFactory;
@@ -45,6 +46,12 @@
4546
public class CacheConfig {
4647
private static final Log LOG = LogFactory.getLog(CacheConfig.class.getName());
4748

49+
/**
50+
* Configuration key to cache block policy (Lru, TinyLfu).
51+
*/
52+
public static final String HFILE_BLOCK_CACHE_POLICY_KEY = "hfile.block.cache.policy";
53+
public static final String HFILE_BLOCK_CACHE_POLICY_DEFAULT = "LRU";
54+
4855
/**
4956
* Configuration key to cache data blocks on write. There are separate
5057
* switches for bloom blocks and non-root index blocks.
@@ -92,19 +99,19 @@ public class CacheConfig {
9299
* is an in-memory map that needs to be persisted across restarts. Where to store this
93100
* in-memory state is what you supply here: e.g. <code>/tmp/bucketcache.map</code>.
94101
*/
95-
public static final String BUCKET_CACHE_PERSISTENT_PATH_KEY =
102+
public static final String BUCKET_CACHE_PERSISTENT_PATH_KEY =
96103
"hbase.bucketcache.persistent.path";
97104

98105
/**
99106
* If the bucket cache is used in league with the lru on-heap block cache (meta blocks such
100107
* as indices and blooms are kept in the lru blockcache and the data blocks in the
101108
* bucket cache).
102109
*/
103-
public static final String BUCKET_CACHE_COMBINED_KEY =
110+
public static final String BUCKET_CACHE_COMBINED_KEY =
104111
"hbase.bucketcache.combinedcache.enabled";
105112

106113
public static final String BUCKET_CACHE_WRITER_THREADS_KEY = "hbase.bucketcache.writer.threads";
107-
public static final String BUCKET_CACHE_WRITER_QUEUE_KEY =
114+
public static final String BUCKET_CACHE_WRITER_QUEUE_KEY =
108115
"hbase.bucketcache.writer.queuelength";
109116

110117
/**
@@ -149,6 +156,7 @@ private static enum ExternalBlockCaches {
149156
memcached("org.apache.hadoop.hbase.io.hfile.MemcachedBlockCache");
150157
// TODO(eclark): Consider more. Redis, etc.
151158
Class<? extends BlockCache> clazz;
159+
@SuppressWarnings("unchecked")
152160
ExternalBlockCaches(String clazzName) {
153161
try {
154162
clazz = (Class<? extends BlockCache>) Class.forName(clazzName);
@@ -443,7 +451,9 @@ public boolean shouldCacheDataCompressed() {
443451
* @return true if this {@link BlockCategory} should be compressed in blockcache, false otherwise
444452
*/
445453
public boolean shouldCacheCompressed(BlockCategory category) {
446-
if (!isBlockCacheEnabled()) return false;
454+
if (!isBlockCacheEnabled()) {
455+
return false;
456+
}
447457
switch (category) {
448458
case DATA:
449459
return this.cacheDataCompressed;
@@ -525,28 +535,62 @@ public String toString() {
525535
// Clear this if in tests you'd make more than one block cache instance.
526536
@VisibleForTesting
527537
static BlockCache GLOBAL_BLOCK_CACHE_INSTANCE;
528-
private static LruBlockCache GLOBAL_L1_CACHE_INSTANCE = null;
529-
private static BlockCache GLOBAL_L2_CACHE_INSTANCE = null;
538+
private static FirstLevelBlockCache GLOBAL_L1_CACHE_INSTANCE;
539+
private static BlockCache GLOBAL_L2_CACHE_INSTANCE;
540+
private static ForkJoinPool GLOBAL_FORKJOIN_POOL;
530541

531542
/** Boolean whether we have disabled the block cache entirely. */
532543
@VisibleForTesting
533544
static boolean blockCacheDisabled = false;
534545

535546
/**
536-
* @param c Configuration to use.
537-
* @return An L1 instance. Currently an instance of LruBlockCache.
547+
* @param c Configuration to use
548+
* @return An L1 instance
549+
*/
550+
public static FirstLevelBlockCache getL1(final Configuration c) {
551+
long xmx = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax();
552+
long l1CacheSize = HeapMemorySizeUtil.getFirstLevelCacheSize(c, xmx);
553+
return getL1(l1CacheSize, c);
554+
}
555+
556+
/**
557+
* @param c Configuration to use
558+
* @param xmx Max heap memory
559+
* @return An L1 instance
538560
*/
539-
private static synchronized LruBlockCache getL1(final Configuration c) {
561+
562+
private synchronized static FirstLevelBlockCache getL1(long cacheSize, Configuration c) {
540563
if (GLOBAL_L1_CACHE_INSTANCE != null) return GLOBAL_L1_CACHE_INSTANCE;
541-
final long lruCacheSize = HeapMemorySizeUtil.getLruCacheSize(c);
542-
if (lruCacheSize < 0) {
543-
blockCacheDisabled = true;
564+
if (cacheSize < 0) {
565+
return null;
544566
}
545-
if (blockCacheDisabled) return null;
567+
String policy = c.get(HFILE_BLOCK_CACHE_POLICY_KEY, HFILE_BLOCK_CACHE_POLICY_DEFAULT);
546568
int blockSize = c.getInt(BLOCKCACHE_BLOCKSIZE_KEY, HConstants.DEFAULT_BLOCKSIZE);
547-
LOG.info("Allocating LruBlockCache size=" +
548-
StringUtils.byteDesc(lruCacheSize) + ", blockSize=" + StringUtils.byteDesc(blockSize));
549-
GLOBAL_L1_CACHE_INSTANCE = new LruBlockCache(lruCacheSize, blockSize, true, c);
569+
LOG.info("Allocating BlockCache size=" +
570+
StringUtils.byteDesc(cacheSize) + ", blockSize=" + StringUtils.byteDesc(blockSize));
571+
if (policy.equalsIgnoreCase("LRU")) {
572+
GLOBAL_L1_CACHE_INSTANCE = new LruBlockCache(cacheSize, blockSize, true, c);
573+
} else if (policy.equalsIgnoreCase("TinyLFU")) {
574+
if (GLOBAL_FORKJOIN_POOL == null) {
575+
GLOBAL_FORKJOIN_POOL = new ForkJoinPool();
576+
}
577+
Class<?> tinyLFUClass;
578+
try {
579+
tinyLFUClass = Class.forName("org.apache.hadoop.hbase.io.hfile.TinyLfuBlockCache");
580+
GLOBAL_L1_CACHE_INSTANCE = (FirstLevelBlockCache)
581+
tinyLFUClass.getDeclaredConstructor(long.class, long.class, Executor.class,
582+
Configuration.class)
583+
.newInstance(cacheSize, blockSize, GLOBAL_FORKJOIN_POOL, c);
584+
} catch (Exception e) {
585+
throw new RuntimeException(
586+
"Unable to instantiate the TinyLfuBlockCache block cache policy." +
587+
"If you want to use TinyLFU you must build with JDK8+, run with JRE8+, and have both " +
588+
"the hbase-tinylfu-blockcache module and its dependency the caffiene library " +
589+
"installed into the classpath.", e);
590+
}
591+
} else {
592+
throw new IllegalArgumentException("Unknown block cache policy " + policy);
593+
}
550594
return GLOBAL_L1_CACHE_INSTANCE;
551595
}
552596

@@ -587,7 +631,7 @@ public CacheStats getL2Stats() {
587631
}
588632

589633
private static BlockCache getExternalBlockcache(Configuration c) {
590-
Class klass = null;
634+
Class<?> klass = null;
591635

592636
// Get the class, from the config. s
593637
try {
@@ -615,7 +659,9 @@ private static BlockCache getExternalBlockcache(Configuration c) {
615659
private static BlockCache getBucketCache(Configuration c) {
616660
// Check for L2. ioengine name must be non-null.
617661
String bucketCacheIOEngineName = c.get(BUCKET_CACHE_IOENGINE_KEY, null);
618-
if (bucketCacheIOEngineName == null || bucketCacheIOEngineName.length() <= 0) return null;
662+
if (bucketCacheIOEngineName == null || bucketCacheIOEngineName.length() <= 0) {
663+
return null;
664+
}
619665

620666
int blockSize = c.getInt(BLOCKCACHE_BLOCKSIZE_KEY, HConstants.DEFAULT_BLOCKSIZE);
621667
final long bucketCacheSize = HeapMemorySizeUtil.getBucketCacheSize(c);
@@ -673,28 +719,30 @@ private static BlockCache getBucketCache(Configuration c) {
673719
* @return The block cache or <code>null</code>.
674720
*/
675721
public static synchronized BlockCache instantiateBlockCache(Configuration conf) {
676-
if (GLOBAL_BLOCK_CACHE_INSTANCE != null) return GLOBAL_BLOCK_CACHE_INSTANCE;
677-
if (blockCacheDisabled) return null;
678-
LruBlockCache l1 = getL1(conf);
679-
// blockCacheDisabled is set as a side-effect of getL1Internal(), so check it again after the call.
680-
if (blockCacheDisabled) return null;
722+
if (GLOBAL_BLOCK_CACHE_INSTANCE != null) {
723+
return GLOBAL_BLOCK_CACHE_INSTANCE;
724+
}
725+
if (blockCacheDisabled) {
726+
return null;
727+
}
728+
FirstLevelBlockCache l1 = getL1(conf);
681729
BlockCache l2 = getL2(conf);
682730
if (l2 == null) {
683731
GLOBAL_BLOCK_CACHE_INSTANCE = l1;
684732
} else {
685733
boolean useExternal = conf.getBoolean(EXTERNAL_BLOCKCACHE_KEY, EXTERNAL_BLOCKCACHE_DEFAULT);
686-
boolean combinedWithLru = conf.getBoolean(BUCKET_CACHE_COMBINED_KEY,
734+
boolean combinedWithL1 = conf.getBoolean(BUCKET_CACHE_COMBINED_KEY,
687735
DEFAULT_BUCKET_CACHE_COMBINED);
688736
if (useExternal) {
689737
GLOBAL_BLOCK_CACHE_INSTANCE = new InclusiveCombinedBlockCache(l1, l2);
690738
} else {
691-
if (combinedWithLru) {
739+
if (combinedWithL1) {
692740
GLOBAL_BLOCK_CACHE_INSTANCE = new CombinedBlockCache(l1, l2);
693741
} else {
694-
// L1 and L2 are not 'combined'. They are connected via the LruBlockCache victimhandler
695-
// mechanism. It is a little ugly but works according to the following: when the
696-
// background eviction thread runs, blocks evicted from L1 will go to L2 AND when we get
697-
// a block from the L1 cache, if not in L1, we will search L2.
742+
// L1 and L2 are not 'combined'. They are connected via the FirstLevelBlockCache
743+
// victimhandler mechanism. It is a little ugly but works according to the following:
744+
// when the background eviction thread runs, blocks evicted from L1 will go to L2 AND when
745+
// we get a block from the L1 cache, if not in L1, we will search L2.
698746
GLOBAL_BLOCK_CACHE_INSTANCE = l1;
699747
}
700748
}

0 commit comments

Comments
 (0)