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