1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.hadoop.hbase.io.hfile;
21
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertTrue;
24
25 import java.nio.ByteBuffer;
26 import java.util.Collection;
27 import java.util.Map;
28 import java.util.Random;
29
30 import org.apache.hadoop.hbase.HBaseTestingUtility;
31 import org.apache.hadoop.hbase.MediumTests;
32 import org.apache.hadoop.hbase.io.HeapSize;
33 import org.apache.hadoop.hbase.io.hfile.LruBlockCache.EvictionThread;
34 import org.apache.hadoop.hbase.regionserver.metrics.SchemaMetrics;
35 import org.apache.hadoop.hbase.regionserver.metrics.TestSchemaMetrics;
36 import org.apache.hadoop.hbase.util.ClassSize;
37 import org.junit.After;
38 import org.junit.Before;
39 import org.junit.Test;
40 import org.junit.experimental.categories.Category;
41 import org.junit.runner.RunWith;
42 import org.junit.runners.Parameterized;
43 import org.junit.runners.Parameterized.Parameters;
44
45
46
47
48
49
50
51
52 @RunWith(Parameterized.class)
53 @Category(MediumTests.class)
54 public class TestLruBlockCache {
55
56 private Map<String, Long> startingMetrics;
57 private final HBaseTestingUtility TEST_UTIL =
58 new HBaseTestingUtility();
59
60 public TestLruBlockCache(boolean useTableName) {
61 SchemaMetrics.setUseTableNameInTest(useTableName);
62 }
63
64 @Parameters
65 public static Collection<Object[]> parameters() {
66 return TestSchemaMetrics.parameters();
67 }
68
69 @Before
70 public void setUp() throws Exception {
71 startingMetrics = SchemaMetrics.getMetricsSnapshot();
72 }
73
74 @After
75 public void tearDown() throws Exception {
76 SchemaMetrics.validateMetricChanges(startingMetrics);
77 }
78
79 @Test
80 public void testBackgroundEvictionThread() throws Exception {
81 long maxSize = 100000;
82 long blockSize = calculateBlockSizeDefault(maxSize, 9);
83
84 LruBlockCache cache = new LruBlockCache(maxSize, blockSize, TEST_UTIL.getConfiguration());
85
86 CachedItem [] blocks = generateFixedBlocks(10, blockSize, "block");
87
88 EvictionThread evictionThread = cache.getEvictionThread();
89 assertTrue(evictionThread != null);
90
91
92 while (!evictionThread.isEnteringRun()) {
93 Thread.sleep(1);
94 }
95
96
97 for (CachedItem block : blocks) {
98 cache.cacheBlock(block.cacheKey, block);
99 }
100
101
102 int n = 0;
103 while(cache.getEvictionCount() == 0) {
104 Thread.sleep(200);
105 assertTrue(n++ < 20);
106 }
107 System.out.println("Background Evictions run: " + cache.getEvictionCount());
108
109
110 assertEquals(cache.getEvictionCount(), 1);
111 }
112
113 @Test
114 public void testCacheSimple() throws Exception {
115
116 long maxSize = 1000000;
117 long blockSize = calculateBlockSizeDefault(maxSize, 101);
118
119 LruBlockCache cache = new LruBlockCache(maxSize, blockSize, TEST_UTIL.getConfiguration());
120
121 CachedItem [] blocks = generateRandomBlocks(100, blockSize);
122
123 long expectedCacheSize = cache.heapSize();
124
125
126 for (CachedItem block : blocks) {
127 assertTrue(cache.getBlock(block.cacheKey, true, false) == null);
128 }
129
130
131 for (CachedItem block : blocks) {
132 cache.cacheBlock(block.cacheKey, block);
133 expectedCacheSize += block.cacheBlockHeapSize();
134 }
135
136
137 assertEquals(expectedCacheSize, cache.heapSize());
138
139
140 for (CachedItem block : blocks) {
141 HeapSize buf = cache.getBlock(block.cacheKey, true, false);
142 assertTrue(buf != null);
143 assertEquals(buf.heapSize(), block.heapSize());
144 }
145
146
147 assertEquals(expectedCacheSize, cache.heapSize());
148
149
150 for (CachedItem block : blocks) {
151 HeapSize buf = cache.getBlock(block.cacheKey, true, false);
152 assertTrue(buf != null);
153 assertEquals(buf.heapSize(), block.heapSize());
154 }
155
156
157 assertEquals(0, cache.getEvictionCount());
158 Thread t = new LruBlockCache.StatisticsThread(cache);
159 t.start();
160 t.join();
161 }
162
163 @Test
164 public void testCacheEvictionSimple() throws Exception {
165
166 long maxSize = 100000;
167 long blockSize = calculateBlockSizeDefault(maxSize, 10);
168
169 LruBlockCache cache = new LruBlockCache(maxSize, blockSize, false, TEST_UTIL.getConfiguration());
170
171 CachedItem [] blocks = generateFixedBlocks(10, blockSize, "block");
172
173 long expectedCacheSize = cache.heapSize();
174
175
176 for (CachedItem block : blocks) {
177 cache.cacheBlock(block.cacheKey, block);
178 expectedCacheSize += block.cacheBlockHeapSize();
179 }
180
181
182 assertEquals(1, cache.getEvictionCount());
183
184
185 assertTrue(expectedCacheSize >
186 (maxSize * LruBlockCache.DEFAULT_ACCEPTABLE_FACTOR));
187
188
189 assertTrue(cache.heapSize() < maxSize);
190
191
192 assertTrue(cache.heapSize() <
193 (maxSize * LruBlockCache.DEFAULT_ACCEPTABLE_FACTOR));
194
195
196 assertTrue(cache.getBlock(blocks[0].cacheKey, true, false) == null);
197 assertTrue(cache.getBlock(blocks[1].cacheKey, true, false) == null);
198 for(int i=2;i<blocks.length;i++) {
199 assertEquals(cache.getBlock(blocks[i].cacheKey, true, false),
200 blocks[i]);
201 }
202 }
203
204 @Test
205 public void testCacheEvictionTwoPriorities() throws Exception {
206
207 long maxSize = 100000;
208 long blockSize = calculateBlockSizeDefault(maxSize, 10);
209
210 LruBlockCache cache = new LruBlockCache(maxSize, blockSize, false, TEST_UTIL.getConfiguration());
211
212 CachedItem [] singleBlocks = generateFixedBlocks(5, 10000, "single");
213 CachedItem [] multiBlocks = generateFixedBlocks(5, 10000, "multi");
214
215 long expectedCacheSize = cache.heapSize();
216
217
218 for (CachedItem block : multiBlocks) {
219 cache.cacheBlock(block.cacheKey, block);
220 expectedCacheSize += block.cacheBlockHeapSize();
221 assertEquals(cache.getBlock(block.cacheKey, true, false), block);
222 }
223
224
225 for (CachedItem block : singleBlocks) {
226 cache.cacheBlock(block.cacheKey, block);
227 expectedCacheSize += block.heapSize();
228 }
229
230
231 assertEquals(cache.getEvictionCount(), 1);
232
233
234 assertEquals(cache.getEvictedCount(), 2);
235
236
237 assertTrue(expectedCacheSize >
238 (maxSize * LruBlockCache.DEFAULT_ACCEPTABLE_FACTOR));
239
240
241 assertTrue(cache.heapSize() <= maxSize);
242
243
244 assertTrue(cache.heapSize() <=
245 (maxSize * LruBlockCache.DEFAULT_ACCEPTABLE_FACTOR));
246
247
248
249
250
251 assertTrue(cache.getBlock(singleBlocks[0].cacheKey, true, false) == null);
252 assertTrue(cache.getBlock(multiBlocks[0].cacheKey, true, false) == null);
253
254
255 for(int i=1;i<4;i++) {
256 assertEquals(cache.getBlock(singleBlocks[i].cacheKey, true, false),
257 singleBlocks[i]);
258 assertEquals(cache.getBlock(multiBlocks[i].cacheKey, true, false),
259 multiBlocks[i]);
260 }
261 }
262
263 @Test
264 public void testCacheEvictionThreePriorities() throws Exception {
265
266 long maxSize = 100000;
267 long blockSize = calculateBlockSize(maxSize, 10);
268
269 LruBlockCache cache = new LruBlockCache(maxSize, blockSize, false,
270 (int)Math.ceil(1.2*maxSize/blockSize),
271 LruBlockCache.DEFAULT_LOAD_FACTOR,
272 LruBlockCache.DEFAULT_CONCURRENCY_LEVEL,
273 0.98f,
274 0.99f,
275 0.33f,
276 0.33f,
277 0.34f);
278
279
280 CachedItem [] singleBlocks = generateFixedBlocks(5, blockSize, "single");
281 CachedItem [] multiBlocks = generateFixedBlocks(5, blockSize, "multi");
282 CachedItem [] memoryBlocks = generateFixedBlocks(5, blockSize, "memory");
283
284 long expectedCacheSize = cache.heapSize();
285
286
287 for(int i=0;i<3;i++) {
288
289
290 cache.cacheBlock(singleBlocks[i].cacheKey, singleBlocks[i]);
291 expectedCacheSize += singleBlocks[i].cacheBlockHeapSize();
292
293
294 cache.cacheBlock(multiBlocks[i].cacheKey, multiBlocks[i]);
295 expectedCacheSize += multiBlocks[i].cacheBlockHeapSize();
296 cache.getBlock(multiBlocks[i].cacheKey, true, false);
297
298
299 cache.cacheBlock(memoryBlocks[i].cacheKey, memoryBlocks[i], true);
300 expectedCacheSize += memoryBlocks[i].cacheBlockHeapSize();
301
302 }
303
304
305 assertEquals(0, cache.getEvictionCount());
306
307
308 assertEquals(expectedCacheSize, cache.heapSize());
309
310
311 cache.cacheBlock(singleBlocks[3].cacheKey, singleBlocks[3]);
312
313
314 assertEquals(1, cache.getEvictionCount());
315 assertEquals(1, cache.getEvictedCount());
316
317
318 assertEquals(null, cache.getBlock(singleBlocks[0].cacheKey, true, false));
319
320
321 cache.getBlock(singleBlocks[1].cacheKey, true, false);
322
323
324 cache.cacheBlock(singleBlocks[4].cacheKey, singleBlocks[4]);
325
326
327 assertEquals(2, cache.getEvictionCount());
328 assertEquals(2, cache.getEvictedCount());
329
330
331 assertEquals(null, cache.getBlock(multiBlocks[0].cacheKey, true, false));
332
333
334 cache.cacheBlock(memoryBlocks[3].cacheKey, memoryBlocks[3], true);
335
336
337 assertEquals(3, cache.getEvictionCount());
338 assertEquals(3, cache.getEvictedCount());
339
340
341 assertEquals(null, cache.getBlock(memoryBlocks[0].cacheKey, true, false));
342
343
344 CachedItem [] bigBlocks = generateFixedBlocks(3, blockSize*3, "big");
345 cache.cacheBlock(bigBlocks[0].cacheKey, bigBlocks[0]);
346
347
348 assertEquals(4, cache.getEvictionCount());
349 assertEquals(6, cache.getEvictedCount());
350
351
352 assertEquals(null, cache.getBlock(singleBlocks[2].cacheKey, true, false));
353 assertEquals(null, cache.getBlock(singleBlocks[3].cacheKey, true, false));
354 assertEquals(null, cache.getBlock(singleBlocks[4].cacheKey, true, false));
355
356
357 cache.getBlock(bigBlocks[0].cacheKey, true, false);
358
359
360 cache.cacheBlock(bigBlocks[1].cacheKey, bigBlocks[1]);
361
362
363 assertEquals(5, cache.getEvictionCount());
364 assertEquals(9, cache.getEvictedCount());
365
366
367 assertEquals(null, cache.getBlock(singleBlocks[1].cacheKey, true, false));
368 assertEquals(null, cache.getBlock(multiBlocks[1].cacheKey, true, false));
369 assertEquals(null, cache.getBlock(multiBlocks[2].cacheKey, true, false));
370
371
372 cache.cacheBlock(bigBlocks[2].cacheKey, bigBlocks[2], true);
373
374
375 assertEquals(6, cache.getEvictionCount());
376 assertEquals(12, cache.getEvictedCount());
377
378
379 assertEquals(null, cache.getBlock(memoryBlocks[1].cacheKey, true, false));
380 assertEquals(null, cache.getBlock(memoryBlocks[2].cacheKey, true, false));
381 assertEquals(null, cache.getBlock(memoryBlocks[3].cacheKey, true, false));
382
383
384 }
385
386
387 @Test
388 public void testScanResistance() throws Exception {
389
390 long maxSize = 100000;
391 long blockSize = calculateBlockSize(maxSize, 10);
392
393 LruBlockCache cache = new LruBlockCache(maxSize, blockSize, false,
394 (int)Math.ceil(1.2*maxSize/blockSize),
395 LruBlockCache.DEFAULT_LOAD_FACTOR,
396 LruBlockCache.DEFAULT_CONCURRENCY_LEVEL,
397 0.66f,
398 0.99f,
399 0.33f,
400 0.33f,
401 0.34f);
402
403 CachedItem [] singleBlocks = generateFixedBlocks(20, blockSize, "single");
404 CachedItem [] multiBlocks = generateFixedBlocks(5, blockSize, "multi");
405
406
407 for (CachedItem block : multiBlocks) {
408 cache.cacheBlock(block.cacheKey, block);
409 cache.getBlock(block.cacheKey, true, false);
410 }
411
412
413 for(int i=0;i<5;i++) {
414 cache.cacheBlock(singleBlocks[i].cacheKey, singleBlocks[i]);
415 }
416
417
418 assertEquals(1, cache.getEvictionCount());
419
420
421 assertEquals(4, cache.getEvictedCount());
422
423
424 assertEquals(null, cache.getBlock(singleBlocks[0].cacheKey, true, false));
425 assertEquals(null, cache.getBlock(singleBlocks[1].cacheKey, true, false));
426 assertEquals(null, cache.getBlock(multiBlocks[0].cacheKey, true, false));
427 assertEquals(null, cache.getBlock(multiBlocks[1].cacheKey, true, false));
428
429
430
431
432
433
434
435
436 for(int i=5;i<18;i++) {
437 cache.cacheBlock(singleBlocks[i].cacheKey, singleBlocks[i]);
438 }
439
440
441 assertEquals(4, cache.getEvictionCount());
442 assertEquals(16, cache.getEvictedCount());
443
444
445 assertEquals(7, cache.size());
446
447 }
448
449
450 @Test
451 public void testResizeBlockCache() throws Exception {
452
453 long maxSize = 300000;
454 long blockSize = calculateBlockSize(maxSize, 31);
455
456 LruBlockCache cache = new LruBlockCache(maxSize, blockSize, false,
457 (int)Math.ceil(1.2*maxSize/blockSize),
458 LruBlockCache.DEFAULT_LOAD_FACTOR,
459 LruBlockCache.DEFAULT_CONCURRENCY_LEVEL,
460 0.98f,
461 0.99f,
462 0.33f,
463 0.33f,
464 0.34f);
465
466 CachedItem [] singleBlocks = generateFixedBlocks(10, blockSize, "single");
467 CachedItem [] multiBlocks = generateFixedBlocks(10, blockSize, "multi");
468 CachedItem [] memoryBlocks = generateFixedBlocks(10, blockSize, "memory");
469
470
471 for(int i=0;i<10;i++) {
472
473
474 cache.cacheBlock(singleBlocks[i].cacheKey, singleBlocks[i]);
475
476
477 cache.cacheBlock(multiBlocks[i].cacheKey, multiBlocks[i]);
478 cache.getBlock(multiBlocks[i].cacheKey, true, false);
479
480
481 cache.cacheBlock(memoryBlocks[i].cacheKey, memoryBlocks[i], true);
482 }
483
484
485 assertEquals(0, cache.getEvictionCount());
486
487
488 cache.setMaxSize((long)(maxSize * 0.5f));
489
490
491 assertEquals(1, cache.getEvictionCount());
492
493
494 assertEquals(15, cache.getEvictedCount());
495
496
497 for(int i=0;i<5;i++) {
498 assertEquals(null, cache.getBlock(singleBlocks[i].cacheKey, true, false));
499 assertEquals(null, cache.getBlock(multiBlocks[i].cacheKey, true, false));
500 assertEquals(null, cache.getBlock(memoryBlocks[i].cacheKey, true, false));
501 }
502
503
504 for(int i=5;i<10;i++) {
505 assertEquals(singleBlocks[i], cache.getBlock(singleBlocks[i].cacheKey, true, false));
506 assertEquals(multiBlocks[i], cache.getBlock(multiBlocks[i].cacheKey, true, false));
507 assertEquals(memoryBlocks[i], cache.getBlock(memoryBlocks[i].cacheKey, true, false));
508 }
509 }
510
511
512 @Test
513 public void testPastNPeriodsMetrics() throws Exception {
514 double delta = 0.01;
515
516
517 CacheStats stats = new CacheStats(3);
518
519
520 stats.rollMetricsPeriod();
521 assertEquals(0.0, stats.getHitRatioPastNPeriods(), delta);
522 assertEquals(0.0, stats.getHitCachingRatioPastNPeriods(), delta);
523
524
525
526 stats.hit(false);
527 stats.hit(true);
528 stats.miss(false);
529 stats.miss(false);
530 stats.rollMetricsPeriod();
531 assertEquals(0.5, stats.getHitRatioPastNPeriods(), delta);
532 assertEquals(1.0, stats.getHitCachingRatioPastNPeriods(), delta);
533
534
535
536 stats.miss(true);
537 stats.miss(false);
538 stats.miss(false);
539 stats.miss(false);
540 stats.rollMetricsPeriod();
541 assertEquals(0.25, stats.getHitRatioPastNPeriods(), delta);
542 assertEquals(0.5, stats.getHitCachingRatioPastNPeriods(), delta);
543
544
545
546 stats.hit(false);
547 stats.hit(true);
548 stats.hit(false);
549 stats.hit(true);
550 stats.rollMetricsPeriod();
551 assertEquals(0.5, stats.getHitRatioPastNPeriods(), delta);
552 assertEquals(0.75, stats.getHitCachingRatioPastNPeriods(), delta);
553
554
555
556 stats.miss(true);
557 stats.miss(true);
558 stats.rollMetricsPeriod();
559 assertEquals(0.4, stats.getHitRatioPastNPeriods(), delta);
560 assertEquals(0.4, stats.getHitCachingRatioPastNPeriods(), delta);
561
562
563
564 stats.miss(true);
565 stats.miss(true);
566 stats.hit(false);
567 stats.hit(false);
568 stats.rollMetricsPeriod();
569 assertEquals(0.6, stats.getHitRatioPastNPeriods(), delta);
570 assertEquals((double)1/3, stats.getHitCachingRatioPastNPeriods(), delta);
571
572
573
574 stats.rollMetricsPeriod();
575 assertEquals((double)1/3, stats.getHitRatioPastNPeriods(), delta);
576 assertEquals(0.0, stats.getHitCachingRatioPastNPeriods(), delta);
577
578
579
580 stats.rollMetricsPeriod();
581 assertEquals(0.5, stats.getHitRatioPastNPeriods(), delta);
582 assertEquals(0.0, stats.getHitCachingRatioPastNPeriods(), delta);
583
584
585
586 stats.rollMetricsPeriod();
587 assertEquals(0.0, stats.getHitRatioPastNPeriods(), delta);
588 assertEquals(0.0, stats.getHitCachingRatioPastNPeriods(), delta);
589
590
591
592 stats.miss(true);
593 stats.miss(false);
594 stats.hit(true);
595 stats.hit(false);
596 stats.rollMetricsPeriod();
597 assertEquals(0.5, stats.getHitRatioPastNPeriods(), delta);
598 assertEquals(0.5, stats.getHitCachingRatioPastNPeriods(), delta);
599 }
600
601 private CachedItem [] generateFixedBlocks(int numBlocks, int size, String pfx) {
602 CachedItem [] blocks = new CachedItem[numBlocks];
603 for(int i=0;i<numBlocks;i++) {
604 blocks[i] = new CachedItem(pfx + i, size);
605 }
606 return blocks;
607 }
608
609 private CachedItem [] generateFixedBlocks(int numBlocks, long size, String pfx) {
610 return generateFixedBlocks(numBlocks, (int)size, pfx);
611 }
612
613 private CachedItem [] generateRandomBlocks(int numBlocks, long maxSize) {
614 CachedItem [] blocks = new CachedItem[numBlocks];
615 Random r = new Random();
616 for(int i=0;i<numBlocks;i++) {
617 blocks[i] = new CachedItem("block" + i, r.nextInt((int)maxSize)+1);
618 }
619 return blocks;
620 }
621
622 private long calculateBlockSize(long maxSize, int numBlocks) {
623 long roughBlockSize = maxSize / numBlocks;
624 int numEntries = (int)Math.ceil((1.2)*maxSize/roughBlockSize);
625 long totalOverhead = LruBlockCache.CACHE_FIXED_OVERHEAD +
626 ClassSize.CONCURRENT_HASHMAP +
627 (numEntries * ClassSize.CONCURRENT_HASHMAP_ENTRY) +
628 (LruBlockCache.DEFAULT_CONCURRENCY_LEVEL * ClassSize.CONCURRENT_HASHMAP_SEGMENT);
629 long negateBlockSize = (long)(totalOverhead/numEntries);
630 negateBlockSize += CachedBlock.PER_BLOCK_OVERHEAD;
631 return ClassSize.align((long)Math.floor((roughBlockSize - negateBlockSize)*0.99f));
632 }
633
634 private long calculateBlockSizeDefault(long maxSize, int numBlocks) {
635 long roughBlockSize = maxSize / numBlocks;
636 int numEntries = (int)Math.ceil((1.2)*maxSize/roughBlockSize);
637 long totalOverhead = LruBlockCache.CACHE_FIXED_OVERHEAD +
638 ClassSize.CONCURRENT_HASHMAP +
639 (numEntries * ClassSize.CONCURRENT_HASHMAP_ENTRY) +
640 (LruBlockCache.DEFAULT_CONCURRENCY_LEVEL * ClassSize.CONCURRENT_HASHMAP_SEGMENT);
641 long negateBlockSize = totalOverhead / numEntries;
642 negateBlockSize += CachedBlock.PER_BLOCK_OVERHEAD;
643 return ClassSize.align((long)Math.floor((roughBlockSize - negateBlockSize)*
644 LruBlockCache.DEFAULT_ACCEPTABLE_FACTOR));
645 }
646
647 private static class CachedItem implements Cacheable {
648 BlockCacheKey cacheKey;
649 int size;
650
651 CachedItem(String blockName, int size) {
652 this.cacheKey = new BlockCacheKey(blockName, 0);
653 this.size = size;
654 }
655
656
657 @Override
658 public long heapSize() {
659 return ClassSize.align(size);
660 }
661
662
663 public long cacheBlockHeapSize() {
664 return CachedBlock.PER_BLOCK_OVERHEAD
665 + ClassSize.align(cacheKey.heapSize())
666 + ClassSize.align(size);
667 }
668
669 @Override
670 public BlockType getBlockType() {
671 return BlockType.DATA;
672 }
673
674 @Override
675 public SchemaMetrics getSchemaMetrics() {
676 return SchemaMetrics.getUnknownInstanceForTest();
677 }
678
679 @Override
680 public int getSerializedLength() {
681 return 0;
682 }
683
684 @Override
685 public CacheableDeserializer<Cacheable> getDeserializer() {
686 return null;
687 }
688
689 @Override
690 public void serialize(ByteBuffer destination) {
691 }
692
693 }
694
695 @org.junit.Rule
696 public org.apache.hadoop.hbase.ResourceCheckerJUnitRule cu =
697 new org.apache.hadoop.hbase.ResourceCheckerJUnitRule();
698 }
699