001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.io.hfile; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertFalse; 022import static org.junit.Assert.assertTrue; 023import static org.junit.Assert.fail; 024 025import java.io.IOException; 026import java.lang.management.ManagementFactory; 027import java.lang.management.MemoryUsage; 028import java.nio.ByteBuffer; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.fs.FileSystem; 031import org.apache.hadoop.fs.Path; 032import org.apache.hadoop.hbase.HBaseClassTestRule; 033import org.apache.hadoop.hbase.HBaseConfiguration; 034import org.apache.hadoop.hbase.HBaseTestingUtility; 035import org.apache.hadoop.hbase.HColumnDescriptor; 036import org.apache.hadoop.hbase.HConstants; 037import org.apache.hadoop.hbase.io.ByteBuffAllocator; 038import org.apache.hadoop.hbase.io.hfile.BlockType.BlockCategory; 039import org.apache.hadoop.hbase.io.hfile.bucket.BucketCache; 040import org.apache.hadoop.hbase.io.util.MemorySizeUtil; 041import org.apache.hadoop.hbase.nio.ByteBuff; 042import org.apache.hadoop.hbase.testclassification.IOTests; 043import org.apache.hadoop.hbase.testclassification.MediumTests; 044import org.apache.hadoop.hbase.util.Threads; 045import org.junit.Before; 046import org.junit.ClassRule; 047import org.junit.Test; 048import org.junit.experimental.categories.Category; 049import org.slf4j.Logger; 050import org.slf4j.LoggerFactory; 051 052/** 053 * Tests that {@link CacheConfig} does as expected. 054 */ 055// This test is marked as a large test though it runs in a short amount of time 056// (seconds). It is large because it depends on being able to reset the global 057// blockcache instance which is in a global variable. Experience has it that 058// tests clash on the global variable if this test is run as small sized test. 059@Category({ IOTests.class, MediumTests.class }) 060public class TestCacheConfig { 061 062 @ClassRule 063 public static final HBaseClassTestRule CLASS_RULE = 064 HBaseClassTestRule.forClass(TestCacheConfig.class); 065 066 private static final Logger LOG = LoggerFactory.getLogger(TestCacheConfig.class); 067 private Configuration conf; 068 069 static class Deserializer implements CacheableDeserializer<Cacheable> { 070 private final Cacheable cacheable; 071 private int deserializedIdentifier = 0; 072 073 Deserializer(final Cacheable c) { 074 deserializedIdentifier = CacheableDeserializerIdManager.registerDeserializer(this); 075 this.cacheable = c; 076 } 077 078 @Override 079 public int getDeserializerIdentifier() { 080 return deserializedIdentifier; 081 } 082 083 @Override 084 public Cacheable deserialize(ByteBuff b, ByteBuffAllocator alloc) throws IOException { 085 LOG.info("Deserialized " + b); 086 return cacheable; 087 } 088 }; 089 090 static class IndexCacheEntry extends DataCacheEntry { 091 private static IndexCacheEntry SINGLETON = new IndexCacheEntry(); 092 093 public IndexCacheEntry() { 094 super(SINGLETON); 095 } 096 097 @Override 098 public BlockType getBlockType() { 099 return BlockType.ROOT_INDEX; 100 } 101 } 102 103 static class DataCacheEntry implements Cacheable { 104 private static final int SIZE = 1; 105 private static DataCacheEntry SINGLETON = new DataCacheEntry(); 106 final CacheableDeserializer<Cacheable> deserializer; 107 108 DataCacheEntry() { 109 this(SINGLETON); 110 } 111 112 DataCacheEntry(final Cacheable c) { 113 this.deserializer = new Deserializer(c); 114 } 115 116 @Override 117 public String toString() { 118 return "size=" + SIZE + ", type=" + getBlockType(); 119 }; 120 121 @Override 122 public long heapSize() { 123 return SIZE; 124 } 125 126 @Override 127 public int getSerializedLength() { 128 return SIZE; 129 } 130 131 @Override 132 public void serialize(ByteBuffer destination, boolean includeNextBlockMetadata) { 133 LOG.info("Serialized " + this + " to " + destination); 134 } 135 136 @Override 137 public CacheableDeserializer<Cacheable> getDeserializer() { 138 return this.deserializer; 139 } 140 141 @Override 142 public BlockType getBlockType() { 143 return BlockType.DATA; 144 } 145 } 146 147 static class MetaCacheEntry extends DataCacheEntry { 148 @Override 149 public BlockType getBlockType() { 150 return BlockType.INTERMEDIATE_INDEX; 151 } 152 } 153 154 @Before 155 public void setUp() throws Exception { 156 this.conf = HBaseConfiguration.create(); 157 } 158 159 /** 160 * @param bc The block cache instance. 161 * @param cc Cache config. 162 * @param doubling If true, addition of element ups counter by 2, not 1, because element added to 163 * onheap and offheap caches. 164 * @param sizing True if we should run sizing test (doesn't always apply). 165 */ 166 void basicBlockCacheOps(final BlockCache bc, final CacheConfig cc, final boolean doubling, 167 final boolean sizing) { 168 assertTrue(CacheConfig.DEFAULT_IN_MEMORY == cc.isInMemory()); 169 BlockCacheKey bck = new BlockCacheKey("f", 0); 170 Cacheable c = new DataCacheEntry(); 171 // Do asserts on block counting. 172 long initialBlockCount = bc.getBlockCount(); 173 bc.cacheBlock(bck, c, cc.isInMemory()); 174 assertEquals(doubling ? 2 : 1, bc.getBlockCount() - initialBlockCount); 175 bc.evictBlock(bck); 176 assertEquals(initialBlockCount, bc.getBlockCount()); 177 // Do size accounting. Do it after the above 'warm-up' because it looks like some 178 // buffers do lazy allocation so sizes are off on first go around. 179 if (sizing) { 180 long originalSize = bc.getCurrentSize(); 181 bc.cacheBlock(bck, c, cc.isInMemory()); 182 assertTrue(bc.getCurrentSize() > originalSize); 183 bc.evictBlock(bck); 184 long size = bc.getCurrentSize(); 185 assertEquals(originalSize, size); 186 } 187 } 188 189 @Test 190 public void testDisableCacheDataBlock() throws IOException { 191 Configuration conf = HBaseConfiguration.create(); 192 CacheConfig cacheConfig = new CacheConfig(conf); 193 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.DATA)); 194 assertFalse(cacheConfig.shouldCacheCompressed(BlockCategory.DATA)); 195 assertFalse(cacheConfig.shouldCacheDataCompressed()); 196 assertFalse(cacheConfig.shouldCacheDataOnWrite()); 197 assertTrue(cacheConfig.shouldCacheDataOnRead()); 198 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.INDEX)); 199 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.META)); 200 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.BLOOM)); 201 assertFalse(cacheConfig.shouldCacheBloomsOnWrite()); 202 assertFalse(cacheConfig.shouldCacheIndexesOnWrite()); 203 204 conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, true); 205 conf.setBoolean(CacheConfig.CACHE_DATA_BLOCKS_COMPRESSED_KEY, true); 206 conf.setBoolean(CacheConfig.CACHE_BLOOM_BLOCKS_ON_WRITE_KEY, true); 207 conf.setBoolean(CacheConfig.CACHE_INDEX_BLOCKS_ON_WRITE_KEY, true); 208 209 cacheConfig = new CacheConfig(conf); 210 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.DATA)); 211 assertTrue(cacheConfig.shouldCacheCompressed(BlockCategory.DATA)); 212 assertTrue(cacheConfig.shouldCacheDataCompressed()); 213 assertTrue(cacheConfig.shouldCacheDataOnWrite()); 214 assertTrue(cacheConfig.shouldCacheDataOnRead()); 215 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.INDEX)); 216 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.META)); 217 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.BLOOM)); 218 assertTrue(cacheConfig.shouldCacheBloomsOnWrite()); 219 assertTrue(cacheConfig.shouldCacheIndexesOnWrite()); 220 221 conf.setBoolean(CacheConfig.CACHE_DATA_ON_READ_KEY, false); 222 conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, false); 223 224 cacheConfig = new CacheConfig(conf); 225 assertFalse(cacheConfig.shouldCacheBlockOnRead(BlockCategory.DATA)); 226 assertFalse(cacheConfig.shouldCacheCompressed(BlockCategory.DATA)); 227 assertFalse(cacheConfig.shouldCacheDataCompressed()); 228 assertFalse(cacheConfig.shouldCacheDataOnWrite()); 229 assertFalse(cacheConfig.shouldCacheDataOnRead()); 230 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.INDEX)); 231 assertFalse(cacheConfig.shouldCacheBlockOnRead(BlockCategory.META)); 232 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.BLOOM)); 233 assertTrue(cacheConfig.shouldCacheBloomsOnWrite()); 234 assertTrue(cacheConfig.shouldCacheIndexesOnWrite()); 235 236 conf.setBoolean(CacheConfig.CACHE_DATA_ON_READ_KEY, true); 237 conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, false); 238 239 HColumnDescriptor family = new HColumnDescriptor("testDisableCacheDataBlock"); 240 family.setBlockCacheEnabled(false); 241 242 cacheConfig = new CacheConfig(conf, family, null, ByteBuffAllocator.HEAP); 243 assertFalse(cacheConfig.shouldCacheBlockOnRead(BlockCategory.DATA)); 244 assertFalse(cacheConfig.shouldCacheCompressed(BlockCategory.DATA)); 245 assertFalse(cacheConfig.shouldCacheDataCompressed()); 246 assertFalse(cacheConfig.shouldCacheDataOnWrite()); 247 assertFalse(cacheConfig.shouldCacheDataOnRead()); 248 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.INDEX)); 249 assertFalse(cacheConfig.shouldCacheBlockOnRead(BlockCategory.META)); 250 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.BLOOM)); 251 assertTrue(cacheConfig.shouldCacheBloomsOnWrite()); 252 assertTrue(cacheConfig.shouldCacheIndexesOnWrite()); 253 } 254 255 @Test 256 public void testCacheConfigDefaultLRUBlockCache() { 257 CacheConfig cc = new CacheConfig(this.conf); 258 assertTrue(CacheConfig.DEFAULT_IN_MEMORY == cc.isInMemory()); 259 BlockCache blockCache = BlockCacheFactory.createBlockCache(this.conf); 260 basicBlockCacheOps(blockCache, cc, false, true); 261 assertTrue(blockCache instanceof LruBlockCache); 262 } 263 264 /** 265 * Assert that the caches are deployed with CombinedBlockCache and of the appropriate sizes. 266 */ 267 @Test 268 public void testOffHeapBucketCacheConfig() { 269 this.conf.set(HConstants.BUCKET_CACHE_IOENGINE_KEY, "offheap"); 270 doBucketCacheConfigTest(); 271 } 272 273 @Test 274 public void testFileBucketCacheConfig() throws IOException { 275 HBaseTestingUtility htu = new HBaseTestingUtility(this.conf); 276 try { 277 Path p = new Path(htu.getDataTestDir(), "bc.txt"); 278 FileSystem fs = FileSystem.get(this.conf); 279 fs.create(p).close(); 280 this.conf.set(HConstants.BUCKET_CACHE_IOENGINE_KEY, "file:" + p); 281 doBucketCacheConfigTest(); 282 } finally { 283 htu.cleanupTestDir(); 284 } 285 } 286 287 private void doBucketCacheConfigTest() { 288 final int bcSize = 100; 289 this.conf.setInt(HConstants.BUCKET_CACHE_SIZE_KEY, bcSize); 290 CacheConfig cc = new CacheConfig(this.conf); 291 BlockCache blockCache = BlockCacheFactory.createBlockCache(this.conf); 292 basicBlockCacheOps(blockCache, cc, false, false); 293 assertTrue(blockCache instanceof CombinedBlockCache); 294 // TODO: Assert sizes allocated are right and proportions. 295 CombinedBlockCache cbc = (CombinedBlockCache) blockCache; 296 BlockCache[] bcs = cbc.getBlockCaches(); 297 assertTrue(bcs[0] instanceof LruBlockCache); 298 LruBlockCache lbc = (LruBlockCache) bcs[0]; 299 assertEquals(MemorySizeUtil.getOnHeapCacheSize(this.conf), lbc.getMaxSize()); 300 assertTrue(bcs[1] instanceof BucketCache); 301 BucketCache bc = (BucketCache) bcs[1]; 302 // getMaxSize comes back in bytes but we specified size in MB 303 assertEquals(bcSize, bc.getMaxSize() / (1024 * 1024)); 304 } 305 306 /** 307 * Assert that when BUCKET_CACHE_COMBINED_KEY is false, the non-default, that we deploy 308 * LruBlockCache as L1 with a BucketCache for L2. 309 */ 310 @Test 311 public void testBucketCacheConfigL1L2Setup() { 312 this.conf.set(HConstants.BUCKET_CACHE_IOENGINE_KEY, "offheap"); 313 // Make lru size is smaller than bcSize for sure. Need this to be true so when eviction 314 // from L1 happens, it does not fail because L2 can't take the eviction because block too big. 315 this.conf.setFloat(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY, 0.001f); 316 MemoryUsage mu = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); 317 long lruExpectedSize = MemorySizeUtil.getOnHeapCacheSize(this.conf); 318 final int bcSize = 100; 319 long bcExpectedSize = 100 * 1024 * 1024; // MB. 320 assertTrue(lruExpectedSize < bcExpectedSize); 321 this.conf.setInt(HConstants.BUCKET_CACHE_SIZE_KEY, bcSize); 322 CacheConfig cc = new CacheConfig(this.conf); 323 BlockCache blockCache = BlockCacheFactory.createBlockCache(this.conf); 324 basicBlockCacheOps(blockCache, cc, false, false); 325 assertTrue(blockCache instanceof CombinedBlockCache); 326 // TODO: Assert sizes allocated are right and proportions. 327 CombinedBlockCache cbc = (CombinedBlockCache) blockCache; 328 FirstLevelBlockCache lbc = cbc.l1Cache; 329 assertEquals(lruExpectedSize, lbc.getMaxSize()); 330 BlockCache bc = cbc.l2Cache; 331 // getMaxSize comes back in bytes but we specified size in MB 332 assertEquals(bcExpectedSize, ((BucketCache) bc).getMaxSize()); 333 // Test the L1+L2 deploy works as we'd expect with blocks evicted from L1 going to L2. 334 long initialL1BlockCount = lbc.getBlockCount(); 335 long initialL2BlockCount = bc.getBlockCount(); 336 Cacheable c = new DataCacheEntry(); 337 BlockCacheKey bck = new BlockCacheKey("bck", 0); 338 lbc.cacheBlock(bck, c, false); 339 assertEquals(initialL1BlockCount + 1, lbc.getBlockCount()); 340 assertEquals(initialL2BlockCount, bc.getBlockCount()); 341 // Force evictions by putting in a block too big. 342 final long justTooBigSize = ((LruBlockCache) lbc).acceptableSize() + 1; 343 lbc.cacheBlock(new BlockCacheKey("bck2", 0), new DataCacheEntry() { 344 @Override 345 public long heapSize() { 346 return justTooBigSize; 347 } 348 349 @Override 350 public int getSerializedLength() { 351 return (int) heapSize(); 352 } 353 }); 354 // The eviction thread in lrublockcache needs to run. 355 while (initialL1BlockCount != lbc.getBlockCount()) 356 Threads.sleep(10); 357 assertEquals(initialL1BlockCount, lbc.getBlockCount()); 358 } 359 360 @Test 361 public void testL2CacheWithInvalidBucketSize() { 362 Configuration c = new Configuration(this.conf); 363 c.set(HConstants.BUCKET_CACHE_IOENGINE_KEY, "offheap"); 364 c.set(BlockCacheFactory.BUCKET_CACHE_BUCKETS_KEY, "256,512,1024,2048,4000,4096"); 365 c.setFloat(HConstants.BUCKET_CACHE_SIZE_KEY, 1024); 366 try { 367 BlockCacheFactory.createBlockCache(c); 368 fail("Should throw IllegalArgumentException when passing illegal value for bucket size"); 369 } catch (IllegalArgumentException e) { 370 } 371 } 372 373 @Test 374 public void testIndexOnlyLruBlockCache() { 375 CacheConfig cc = new CacheConfig(this.conf); 376 conf.set(BlockCacheFactory.BLOCKCACHE_POLICY_KEY, "IndexOnlyLRU"); 377 BlockCache blockCache = BlockCacheFactory.createBlockCache(this.conf); 378 assertTrue(blockCache instanceof IndexOnlyLruBlockCache); 379 // reject data block 380 long initialBlockCount = blockCache.getBlockCount(); 381 BlockCacheKey bck = new BlockCacheKey("bck", 0); 382 Cacheable c = new DataCacheEntry(); 383 blockCache.cacheBlock(bck, c, true); 384 // accept index block 385 Cacheable indexCacheEntry = new IndexCacheEntry(); 386 blockCache.cacheBlock(bck, indexCacheEntry, true); 387 assertEquals(initialBlockCount + 1, blockCache.getBlockCount()); 388 } 389 390 @Test 391 public void testGetOnHeapCacheSize() { 392 Configuration copyConf = new Configuration(conf); 393 long fixedSize = 1024 * 1024L; 394 long onHeapCacheSize = MemorySizeUtil.getOnHeapCacheSize(copyConf); 395 assertEquals(null, copyConf.get(HConstants.HFILE_ONHEAP_BLOCK_CACHE_FIXED_SIZE_KEY)); 396 assertTrue(onHeapCacheSize > 0 && onHeapCacheSize != fixedSize); 397 // when HBASE_BLOCK_CACHE_FIXED_SIZE_KEY is set, it will be a fixed size 398 copyConf.setLong(HConstants.HFILE_ONHEAP_BLOCK_CACHE_FIXED_SIZE_KEY, fixedSize); 399 onHeapCacheSize = MemorySizeUtil.getOnHeapCacheSize(copyConf); 400 assertEquals(fixedSize, onHeapCacheSize); 401 } 402}