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.apache.hadoop.hbase.io.ByteBuffAllocator.HEAP; 021import static org.junit.jupiter.api.Assertions.assertEquals; 022import static org.junit.jupiter.api.Assertions.assertFalse; 023import static org.junit.jupiter.api.Assertions.assertNotNull; 024import static org.junit.jupiter.api.Assertions.assertNull; 025import static org.junit.jupiter.api.Assertions.assertTrue; 026 027import java.nio.ByteBuffer; 028import java.util.Random; 029import java.util.concurrent.ExecutorService; 030import java.util.concurrent.Executors; 031import java.util.concurrent.ThreadLocalRandom; 032import java.util.concurrent.TimeUnit; 033import java.util.concurrent.atomic.AtomicBoolean; 034import java.util.concurrent.atomic.AtomicInteger; 035import org.apache.hadoop.conf.Configuration; 036import org.apache.hadoop.hbase.HBaseConfiguration; 037import org.apache.hadoop.hbase.HConstants; 038import org.apache.hadoop.hbase.Waiter; 039import org.apache.hadoop.hbase.Waiter.ExplainingPredicate; 040import org.apache.hadoop.hbase.io.HeapSize; 041import org.apache.hadoop.hbase.io.hfile.LruBlockCache.EvictionThread; 042import org.apache.hadoop.hbase.nio.ByteBuff; 043import org.apache.hadoop.hbase.testclassification.IOTests; 044import org.apache.hadoop.hbase.testclassification.SmallTests; 045import org.apache.hadoop.hbase.util.ClassSize; 046import org.junit.jupiter.api.Tag; 047import org.junit.jupiter.api.Test; 048import org.slf4j.Logger; 049import org.slf4j.LoggerFactory; 050 051/** 052 * Tests the concurrent LruBlockCache. 053 * <p> 054 * Tests will ensure it grows and shrinks in size properly, evictions run when they're supposed to 055 * and do what they should, and that cached blocks are accessible when expected to be. 056 */ 057@Tag(IOTests.TAG) 058@Tag(SmallTests.TAG) 059public class TestLruBlockCache { 060 061 private static final Logger LOG = LoggerFactory.getLogger(TestLruBlockCache.class); 062 063 private static final Configuration CONF = HBaseConfiguration.create(); 064 065 @Test 066 public void testCacheEvictionThreadSafe() throws Exception { 067 long maxSize = 100000; 068 int numBlocks = 9; 069 int testRuns = 10; 070 final long blockSize = calculateBlockSizeDefault(maxSize, numBlocks); 071 assertTrue(blockSize * numBlocks <= maxSize, "calculateBlockSize appears broken."); 072 073 final LruBlockCache cache = new LruBlockCache(maxSize, blockSize); 074 EvictionThread evictionThread = cache.getEvictionThread(); 075 assertTrue(evictionThread != null); 076 Waiter.waitFor(CONF, 10000, 100, () -> evictionThread.isEnteringRun()); 077 final String hfileName = "hfile"; 078 int threads = 10; 079 final int blocksPerThread = 5 * numBlocks; 080 for (int run = 0; run != testRuns; ++run) { 081 final AtomicInteger blockCount = new AtomicInteger(0); 082 ExecutorService service = Executors.newFixedThreadPool(threads); 083 for (int i = 0; i != threads; ++i) { 084 service.execute(new Runnable() { 085 @Override 086 public void run() { 087 for (int blockIndex = 0; blockIndex < blocksPerThread 088 || (!cache.isEvictionInProgress()); ++blockIndex) { 089 CachedItem block = 090 new CachedItem(hfileName, (int) blockSize, blockCount.getAndIncrement()); 091 boolean inMemory = Math.random() > 0.5; 092 cache.cacheBlock(block.cacheKey, block, inMemory); 093 } 094 cache.evictBlocksByHfileName(hfileName); 095 } 096 }); 097 } 098 service.shutdown(); 099 // The test may fail here if the evict thread frees the blocks too fast 100 service.awaitTermination(10, TimeUnit.MINUTES); 101 Waiter.waitFor(CONF, 10000, 100, new ExplainingPredicate<Exception>() { 102 @Override 103 public boolean evaluate() throws Exception { 104 return cache.getBlockCount() == 0; 105 } 106 107 @Override 108 public String explainFailure() throws Exception { 109 return "Cache block count failed to return to 0"; 110 } 111 }); 112 assertEquals(0, cache.getBlockCount()); 113 assertEquals(cache.getOverhead(), cache.getCurrentSize()); 114 } 115 } 116 117 @Test 118 public void testBackgroundEvictionThread() throws Exception { 119 long maxSize = 100000; 120 int numBlocks = 9; 121 long blockSize = calculateBlockSizeDefault(maxSize, numBlocks); 122 assertTrue(blockSize * numBlocks <= maxSize, "calculateBlockSize appears broken."); 123 124 LruBlockCache cache = new LruBlockCache(maxSize, blockSize); 125 EvictionThread evictionThread = cache.getEvictionThread(); 126 assertTrue(evictionThread != null); 127 128 CachedItem[] blocks = generateFixedBlocks(numBlocks + 1, blockSize, "block"); 129 130 // Make sure eviction thread has entered run method 131 Waiter.waitFor(CONF, 10000, 10, () -> evictionThread.isEnteringRun()); 132 133 // Add all the blocks 134 for (CachedItem block : blocks) { 135 cache.cacheBlock(block.cacheKey, block); 136 } 137 138 // wait until at least one eviction has run 139 Waiter.waitFor(CONF, 30000, 200, new ExplainingPredicate<Exception>() { 140 141 @Override 142 public boolean evaluate() throws Exception { 143 return cache.getStats().getEvictionCount() > 0; 144 } 145 146 @Override 147 public String explainFailure() throws Exception { 148 return "Eviction never happened."; 149 } 150 }); 151 152 // let cache stabilize 153 // On some systems, the cache will run multiple evictions before it attains 154 // steady-state. For instance, after populating the cache with 10 blocks, 155 // the first eviction evicts a single block and then a second eviction 156 // evicts another. I think this is due to the delta between minSize and 157 // acceptableSize, combined with variance between object overhead on 158 // different environments. 159 int n = 0; 160 for (long prevCnt = 0 /* < number of blocks added */, curCnt = cache.getBlockCount(); prevCnt 161 != curCnt; prevCnt = curCnt, curCnt = cache.getBlockCount()) { 162 Thread.sleep(200); 163 assertTrue(n++ < 100, "Cache never stabilized."); 164 } 165 166 long evictionCount = cache.getStats().getEvictionCount(); 167 assertTrue(evictionCount >= 1); 168 LOG.info("Background Evictions run: {}", evictionCount); 169 } 170 171 @Test 172 public void testCacheSimple() throws Exception { 173 long maxSize = 1000000; 174 long blockSize = calculateBlockSizeDefault(maxSize, 101); 175 176 LruBlockCache cache = new LruBlockCache(maxSize, blockSize); 177 178 CachedItem[] blocks = generateRandomBlocks(100, blockSize); 179 180 long expectedCacheSize = cache.heapSize(); 181 182 // Confirm empty 183 for (CachedItem block : blocks) { 184 assertTrue(cache.getBlock(block.cacheKey, true, false, true) == null); 185 } 186 187 // Add blocks 188 for (CachedItem block : blocks) { 189 cache.cacheBlock(block.cacheKey, block); 190 expectedCacheSize += block.cacheBlockHeapSize(); 191 } 192 193 // Verify correctly calculated cache heap size 194 assertEquals(expectedCacheSize, cache.heapSize()); 195 196 // Check if all blocks are properly cached and retrieved 197 for (CachedItem block : blocks) { 198 HeapSize buf = cache.getBlock(block.cacheKey, true, false, true); 199 assertTrue(buf != null); 200 assertEquals(buf.heapSize(), block.heapSize()); 201 } 202 203 // Re-add same blocks and ensure nothing has changed 204 long expectedBlockCount = cache.getBlockCount(); 205 for (CachedItem block : blocks) { 206 cache.cacheBlock(block.cacheKey, block); 207 } 208 assertEquals(expectedBlockCount, cache.getBlockCount(), 209 "Cache should ignore cache requests for blocks already in cache"); 210 211 // Verify correctly calculated cache heap size 212 assertEquals(expectedCacheSize, cache.heapSize()); 213 214 // Check if all blocks are properly cached and retrieved 215 for (CachedItem block : blocks) { 216 HeapSize buf = cache.getBlock(block.cacheKey, true, false, true); 217 assertTrue(buf != null); 218 assertEquals(buf.heapSize(), block.heapSize()); 219 } 220 221 CacheTestUtils.testConvertToJSON(cache); 222 223 // Expect no evictions 224 assertEquals(0, cache.getStats().getEvictionCount()); 225 Thread t = new LruBlockCache.StatisticsThread(cache); 226 t.start(); 227 t.join(); 228 } 229 230 @Test 231 public void testCacheEvictionSimple() throws Exception { 232 long maxSize = 100000; 233 long blockSize = calculateBlockSizeDefault(maxSize, 10); 234 235 LruBlockCache cache = new LruBlockCache(maxSize, blockSize, false); 236 237 CachedItem[] blocks = generateFixedBlocks(10, blockSize, "block"); 238 239 long expectedCacheSize = cache.heapSize(); 240 241 // Add all the blocks 242 for (CachedItem block : blocks) { 243 cache.cacheBlock(block.cacheKey, block); 244 expectedCacheSize += block.cacheBlockHeapSize(); 245 } 246 247 // A single eviction run should have occurred 248 assertEquals(1, cache.getStats().getEvictionCount()); 249 250 // Our expected size overruns acceptable limit 251 assertTrue(expectedCacheSize > (maxSize * LruBlockCache.DEFAULT_ACCEPTABLE_FACTOR)); 252 253 // But the cache did not grow beyond max 254 assertTrue(cache.heapSize() < maxSize); 255 256 // And is still below the acceptable limit 257 assertTrue(cache.heapSize() < (maxSize * LruBlockCache.DEFAULT_ACCEPTABLE_FACTOR)); 258 259 // All blocks except block 0 should be in the cache 260 assertTrue(cache.getBlock(blocks[0].cacheKey, true, false, true) == null); 261 for (int i = 1; i < blocks.length; i++) { 262 assertEquals(cache.getBlock(blocks[i].cacheKey, true, false, true), blocks[i]); 263 } 264 } 265 266 @Test 267 public void testCacheEvictionTwoPriorities() throws Exception { 268 long maxSize = 100000; 269 long blockSize = calculateBlockSizeDefault(maxSize, 10); 270 271 LruBlockCache cache = new LruBlockCache(maxSize, blockSize, false); 272 273 CachedItem[] singleBlocks = generateFixedBlocks(5, 10000, "single"); 274 CachedItem[] multiBlocks = generateFixedBlocks(5, 10000, "multi"); 275 276 long expectedCacheSize = cache.heapSize(); 277 278 // Add and get the multi blocks 279 for (CachedItem block : multiBlocks) { 280 cache.cacheBlock(block.cacheKey, block); 281 expectedCacheSize += block.cacheBlockHeapSize(); 282 assertEquals(cache.getBlock(block.cacheKey, true, false, true), block); 283 } 284 285 // Add the single blocks (no get) 286 for (CachedItem block : singleBlocks) { 287 cache.cacheBlock(block.cacheKey, block); 288 expectedCacheSize += block.heapSize(); 289 } 290 291 // A single eviction run should have occurred 292 assertEquals(1, cache.getStats().getEvictionCount()); 293 294 // We expect two entries evicted 295 assertEquals(2, cache.getStats().getEvictedCount()); 296 297 // Our expected size overruns acceptable limit 298 assertTrue(expectedCacheSize > (maxSize * LruBlockCache.DEFAULT_ACCEPTABLE_FACTOR)); 299 300 // But the cache did not grow beyond max 301 assertTrue(cache.heapSize() <= maxSize); 302 303 // And is now below the acceptable limit 304 assertTrue(cache.heapSize() <= (maxSize * LruBlockCache.DEFAULT_ACCEPTABLE_FACTOR)); 305 306 // We expect fairness across the two priorities. 307 // This test makes multi go barely over its limit, in-memory 308 // empty, and the rest in single. Two single evictions and 309 // one multi eviction expected. 310 assertTrue(cache.getBlock(singleBlocks[0].cacheKey, true, false, true) == null); 311 assertTrue(cache.getBlock(multiBlocks[0].cacheKey, true, false, true) == null); 312 313 // And all others to be cached 314 for (int i = 1; i < 4; i++) { 315 assertEquals(cache.getBlock(singleBlocks[i].cacheKey, true, false, true), singleBlocks[i]); 316 assertEquals(cache.getBlock(multiBlocks[i].cacheKey, true, false, true), multiBlocks[i]); 317 } 318 } 319 320 @Test 321 public void testCacheEvictionThreePriorities() throws Exception { 322 long maxSize = 100000; 323 long blockSize = calculateBlockSize(maxSize, 10); 324 325 LruBlockCache cache = 326 new LruBlockCache(maxSize, blockSize, false, (int) Math.ceil(1.2 * maxSize / blockSize), 327 LruBlockCache.DEFAULT_LOAD_FACTOR, LruBlockCache.DEFAULT_CONCURRENCY_LEVEL, 0.98f, // min 328 0.99f, // acceptable 329 0.33f, // single 330 0.33f, // multi 331 0.34f, // memory 332 1.2f, // limit 333 false, 16 * 1024 * 1024); 334 335 CachedItem[] singleBlocks = generateFixedBlocks(5, blockSize, "single"); 336 CachedItem[] multiBlocks = generateFixedBlocks(5, blockSize, "multi"); 337 CachedItem[] memoryBlocks = generateFixedBlocks(5, blockSize, "memory"); 338 339 long expectedCacheSize = cache.heapSize(); 340 341 // Add 3 blocks from each priority 342 for (int i = 0; i < 3; i++) { 343 344 // Just add single blocks 345 cache.cacheBlock(singleBlocks[i].cacheKey, singleBlocks[i]); 346 expectedCacheSize += singleBlocks[i].cacheBlockHeapSize(); 347 348 // Add and get multi blocks 349 cache.cacheBlock(multiBlocks[i].cacheKey, multiBlocks[i]); 350 expectedCacheSize += multiBlocks[i].cacheBlockHeapSize(); 351 cache.getBlock(multiBlocks[i].cacheKey, true, false, true); 352 353 // Add memory blocks as such 354 cache.cacheBlock(memoryBlocks[i].cacheKey, memoryBlocks[i], true); 355 expectedCacheSize += memoryBlocks[i].cacheBlockHeapSize(); 356 357 } 358 359 // Do not expect any evictions yet 360 assertEquals(0, cache.getStats().getEvictionCount()); 361 362 // Verify cache size 363 assertEquals(expectedCacheSize, cache.heapSize()); 364 365 // Insert a single block, oldest single should be evicted 366 cache.cacheBlock(singleBlocks[3].cacheKey, singleBlocks[3]); 367 368 // Single eviction, one thing evicted 369 assertEquals(1, cache.getStats().getEvictionCount()); 370 assertEquals(1, cache.getStats().getEvictedCount()); 371 372 // Verify oldest single block is the one evicted 373 assertEquals(null, cache.getBlock(singleBlocks[0].cacheKey, true, false, true)); 374 375 // Change the oldest remaining single block to a multi 376 cache.getBlock(singleBlocks[1].cacheKey, true, false, true); 377 378 // Insert another single block 379 cache.cacheBlock(singleBlocks[4].cacheKey, singleBlocks[4]); 380 381 // Two evictions, two evicted. 382 assertEquals(2, cache.getStats().getEvictionCount()); 383 assertEquals(2, cache.getStats().getEvictedCount()); 384 385 // Oldest multi block should be evicted now 386 assertEquals(null, cache.getBlock(multiBlocks[0].cacheKey, true, false, true)); 387 388 // Insert another memory block 389 cache.cacheBlock(memoryBlocks[3].cacheKey, memoryBlocks[3], true); 390 391 // Three evictions, three evicted. 392 assertEquals(3, cache.getStats().getEvictionCount()); 393 assertEquals(3, cache.getStats().getEvictedCount()); 394 395 // Oldest memory block should be evicted now 396 assertEquals(null, cache.getBlock(memoryBlocks[0].cacheKey, true, false, true)); 397 398 // Add a block that is twice as big (should force two evictions) 399 CachedItem[] bigBlocks = generateFixedBlocks(3, blockSize * 3, "big"); 400 cache.cacheBlock(bigBlocks[0].cacheKey, bigBlocks[0]); 401 402 // Four evictions, six evicted (inserted block 3X size, expect +3 evicted) 403 assertEquals(4, cache.getStats().getEvictionCount()); 404 assertEquals(6, cache.getStats().getEvictedCount()); 405 406 // Expect three remaining singles to be evicted 407 assertEquals(null, cache.getBlock(singleBlocks[2].cacheKey, true, false, true)); 408 assertEquals(null, cache.getBlock(singleBlocks[3].cacheKey, true, false, true)); 409 assertEquals(null, cache.getBlock(singleBlocks[4].cacheKey, true, false, true)); 410 411 // Make the big block a multi block 412 cache.getBlock(bigBlocks[0].cacheKey, true, false, true); 413 414 // Cache another single big block 415 cache.cacheBlock(bigBlocks[1].cacheKey, bigBlocks[1]); 416 417 // Five evictions, nine evicted (3 new) 418 assertEquals(5, cache.getStats().getEvictionCount()); 419 assertEquals(9, cache.getStats().getEvictedCount()); 420 421 // Expect three remaining multis to be evicted 422 assertEquals(null, cache.getBlock(singleBlocks[1].cacheKey, true, false, true)); 423 assertEquals(null, cache.getBlock(multiBlocks[1].cacheKey, true, false, true)); 424 assertEquals(null, cache.getBlock(multiBlocks[2].cacheKey, true, false, true)); 425 426 // Cache a big memory block 427 cache.cacheBlock(bigBlocks[2].cacheKey, bigBlocks[2], true); 428 429 // Six evictions, twelve evicted (3 new) 430 assertEquals(6, cache.getStats().getEvictionCount()); 431 assertEquals(12, cache.getStats().getEvictedCount()); 432 433 // Expect three remaining in-memory to be evicted 434 assertEquals(null, cache.getBlock(memoryBlocks[1].cacheKey, true, false, true)); 435 assertEquals(null, cache.getBlock(memoryBlocks[2].cacheKey, true, false, true)); 436 assertEquals(null, cache.getBlock(memoryBlocks[3].cacheKey, true, false, true)); 437 } 438 439 @Test 440 public void testCacheEvictionInMemoryForceMode() throws Exception { 441 long maxSize = 100000; 442 long blockSize = calculateBlockSize(maxSize, 10); 443 444 LruBlockCache cache = 445 new LruBlockCache(maxSize, blockSize, false, (int) Math.ceil(1.2 * maxSize / blockSize), 446 LruBlockCache.DEFAULT_LOAD_FACTOR, LruBlockCache.DEFAULT_CONCURRENCY_LEVEL, 0.98f, // min 447 0.99f, // acceptable 448 0.2f, // single 449 0.3f, // multi 450 0.5f, // memory 451 1.2f, // limit 452 true, 16 * 1024 * 1024); 453 454 CachedItem[] singleBlocks = generateFixedBlocks(10, blockSize, "single"); 455 CachedItem[] multiBlocks = generateFixedBlocks(10, blockSize, "multi"); 456 CachedItem[] memoryBlocks = generateFixedBlocks(10, blockSize, "memory"); 457 458 long expectedCacheSize = cache.heapSize(); 459 460 // 0. Add 5 single blocks and 4 multi blocks to make cache full, si:mu:me = 5:4:0 461 for (int i = 0; i < 4; i++) { 462 // Just add single blocks 463 cache.cacheBlock(singleBlocks[i].cacheKey, singleBlocks[i]); 464 expectedCacheSize += singleBlocks[i].cacheBlockHeapSize(); 465 // Add and get multi blocks 466 cache.cacheBlock(multiBlocks[i].cacheKey, multiBlocks[i]); 467 expectedCacheSize += multiBlocks[i].cacheBlockHeapSize(); 468 cache.getBlock(multiBlocks[i].cacheKey, true, false, true); 469 } 470 // 5th single block 471 cache.cacheBlock(singleBlocks[4].cacheKey, singleBlocks[4]); 472 expectedCacheSize += singleBlocks[4].cacheBlockHeapSize(); 473 // Do not expect any evictions yet 474 assertEquals(0, cache.getStats().getEvictionCount()); 475 // Verify cache size 476 assertEquals(expectedCacheSize, cache.heapSize()); 477 478 // 1. Insert a memory block, oldest single should be evicted, si:mu:me = 4:4:1 479 cache.cacheBlock(memoryBlocks[0].cacheKey, memoryBlocks[0], true); 480 // Single eviction, one block evicted 481 assertEquals(1, cache.getStats().getEvictionCount()); 482 assertEquals(1, cache.getStats().getEvictedCount()); 483 // Verify oldest single block (index = 0) is the one evicted 484 assertEquals(null, cache.getBlock(singleBlocks[0].cacheKey, true, false, true)); 485 486 // 2. Insert another memory block, another single evicted, si:mu:me = 3:4:2 487 cache.cacheBlock(memoryBlocks[1].cacheKey, memoryBlocks[1], true); 488 // Two evictions, two evicted. 489 assertEquals(2, cache.getStats().getEvictionCount()); 490 assertEquals(2, cache.getStats().getEvictedCount()); 491 // Current oldest single block (index = 1) should be evicted now 492 assertEquals(null, cache.getBlock(singleBlocks[1].cacheKey, true, false, true)); 493 494 // 3. Insert 4 memory blocks, 2 single and 2 multi evicted, si:mu:me = 1:2:6 495 cache.cacheBlock(memoryBlocks[2].cacheKey, memoryBlocks[2], true); 496 cache.cacheBlock(memoryBlocks[3].cacheKey, memoryBlocks[3], true); 497 cache.cacheBlock(memoryBlocks[4].cacheKey, memoryBlocks[4], true); 498 cache.cacheBlock(memoryBlocks[5].cacheKey, memoryBlocks[5], true); 499 // Three evictions, three evicted. 500 assertEquals(6, cache.getStats().getEvictionCount()); 501 assertEquals(6, cache.getStats().getEvictedCount()); 502 // two oldest single blocks and two oldest multi blocks evicted 503 assertEquals(null, cache.getBlock(singleBlocks[2].cacheKey, true, false, true)); 504 assertEquals(null, cache.getBlock(singleBlocks[3].cacheKey, true, false, true)); 505 assertEquals(null, cache.getBlock(multiBlocks[0].cacheKey, true, false, true)); 506 assertEquals(null, cache.getBlock(multiBlocks[1].cacheKey, true, false, true)); 507 508 // 4. Insert 3 memory blocks, the remaining 1 single and 2 multi evicted 509 // si:mu:me = 0:0:9 510 cache.cacheBlock(memoryBlocks[6].cacheKey, memoryBlocks[6], true); 511 cache.cacheBlock(memoryBlocks[7].cacheKey, memoryBlocks[7], true); 512 cache.cacheBlock(memoryBlocks[8].cacheKey, memoryBlocks[8], true); 513 // Three evictions, three evicted. 514 assertEquals(9, cache.getStats().getEvictionCount()); 515 assertEquals(9, cache.getStats().getEvictedCount()); 516 // one oldest single block and two oldest multi blocks evicted 517 assertEquals(null, cache.getBlock(singleBlocks[4].cacheKey, true, false, true)); 518 assertEquals(null, cache.getBlock(multiBlocks[2].cacheKey, true, false, true)); 519 assertEquals(null, cache.getBlock(multiBlocks[3].cacheKey, true, false, true)); 520 521 // 5. Insert one memory block, the oldest memory evicted 522 // si:mu:me = 0:0:9 523 cache.cacheBlock(memoryBlocks[9].cacheKey, memoryBlocks[9], true); 524 // one eviction, one evicted. 525 assertEquals(10, cache.getStats().getEvictionCount()); 526 assertEquals(10, cache.getStats().getEvictedCount()); 527 // oldest memory block evicted 528 assertEquals(null, cache.getBlock(memoryBlocks[0].cacheKey, true, false, true)); 529 530 // 6. Insert one new single block, itself evicted immediately since 531 // all blocks in cache are memory-type which have higher priority 532 // si:mu:me = 0:0:9 (no change) 533 cache.cacheBlock(singleBlocks[9].cacheKey, singleBlocks[9]); 534 // one eviction, one evicted. 535 assertEquals(11, cache.getStats().getEvictionCount()); 536 assertEquals(11, cache.getStats().getEvictedCount()); 537 // the single block just cached now evicted (can't evict memory) 538 assertEquals(null, cache.getBlock(singleBlocks[9].cacheKey, true, false, true)); 539 } 540 541 // test scan resistance 542 @Test 543 public void testScanResistance() throws Exception { 544 545 long maxSize = 100000; 546 long blockSize = calculateBlockSize(maxSize, 10); 547 548 LruBlockCache cache = 549 new LruBlockCache(maxSize, blockSize, false, (int) Math.ceil(1.2 * maxSize / blockSize), 550 LruBlockCache.DEFAULT_LOAD_FACTOR, LruBlockCache.DEFAULT_CONCURRENCY_LEVEL, 0.66f, // min 551 0.99f, // acceptable 552 0.33f, // single 553 0.33f, // multi 554 0.34f, // memory 555 1.2f, // limit 556 false, 16 * 1024 * 1024); 557 558 CachedItem[] singleBlocks = generateFixedBlocks(20, blockSize, "single"); 559 CachedItem[] multiBlocks = generateFixedBlocks(5, blockSize, "multi"); 560 561 // Add 5 multi blocks 562 for (CachedItem block : multiBlocks) { 563 cache.cacheBlock(block.cacheKey, block); 564 cache.getBlock(block.cacheKey, true, false, true); 565 } 566 567 // Add 5 single blocks 568 for (int i = 0; i < 5; i++) { 569 cache.cacheBlock(singleBlocks[i].cacheKey, singleBlocks[i]); 570 } 571 572 // An eviction ran 573 assertEquals(1, cache.getStats().getEvictionCount()); 574 575 // To drop down to 2/3 capacity, we'll need to evict 4 blocks 576 assertEquals(4, cache.getStats().getEvictedCount()); 577 578 // Should have been taken off equally from single and multi 579 assertEquals(null, cache.getBlock(singleBlocks[0].cacheKey, true, false, true)); 580 assertEquals(null, cache.getBlock(singleBlocks[1].cacheKey, true, false, true)); 581 assertEquals(null, cache.getBlock(multiBlocks[0].cacheKey, true, false, true)); 582 assertEquals(null, cache.getBlock(multiBlocks[1].cacheKey, true, false, true)); 583 584 // Let's keep "scanning" by adding single blocks. From here on we only 585 // expect evictions from the single bucket. 586 587 // Every time we reach 10 total blocks (every 4 inserts) we get 4 single 588 // blocks evicted. Inserting 13 blocks should yield 3 more evictions and 589 // 12 more evicted. 590 591 for (int i = 5; i < 18; i++) { 592 cache.cacheBlock(singleBlocks[i].cacheKey, singleBlocks[i]); 593 } 594 595 // 4 total evictions, 16 total evicted 596 assertEquals(4, cache.getStats().getEvictionCount()); 597 assertEquals(16, cache.getStats().getEvictedCount()); 598 599 // Should now have 7 total blocks 600 assertEquals(7, cache.getBlockCount()); 601 602 } 603 604 @Test 605 public void testMaxBlockSize() throws Exception { 606 long maxSize = 100000; 607 long blockSize = calculateBlockSize(maxSize, 10); 608 609 LruBlockCache cache = 610 new LruBlockCache(maxSize, blockSize, false, (int) Math.ceil(1.2 * maxSize / blockSize), 611 LruBlockCache.DEFAULT_LOAD_FACTOR, LruBlockCache.DEFAULT_CONCURRENCY_LEVEL, 0.66f, // min 612 0.99f, // acceptable 613 0.33f, // single 614 0.33f, // multi 615 0.34f, // memory 616 1.2f, // limit 617 false, 1024); 618 CachedItem[] tooLong = generateFixedBlocks(10, 1024 + 5, "long"); 619 CachedItem[] small = generateFixedBlocks(15, 600, "small"); 620 621 for (CachedItem i : tooLong) { 622 cache.cacheBlock(i.cacheKey, i); 623 } 624 for (CachedItem i : small) { 625 cache.cacheBlock(i.cacheKey, i); 626 } 627 assertEquals(15, cache.getBlockCount()); 628 for (CachedItem i : small) { 629 assertNotNull(cache.getBlock(i.cacheKey, true, false, false)); 630 } 631 for (CachedItem i : tooLong) { 632 assertNull(cache.getBlock(i.cacheKey, true, false, false)); 633 } 634 635 assertEquals(10, cache.getStats().getFailedInserts()); 636 } 637 638 // test setMaxSize 639 @Test 640 public void testResizeBlockCache() throws Exception { 641 long maxSize = 300000; 642 long blockSize = calculateBlockSize(maxSize, 31); 643 644 LruBlockCache cache = 645 new LruBlockCache(maxSize, blockSize, false, (int) Math.ceil(1.2 * maxSize / blockSize), 646 LruBlockCache.DEFAULT_LOAD_FACTOR, LruBlockCache.DEFAULT_CONCURRENCY_LEVEL, 0.98f, // min 647 0.99f, // acceptable 648 0.33f, // single 649 0.33f, // multi 650 0.34f, // memory 651 1.2f, // limit 652 false, 16 * 1024 * 1024); 653 654 CachedItem[] singleBlocks = generateFixedBlocks(10, blockSize, "single"); 655 CachedItem[] multiBlocks = generateFixedBlocks(10, blockSize, "multi"); 656 CachedItem[] memoryBlocks = generateFixedBlocks(10, blockSize, "memory"); 657 658 // Add all blocks from all priorities 659 for (int i = 0; i < 10; i++) { 660 // Just add single blocks 661 cache.cacheBlock(singleBlocks[i].cacheKey, singleBlocks[i]); 662 663 // Add and get multi blocks 664 cache.cacheBlock(multiBlocks[i].cacheKey, multiBlocks[i]); 665 cache.getBlock(multiBlocks[i].cacheKey, true, false, true); 666 667 // Add memory blocks as such 668 cache.cacheBlock(memoryBlocks[i].cacheKey, memoryBlocks[i], true); 669 } 670 671 // Do not expect any evictions yet 672 assertEquals(0, cache.getStats().getEvictionCount()); 673 674 // Resize to half capacity plus an extra block (otherwise we evict an extra) 675 cache.setMaxSize((long) (maxSize * 0.5f)); 676 677 // Should have run a single eviction 678 assertEquals(1, cache.getStats().getEvictionCount()); 679 680 // And we expect 1/2 of the blocks to be evicted 681 assertEquals(15, cache.getStats().getEvictedCount()); 682 683 // And the oldest 5 blocks from each category should be gone 684 for (int i = 0; i < 5; i++) { 685 assertEquals(null, cache.getBlock(singleBlocks[i].cacheKey, true, false, true)); 686 assertEquals(null, cache.getBlock(multiBlocks[i].cacheKey, true, false, true)); 687 assertEquals(null, cache.getBlock(memoryBlocks[i].cacheKey, true, false, true)); 688 } 689 690 // And the newest 5 blocks should still be accessible 691 for (int i = 5; i < 10; i++) { 692 assertEquals(singleBlocks[i], cache.getBlock(singleBlocks[i].cacheKey, true, false, true)); 693 assertEquals(multiBlocks[i], cache.getBlock(multiBlocks[i].cacheKey, true, false, true)); 694 assertEquals(memoryBlocks[i], cache.getBlock(memoryBlocks[i].cacheKey, true, false, true)); 695 } 696 } 697 698 // test metricsPastNPeriods 699 @Test 700 public void testPastNPeriodsMetrics() throws Exception { 701 double delta = 0.01; 702 703 // 3 total periods 704 CacheStats stats = new CacheStats("test", 3); 705 706 // No accesses, should be 0 707 stats.rollMetricsPeriod(); 708 assertEquals(0.0, stats.getHitRatioPastNPeriods(), delta); 709 assertEquals(0.0, stats.getHitCachingRatioPastNPeriods(), delta); 710 711 // period 1, 1 hit caching, 1 hit non-caching, 2 miss non-caching 712 // should be (2/4)=0.5 and (1/1)=1 713 stats.hit(false, true, BlockType.DATA); 714 stats.hit(true, true, BlockType.DATA); 715 stats.miss(false, false, BlockType.DATA); 716 stats.miss(false, false, BlockType.DATA); 717 stats.rollMetricsPeriod(); 718 assertEquals(0.5, stats.getHitRatioPastNPeriods(), delta); 719 assertEquals(1.0, stats.getHitCachingRatioPastNPeriods(), delta); 720 721 // period 2, 1 miss caching, 3 miss non-caching 722 // should be (2/8)=0.25 and (1/2)=0.5 723 stats.miss(true, false, BlockType.DATA); 724 stats.miss(false, false, BlockType.DATA); 725 stats.miss(false, false, BlockType.DATA); 726 stats.miss(false, false, BlockType.DATA); 727 stats.rollMetricsPeriod(); 728 assertEquals(0.25, stats.getHitRatioPastNPeriods(), delta); 729 assertEquals(0.5, stats.getHitCachingRatioPastNPeriods(), delta); 730 731 // period 3, 2 hits of each type 732 // should be (6/12)=0.5 and (3/4)=0.75 733 stats.hit(false, true, BlockType.DATA); 734 stats.hit(true, true, BlockType.DATA); 735 stats.hit(false, true, BlockType.DATA); 736 stats.hit(true, true, BlockType.DATA); 737 stats.rollMetricsPeriod(); 738 assertEquals(0.5, stats.getHitRatioPastNPeriods(), delta); 739 assertEquals(0.75, stats.getHitCachingRatioPastNPeriods(), delta); 740 741 // period 4, evict period 1, two caching misses 742 // should be (4/10)=0.4 and (2/5)=0.4 743 stats.miss(true, false, BlockType.DATA); 744 stats.miss(true, false, BlockType.DATA); 745 stats.rollMetricsPeriod(); 746 assertEquals(0.4, stats.getHitRatioPastNPeriods(), delta); 747 assertEquals(0.4, stats.getHitCachingRatioPastNPeriods(), delta); 748 749 // period 5, evict period 2, 2 caching misses, 2 non-caching hit 750 // should be (6/10)=0.6 and (2/6)=1/3 751 stats.miss(true, false, BlockType.DATA); 752 stats.miss(true, false, BlockType.DATA); 753 stats.hit(false, true, BlockType.DATA); 754 stats.hit(false, true, BlockType.DATA); 755 stats.rollMetricsPeriod(); 756 assertEquals(0.6, stats.getHitRatioPastNPeriods(), delta); 757 assertEquals((double) 1 / 3, stats.getHitCachingRatioPastNPeriods(), delta); 758 759 // period 6, evict period 3 760 // should be (2/6)=1/3 and (0/4)=0 761 stats.rollMetricsPeriod(); 762 assertEquals((double) 1 / 3, stats.getHitRatioPastNPeriods(), delta); 763 assertEquals(0.0, stats.getHitCachingRatioPastNPeriods(), delta); 764 765 // period 7, evict period 4 766 // should be (2/4)=0.5 and (0/2)=0 767 stats.rollMetricsPeriod(); 768 assertEquals(0.5, stats.getHitRatioPastNPeriods(), delta); 769 assertEquals(0.0, stats.getHitCachingRatioPastNPeriods(), delta); 770 771 // period 8, evict period 5 772 // should be 0 and 0 773 stats.rollMetricsPeriod(); 774 assertEquals(0.0, stats.getHitRatioPastNPeriods(), delta); 775 assertEquals(0.0, stats.getHitCachingRatioPastNPeriods(), delta); 776 777 // period 9, one of each 778 // should be (2/4)=0.5 and (1/2)=0.5 779 stats.miss(true, false, BlockType.DATA); 780 stats.miss(false, false, BlockType.DATA); 781 stats.hit(true, true, BlockType.DATA); 782 stats.hit(false, true, BlockType.DATA); 783 stats.rollMetricsPeriod(); 784 assertEquals(0.5, stats.getHitRatioPastNPeriods(), delta); 785 assertEquals(0.5, stats.getHitCachingRatioPastNPeriods(), delta); 786 } 787 788 @Test 789 public void testCacheBlockNextBlockMetadataMissing() { 790 long maxSize = 100000; 791 long blockSize = calculateBlockSize(maxSize, 10); 792 int size = 100; 793 int length = HConstants.HFILEBLOCK_HEADER_SIZE + size; 794 byte[] byteArr = new byte[length]; 795 ByteBuffer buf = ByteBuffer.wrap(byteArr, 0, size); 796 HFileContext meta = new HFileContextBuilder().build(); 797 HFileBlock blockWithNextBlockMetadata = new HFileBlock(BlockType.DATA, size, size, -1, 798 ByteBuff.wrap(buf), HFileBlock.FILL_HEADER, -1, 52, -1, meta, HEAP); 799 HFileBlock blockWithoutNextBlockMetadata = new HFileBlock(BlockType.DATA, size, size, -1, 800 ByteBuff.wrap(buf), HFileBlock.FILL_HEADER, -1, -1, -1, meta, HEAP); 801 802 LruBlockCache cache = 803 new LruBlockCache(maxSize, blockSize, false, (int) Math.ceil(1.2 * maxSize / blockSize), 804 LruBlockCache.DEFAULT_LOAD_FACTOR, LruBlockCache.DEFAULT_CONCURRENCY_LEVEL, 0.66f, // min 805 0.99f, // acceptable 806 0.33f, // single 807 0.33f, // multi 808 0.34f, // memory 809 1.2f, // limit 810 false, 1024); 811 812 BlockCacheKey key = new BlockCacheKey("key1", 0); 813 ByteBuffer actualBuffer = ByteBuffer.allocate(length); 814 ByteBuffer block1Buffer = ByteBuffer.allocate(length); 815 ByteBuffer block2Buffer = ByteBuffer.allocate(length); 816 blockWithNextBlockMetadata.serialize(block1Buffer, true); 817 blockWithoutNextBlockMetadata.serialize(block2Buffer, true); 818 819 // Add blockWithNextBlockMetadata, expect blockWithNextBlockMetadata back. 820 CacheTestUtils.getBlockAndAssertEquals(cache, key, blockWithNextBlockMetadata, actualBuffer, 821 block1Buffer); 822 823 // Add blockWithoutNextBlockMetada, expect blockWithNextBlockMetadata back. 824 CacheTestUtils.getBlockAndAssertEquals(cache, key, blockWithoutNextBlockMetadata, actualBuffer, 825 block1Buffer); 826 827 // Clear and add blockWithoutNextBlockMetadata 828 cache.clearCache(); 829 assertNull(cache.getBlock(key, false, false, false)); 830 CacheTestUtils.getBlockAndAssertEquals(cache, key, blockWithoutNextBlockMetadata, actualBuffer, 831 block2Buffer); 832 833 // Add blockWithNextBlockMetadata, expect blockWithNextBlockMetadata to replace. 834 CacheTestUtils.getBlockAndAssertEquals(cache, key, blockWithNextBlockMetadata, actualBuffer, 835 block1Buffer); 836 } 837 838 private CachedItem[] generateFixedBlocks(int numBlocks, int size, String pfx) { 839 CachedItem[] blocks = new CachedItem[numBlocks]; 840 for (int i = 0; i < numBlocks; i++) { 841 blocks[i] = new CachedItem(pfx + i, size); 842 } 843 return blocks; 844 } 845 846 private CachedItem[] generateFixedBlocks(int numBlocks, long size, String pfx) { 847 return generateFixedBlocks(numBlocks, (int) size, pfx); 848 } 849 850 private CachedItem[] generateRandomBlocks(int numBlocks, long maxSize) { 851 CachedItem[] blocks = new CachedItem[numBlocks]; 852 Random rand = ThreadLocalRandom.current(); 853 for (int i = 0; i < numBlocks; i++) { 854 blocks[i] = new CachedItem("block" + i, rand.nextInt((int) maxSize) + 1); 855 } 856 return blocks; 857 } 858 859 private long calculateBlockSize(long maxSize, int numBlocks) { 860 long roughBlockSize = maxSize / numBlocks; 861 int numEntries = (int) Math.ceil((1.2) * maxSize / roughBlockSize); 862 long totalOverhead = LruBlockCache.CACHE_FIXED_OVERHEAD + ClassSize.CONCURRENT_HASHMAP 863 + (numEntries * ClassSize.CONCURRENT_HASHMAP_ENTRY) 864 + (LruBlockCache.DEFAULT_CONCURRENCY_LEVEL * ClassSize.CONCURRENT_HASHMAP_SEGMENT); 865 long negateBlockSize = (long) (totalOverhead / numEntries); 866 negateBlockSize += LruCachedBlock.PER_BLOCK_OVERHEAD; 867 return ClassSize.align((long) Math.floor((roughBlockSize - negateBlockSize) * 0.99f)); 868 } 869 870 private long calculateBlockSizeDefault(long maxSize, int numBlocks) { 871 long roughBlockSize = maxSize / numBlocks; 872 int numEntries = (int) Math.ceil((1.2) * maxSize / roughBlockSize); 873 long totalOverhead = LruBlockCache.CACHE_FIXED_OVERHEAD + ClassSize.CONCURRENT_HASHMAP 874 + (numEntries * ClassSize.CONCURRENT_HASHMAP_ENTRY) 875 + (LruBlockCache.DEFAULT_CONCURRENCY_LEVEL * ClassSize.CONCURRENT_HASHMAP_SEGMENT); 876 long negateBlockSize = totalOverhead / numEntries; 877 negateBlockSize += LruCachedBlock.PER_BLOCK_OVERHEAD; 878 return ClassSize.align((long) Math 879 .floor((roughBlockSize - negateBlockSize) * LruBlockCache.DEFAULT_ACCEPTABLE_FACTOR)); 880 } 881 882 private static class CachedItem implements Cacheable { 883 BlockCacheKey cacheKey; 884 int size; 885 886 CachedItem(String blockName, int size, int offset) { 887 this.cacheKey = new BlockCacheKey(blockName, offset); 888 this.size = size; 889 } 890 891 CachedItem(String blockName, int size) { 892 this.cacheKey = new BlockCacheKey(blockName, 0); 893 this.size = size; 894 } 895 896 /** The size of this item reported to the block cache layer */ 897 @Override 898 public long heapSize() { 899 return ClassSize.align(size); 900 } 901 902 /** Size of the cache block holding this item. Used for verification. */ 903 public long cacheBlockHeapSize() { 904 return LruCachedBlock.PER_BLOCK_OVERHEAD + ClassSize.align(cacheKey.heapSize()) 905 + ClassSize.align(size); 906 } 907 908 @Override 909 public int getSerializedLength() { 910 return 0; 911 } 912 913 @Override 914 public CacheableDeserializer<Cacheable> getDeserializer() { 915 return null; 916 } 917 918 @Override 919 public void serialize(ByteBuffer destination, boolean includeNextBlockMetadata) { 920 } 921 922 @Override 923 public BlockType getBlockType() { 924 return BlockType.DATA; 925 } 926 } 927 928 static void testMultiThreadGetAndEvictBlockInternal(BlockCache cache) throws Exception { 929 int size = 100; 930 int length = HConstants.HFILEBLOCK_HEADER_SIZE + size; 931 byte[] byteArr = new byte[length]; 932 HFileContext meta = new HFileContextBuilder().build(); 933 BlockCacheKey key = new BlockCacheKey("key1", 0); 934 HFileBlock blk = new HFileBlock(BlockType.DATA, size, size, -1, 935 ByteBuff.wrap(ByteBuffer.wrap(byteArr, 0, size)), HFileBlock.FILL_HEADER, -1, 52, -1, meta, 936 HEAP); 937 AtomicBoolean err1 = new AtomicBoolean(false); 938 Thread t1 = new Thread(() -> { 939 for (int i = 0; i < 10000 && !err1.get(); i++) { 940 try { 941 cache.getBlock(key, false, false, true); 942 } catch (Exception e) { 943 err1.set(true); 944 LOG.info("Cache block or get block failure: ", e); 945 } 946 } 947 }); 948 949 AtomicBoolean err2 = new AtomicBoolean(false); 950 Thread t2 = new Thread(() -> { 951 for (int i = 0; i < 10000 && !err2.get(); i++) { 952 try { 953 cache.evictBlock(key); 954 } catch (Exception e) { 955 err2.set(true); 956 LOG.info("Evict block failure: ", e); 957 } 958 } 959 }); 960 961 AtomicBoolean err3 = new AtomicBoolean(false); 962 Thread t3 = new Thread(() -> { 963 for (int i = 0; i < 10000 && !err3.get(); i++) { 964 try { 965 cache.cacheBlock(key, blk); 966 } catch (Exception e) { 967 err3.set(true); 968 LOG.info("Cache block failure: ", e); 969 } 970 } 971 }); 972 t1.start(); 973 t2.start(); 974 t3.start(); 975 t1.join(); 976 t2.join(); 977 t3.join(); 978 assertFalse(err1.get()); 979 assertFalse(err2.get()); 980 assertFalse(err3.get()); 981 } 982 983 @Test 984 public void testMultiThreadGetAndEvictBlock() throws Exception { 985 long maxSize = 100000; 986 long blockSize = calculateBlockSize(maxSize, 10); 987 LruBlockCache cache = 988 new LruBlockCache(maxSize, blockSize, false, (int) Math.ceil(1.2 * maxSize / blockSize), 989 LruBlockCache.DEFAULT_LOAD_FACTOR, LruBlockCache.DEFAULT_CONCURRENCY_LEVEL, 0.66f, // min 990 0.99f, // acceptable 991 0.33f, // single 992 0.33f, // multi 993 0.34f, // memory 994 1.2f, // limit 995 false, 1024); 996 testMultiThreadGetAndEvictBlockInternal(cache); 997 } 998}