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