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