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 Configuration conf = HBaseConfiguration.create(); 194 CacheConfig cacheConfig = new CacheConfig(conf); 195 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.DATA)); 196 assertFalse(cacheConfig.shouldCacheCompressed(BlockCategory.DATA)); 197 assertFalse(cacheConfig.shouldCacheDataCompressed()); 198 assertFalse(cacheConfig.shouldCacheDataOnWrite()); 199 assertTrue(cacheConfig.shouldCacheDataOnRead()); 200 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.INDEX)); 201 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.META)); 202 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.BLOOM)); 203 assertFalse(cacheConfig.shouldCacheBloomsOnWrite()); 204 assertFalse(cacheConfig.shouldCacheIndexesOnWrite()); 205 206 conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, true); 207 conf.setBoolean(CacheConfig.CACHE_DATA_BLOCKS_COMPRESSED_KEY, true); 208 conf.setBoolean(CacheConfig.CACHE_BLOOM_BLOCKS_ON_WRITE_KEY, true); 209 conf.setBoolean(CacheConfig.CACHE_INDEX_BLOCKS_ON_WRITE_KEY, true); 210 211 cacheConfig = new CacheConfig(conf); 212 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.DATA)); 213 assertTrue(cacheConfig.shouldCacheCompressed(BlockCategory.DATA)); 214 assertTrue(cacheConfig.shouldCacheDataCompressed()); 215 assertTrue(cacheConfig.shouldCacheDataOnWrite()); 216 assertTrue(cacheConfig.shouldCacheDataOnRead()); 217 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.INDEX)); 218 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.META)); 219 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.BLOOM)); 220 assertTrue(cacheConfig.shouldCacheBloomsOnWrite()); 221 assertTrue(cacheConfig.shouldCacheIndexesOnWrite()); 222 223 conf.setBoolean(CacheConfig.CACHE_DATA_ON_READ_KEY, false); 224 conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, false); 225 226 cacheConfig = new CacheConfig(conf); 227 assertFalse(cacheConfig.shouldCacheBlockOnRead(BlockCategory.DATA)); 228 assertFalse(cacheConfig.shouldCacheCompressed(BlockCategory.DATA)); 229 assertFalse(cacheConfig.shouldCacheDataCompressed()); 230 assertFalse(cacheConfig.shouldCacheDataOnWrite()); 231 assertFalse(cacheConfig.shouldCacheDataOnRead()); 232 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.INDEX)); 233 assertFalse(cacheConfig.shouldCacheBlockOnRead(BlockCategory.META)); 234 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.BLOOM)); 235 assertTrue(cacheConfig.shouldCacheBloomsOnWrite()); 236 assertTrue(cacheConfig.shouldCacheIndexesOnWrite()); 237 238 conf.setBoolean(CacheConfig.CACHE_DATA_ON_READ_KEY, true); 239 conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, false); 240 241 ColumnFamilyDescriptor columnFamilyDescriptor = ColumnFamilyDescriptorBuilder 242 .newBuilder(Bytes.toBytes("testDisableCacheDataBlock")).setBlockCacheEnabled(false).build(); 243 244 cacheConfig = new CacheConfig(conf, columnFamilyDescriptor, null, ByteBuffAllocator.HEAP); 245 assertFalse(cacheConfig.shouldCacheBlockOnRead(BlockCategory.DATA)); 246 assertFalse(cacheConfig.shouldCacheCompressed(BlockCategory.DATA)); 247 assertFalse(cacheConfig.shouldCacheDataCompressed()); 248 assertFalse(cacheConfig.shouldCacheDataOnWrite()); 249 assertFalse(cacheConfig.shouldCacheDataOnRead()); 250 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.INDEX)); 251 assertFalse(cacheConfig.shouldCacheBlockOnRead(BlockCategory.META)); 252 assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.BLOOM)); 253 assertTrue(cacheConfig.shouldCacheBloomsOnWrite()); 254 assertTrue(cacheConfig.shouldCacheIndexesOnWrite()); 255 } 256 257 @Test 258 public void testCacheConfigDefaultLRUBlockCache() { 259 CacheConfig cc = new CacheConfig(this.conf); 260 assertTrue(CacheConfig.DEFAULT_IN_MEMORY == cc.isInMemory()); 261 BlockCache blockCache = BlockCacheFactory.createBlockCache(this.conf); 262 basicBlockCacheOps(blockCache, cc, false, true); 263 assertTrue(blockCache instanceof LruBlockCache); 264 } 265 266 /** 267 * Assert that the caches are deployed with CombinedBlockCache and of the appropriate sizes. 268 */ 269 @Test 270 public void testOffHeapBucketCacheConfig() { 271 this.conf.set(HConstants.BUCKET_CACHE_IOENGINE_KEY, "offheap"); 272 doBucketCacheConfigTest(); 273 } 274 275 @Test 276 public void testFileBucketCacheConfig() throws IOException { 277 HBaseTestingUtil htu = new HBaseTestingUtil(this.conf); 278 try { 279 Path p = new Path(htu.getDataTestDir(), "bc.txt"); 280 FileSystem fs = FileSystem.get(this.conf); 281 fs.create(p).close(); 282 this.conf.set(HConstants.BUCKET_CACHE_IOENGINE_KEY, "file:" + p); 283 doBucketCacheConfigTest(); 284 } finally { 285 htu.cleanupTestDir(); 286 } 287 } 288 289 private void doBucketCacheConfigTest() { 290 final int bcSize = 100; 291 this.conf.setInt(HConstants.BUCKET_CACHE_SIZE_KEY, bcSize); 292 CacheConfig cc = new CacheConfig(this.conf); 293 BlockCache blockCache = BlockCacheFactory.createBlockCache(this.conf); 294 basicBlockCacheOps(blockCache, cc, false, false); 295 assertTrue(blockCache instanceof CombinedBlockCache); 296 // TODO: Assert sizes allocated are right and proportions. 297 CombinedBlockCache cbc = (CombinedBlockCache) blockCache; 298 BlockCache[] bcs = cbc.getBlockCaches(); 299 assertTrue(bcs[0] instanceof LruBlockCache); 300 LruBlockCache lbc = (LruBlockCache) bcs[0]; 301 assertEquals(MemorySizeUtil.getOnHeapCacheSize(this.conf), lbc.getMaxSize()); 302 assertTrue(bcs[1] instanceof BucketCache); 303 BucketCache bc = (BucketCache) bcs[1]; 304 // getMaxSize comes back in bytes but we specified size in MB 305 assertEquals(bcSize, bc.getMaxSize() / (1024 * 1024)); 306 } 307 308 /** 309 * Assert that when BUCKET_CACHE_COMBINED_KEY is false, the non-default, that we deploy 310 * LruBlockCache as L1 with a BucketCache for L2. 311 */ 312 @Test 313 public void testBucketCacheConfigL1L2Setup() { 314 this.conf.set(HConstants.BUCKET_CACHE_IOENGINE_KEY, "offheap"); 315 // Make lru size is smaller than bcSize for sure. Need this to be true so when eviction 316 // from L1 happens, it does not fail because L2 can't take the eviction because block too big. 317 this.conf.setFloat(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY, 0.001f); 318 MemoryUsage mu = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); 319 long lruExpectedSize = MemorySizeUtil.getOnHeapCacheSize(this.conf); 320 final int bcSize = 100; 321 long bcExpectedSize = 100 * 1024 * 1024; // MB. 322 assertTrue(lruExpectedSize < bcExpectedSize); 323 this.conf.setInt(HConstants.BUCKET_CACHE_SIZE_KEY, bcSize); 324 CacheConfig cc = new CacheConfig(this.conf); 325 BlockCache blockCache = BlockCacheFactory.createBlockCache(this.conf); 326 basicBlockCacheOps(blockCache, cc, false, false); 327 assertTrue(blockCache instanceof CombinedBlockCache); 328 // TODO: Assert sizes allocated are right and proportions. 329 CombinedBlockCache cbc = (CombinedBlockCache) blockCache; 330 FirstLevelBlockCache lbc = cbc.l1Cache; 331 assertEquals(lruExpectedSize, lbc.getMaxSize()); 332 BlockCache bc = cbc.l2Cache; 333 // getMaxSize comes back in bytes but we specified size in MB 334 assertEquals(bcExpectedSize, ((BucketCache) bc).getMaxSize()); 335 // Test the L1+L2 deploy works as we'd expect with blocks evicted from L1 going to L2. 336 long initialL1BlockCount = lbc.getBlockCount(); 337 long initialL2BlockCount = bc.getBlockCount(); 338 Cacheable c = new DataCacheEntry(); 339 BlockCacheKey bck = new BlockCacheKey("bck", 0); 340 lbc.cacheBlock(bck, c, false); 341 assertEquals(initialL1BlockCount + 1, lbc.getBlockCount()); 342 assertEquals(initialL2BlockCount, bc.getBlockCount()); 343 // Force evictions by putting in a block too big. 344 final long justTooBigSize = ((LruBlockCache) lbc).acceptableSize() + 1; 345 lbc.cacheBlock(new BlockCacheKey("bck2", 0), new DataCacheEntry() { 346 @Override 347 public long heapSize() { 348 return justTooBigSize; 349 } 350 351 @Override 352 public int getSerializedLength() { 353 return (int) heapSize(); 354 } 355 }); 356 // The eviction thread in lrublockcache needs to run. 357 while (initialL1BlockCount != lbc.getBlockCount()) 358 Threads.sleep(10); 359 assertEquals(initialL1BlockCount, lbc.getBlockCount()); 360 } 361 362 @Test 363 public void testL2CacheWithInvalidBucketSize() { 364 Configuration c = new Configuration(this.conf); 365 c.set(HConstants.BUCKET_CACHE_IOENGINE_KEY, "offheap"); 366 c.set(BlockCacheFactory.BUCKET_CACHE_BUCKETS_KEY, "256,512,1024,2048,4000,4096"); 367 c.setFloat(HConstants.BUCKET_CACHE_SIZE_KEY, 1024); 368 try { 369 BlockCacheFactory.createBlockCache(c); 370 fail("Should throw IllegalArgumentException when passing illegal value for bucket size"); 371 } catch (IllegalArgumentException e) { 372 } 373 } 374 375 @Test 376 public void testIndexOnlyLruBlockCache() { 377 CacheConfig cc = new CacheConfig(this.conf); 378 conf.set(BlockCacheFactory.BLOCKCACHE_POLICY_KEY, "IndexOnlyLRU"); 379 BlockCache blockCache = BlockCacheFactory.createBlockCache(this.conf); 380 assertTrue(blockCache instanceof IndexOnlyLruBlockCache); 381 // reject data block 382 long initialBlockCount = blockCache.getBlockCount(); 383 BlockCacheKey bck = new BlockCacheKey("bck", 0); 384 Cacheable c = new DataCacheEntry(); 385 blockCache.cacheBlock(bck, c, true); 386 // accept index block 387 Cacheable indexCacheEntry = new IndexCacheEntry(); 388 blockCache.cacheBlock(bck, indexCacheEntry, true); 389 assertEquals(initialBlockCount + 1, blockCache.getBlockCount()); 390 } 391 392 @Test 393 public void testGetOnHeapCacheSize() { 394 Configuration copyConf = new Configuration(conf); 395 long fixedSize = 1024 * 1024L; 396 long onHeapCacheSize = MemorySizeUtil.getOnHeapCacheSize(copyConf); 397 assertEquals(null, copyConf.get(HConstants.HFILE_ONHEAP_BLOCK_CACHE_FIXED_SIZE_KEY)); 398 assertTrue(onHeapCacheSize > 0 && onHeapCacheSize != fixedSize); 399 // when HBASE_BLOCK_CACHE_FIXED_SIZE_KEY is set, it will be a fixed size 400 copyConf.setLong(HConstants.HFILE_ONHEAP_BLOCK_CACHE_FIXED_SIZE_KEY, fixedSize); 401 onHeapCacheSize = MemorySizeUtil.getOnHeapCacheSize(copyConf); 402 assertEquals(fixedSize, onHeapCacheSize); 403 } 404}