View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.io.hfile;
20  
21  import java.lang.ref.WeakReference;
22  import java.nio.ByteBuffer;
23  import java.util.EnumMap;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.PriorityQueue;
28  import java.util.SortedSet;
29  import java.util.TreeSet;
30  import java.util.concurrent.ConcurrentHashMap;
31  import java.util.concurrent.Executors;
32  import java.util.concurrent.ScheduledExecutorService;
33  import java.util.concurrent.TimeUnit;
34  import java.util.concurrent.atomic.AtomicLong;
35  import java.util.concurrent.locks.ReentrantLock;
36  
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  import org.apache.hadoop.classification.InterfaceAudience;
40  import org.apache.hadoop.conf.Configuration;
41  import org.apache.hadoop.hbase.io.HeapSize;
42  import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
43  import org.apache.hadoop.hbase.io.hfile.bucket.BucketCache;
44  import org.apache.hadoop.hbase.util.Bytes;
45  import org.apache.hadoop.hbase.util.ClassSize;
46  import org.apache.hadoop.hbase.util.HasThread;
47  import org.apache.hadoop.hbase.util.Threads;
48  import org.apache.hadoop.util.StringUtils;
49  import org.codehaus.jackson.annotate.JsonIgnoreProperties;
50  
51  import com.google.common.annotations.VisibleForTesting;
52  import com.google.common.util.concurrent.ThreadFactoryBuilder;
53  
54  /**
55   * A block cache implementation that is memory-aware using {@link HeapSize},
56   * memory-bound using an LRU eviction algorithm, and concurrent: backed by a
57   * {@link ConcurrentHashMap} and with a non-blocking eviction thread giving
58   * constant-time {@link #cacheBlock} and {@link #getBlock} operations.<p>
59   *
60   * Contains three levels of block priority to allow for
61   * scan-resistance and in-memory families.  A block is added with an inMemory
62   * flag if necessary, otherwise a block becomes a single access priority.  Once
63   * a blocked is accessed again, it changes to multiple access.  This is used
64   * to prevent scans from thrashing the cache, adding a least-frequently-used
65   * element to the eviction algorithm.<p>
66   *
67   * Each priority is given its own chunk of the total cache to ensure
68   * fairness during eviction.  Each priority will retain close to its maximum
69   * size, however, if any priority is not using its entire chunk the others
70   * are able to grow beyond their chunk size.<p>
71   *
72   * Instantiated at a minimum with the total size and average block size.
73   * All sizes are in bytes.  The block size is not especially important as this
74   * cache is fully dynamic in its sizing of blocks.  It is only used for
75   * pre-allocating data structures and in initial heap estimation of the map.<p>
76   *
77   * The detailed constructor defines the sizes for the three priorities (they
78   * should total to the maximum size defined).  It also sets the levels that
79   * trigger and control the eviction thread.<p>
80   *
81   * The acceptable size is the cache size level which triggers the eviction
82   * process to start.  It evicts enough blocks to get the size below the
83   * minimum size specified.<p>
84   *
85   * Eviction happens in a separate thread and involves a single full-scan
86   * of the map.  It determines how many bytes must be freed to reach the minimum
87   * size, and then while scanning determines the fewest least-recently-used
88   * blocks necessary from each of the three priorities (would be 3 times bytes
89   * to free).  It then uses the priority chunk sizes to evict fairly according
90   * to the relative sizes and usage.
91   */
92  @InterfaceAudience.Private
93  @JsonIgnoreProperties({"encodingCountsForTest"})
94  public class LruBlockCache implements BlockCache, HeapSize {
95  
96    static final Log LOG = LogFactory.getLog(LruBlockCache.class);
97  
98    static final String LRU_MIN_FACTOR_CONFIG_NAME = "hbase.lru.blockcache.min.factor";
99    static final String LRU_ACCEPTABLE_FACTOR_CONFIG_NAME = "hbase.lru.blockcache.acceptable.factor";
100   static final String LRU_SINGLE_PERCENTAGE_CONFIG_NAME = "hbase.lru.blockcache.single.percentage";
101   static final String LRU_MULTI_PERCENTAGE_CONFIG_NAME = "hbase.lru.blockcache.multi.percentage";
102   static final String LRU_MEMORY_PERCENTAGE_CONFIG_NAME = "hbase.lru.blockcache.memory.percentage";
103 
104   /**
105    * Configuration key to force data-block always(except in-memory are too much)
106    * cached in memory for in-memory hfile, unlike inMemory, which is a column-family
107    * configuration, inMemoryForceMode is a cluster-wide configuration
108    */
109   static final String LRU_IN_MEMORY_FORCE_MODE_CONFIG_NAME = "hbase.lru.rs.inmemoryforcemode";
110 
111   /** Default Configuration Parameters*/
112 
113   /** Backing Concurrent Map Configuration */
114   static final float DEFAULT_LOAD_FACTOR = 0.75f;
115   static final int DEFAULT_CONCURRENCY_LEVEL = 16;
116 
117   /** Eviction thresholds */
118   static final float DEFAULT_MIN_FACTOR = 0.95f;
119   static final float DEFAULT_ACCEPTABLE_FACTOR = 0.99f;
120 
121   /** Priority buckets */
122   static final float DEFAULT_SINGLE_FACTOR = 0.25f;
123   static final float DEFAULT_MULTI_FACTOR = 0.50f;
124   static final float DEFAULT_MEMORY_FACTOR = 0.25f;
125 
126   static final boolean DEFAULT_IN_MEMORY_FORCE_MODE = false;
127 
128   /** Statistics thread */
129   static final int statThreadPeriod = 60 * 5;
130 
131   /** Concurrent map (the cache) */
132   private final Map<BlockCacheKey,LruCachedBlock> map;
133 
134   /** Eviction lock (locked when eviction in process) */
135   private final ReentrantLock evictionLock = new ReentrantLock(true);
136 
137   /** Volatile boolean to track if we are in an eviction process or not */
138   private volatile boolean evictionInProgress = false;
139 
140   /** Eviction thread */
141   private final EvictionThread evictionThread;
142 
143   /** Statistics thread schedule pool (for heavy debugging, could remove) */
144   private final ScheduledExecutorService scheduleThreadPool =
145     Executors.newScheduledThreadPool(1,
146       new ThreadFactoryBuilder()
147         .setNameFormat("LruStats #%d")
148         .setDaemon(true)
149         .build());
150 
151   /** Current size of cache */
152   private final AtomicLong size;
153 
154   /** Current number of cached elements */
155   private final AtomicLong elements;
156 
157   /** Cache access count (sequential ID) */
158   private final AtomicLong count;
159 
160   /** Cache statistics */
161   private final CacheStats stats;
162 
163   /** Maximum allowable size of cache (block put if size > max, evict) */
164   private long maxSize;
165 
166   /** Approximate block size */
167   private long blockSize;
168 
169   /** Acceptable size of cache (no evictions if size < acceptable) */
170   private float acceptableFactor;
171 
172   /** Minimum threshold of cache (when evicting, evict until size < min) */
173   private float minFactor;
174 
175   /** Single access bucket size */
176   private float singleFactor;
177 
178   /** Multiple access bucket size */
179   private float multiFactor;
180 
181   /** In-memory bucket size */
182   private float memoryFactor;
183 
184   /** Overhead of the structure itself */
185   private long overhead;
186 
187   /** Whether in-memory hfile's data block has higher priority when evicting */
188   private boolean forceInMemory;
189 
190   /** Where to send victims (blocks evicted from the cache) */
191   private BucketCache victimHandler = null;
192 
193   /**
194    * Default constructor.  Specify maximum size and expected average block
195    * size (approximation is fine).
196    *
197    * <p>All other factors will be calculated based on defaults specified in
198    * this class.
199    * @param maxSize maximum size of cache, in bytes
200    * @param blockSize approximate size of each block, in bytes
201    */
202   public LruBlockCache(long maxSize, long blockSize) {
203     this(maxSize, blockSize, true);
204   }
205 
206   /**
207    * Constructor used for testing.  Allows disabling of the eviction thread.
208    */
209   public LruBlockCache(long maxSize, long blockSize, boolean evictionThread) {
210     this(maxSize, blockSize, evictionThread,
211         (int)Math.ceil(1.2*maxSize/blockSize),
212         DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL,
213         DEFAULT_MIN_FACTOR, DEFAULT_ACCEPTABLE_FACTOR,
214         DEFAULT_SINGLE_FACTOR,
215         DEFAULT_MULTI_FACTOR,
216         DEFAULT_MEMORY_FACTOR,
217         false
218         );
219   }
220 
221   public LruBlockCache(long maxSize, long blockSize, boolean evictionThread, Configuration conf) {
222     this(maxSize, blockSize, evictionThread,
223         (int)Math.ceil(1.2*maxSize/blockSize),
224         DEFAULT_LOAD_FACTOR,
225         DEFAULT_CONCURRENCY_LEVEL,
226         conf.getFloat(LRU_MIN_FACTOR_CONFIG_NAME, DEFAULT_MIN_FACTOR),
227         conf.getFloat(LRU_ACCEPTABLE_FACTOR_CONFIG_NAME, DEFAULT_ACCEPTABLE_FACTOR),
228         conf.getFloat(LRU_SINGLE_PERCENTAGE_CONFIG_NAME, DEFAULT_SINGLE_FACTOR),
229         conf.getFloat(LRU_MULTI_PERCENTAGE_CONFIG_NAME, DEFAULT_MULTI_FACTOR),
230         conf.getFloat(LRU_MEMORY_PERCENTAGE_CONFIG_NAME, DEFAULT_MEMORY_FACTOR),
231         conf.getBoolean(LRU_IN_MEMORY_FORCE_MODE_CONFIG_NAME, DEFAULT_IN_MEMORY_FORCE_MODE)
232         );
233   }
234 
235   public LruBlockCache(long maxSize, long blockSize, Configuration conf) {
236     this(maxSize, blockSize, true, conf);
237   }
238 
239   /**
240    * Configurable constructor.  Use this constructor if not using defaults.
241    * @param maxSize maximum size of this cache, in bytes
242    * @param blockSize expected average size of blocks, in bytes
243    * @param evictionThread whether to run evictions in a bg thread or not
244    * @param mapInitialSize initial size of backing ConcurrentHashMap
245    * @param mapLoadFactor initial load factor of backing ConcurrentHashMap
246    * @param mapConcurrencyLevel initial concurrency factor for backing CHM
247    * @param minFactor percentage of total size that eviction will evict until
248    * @param acceptableFactor percentage of total size that triggers eviction
249    * @param singleFactor percentage of total size for single-access blocks
250    * @param multiFactor percentage of total size for multiple-access blocks
251    * @param memoryFactor percentage of total size for in-memory blocks
252    */
253   public LruBlockCache(long maxSize, long blockSize, boolean evictionThread,
254       int mapInitialSize, float mapLoadFactor, int mapConcurrencyLevel,
255       float minFactor, float acceptableFactor, float singleFactor,
256       float multiFactor, float memoryFactor, boolean forceInMemory) {
257     if(singleFactor + multiFactor + memoryFactor != 1 ||
258         singleFactor < 0 || multiFactor < 0 || memoryFactor < 0) {
259       throw new IllegalArgumentException("Single, multi, and memory factors " +
260           " should be non-negative and total 1.0");
261     }
262     if(minFactor >= acceptableFactor) {
263       throw new IllegalArgumentException("minFactor must be smaller than acceptableFactor");
264     }
265     if(minFactor >= 1.0f || acceptableFactor >= 1.0f) {
266       throw new IllegalArgumentException("all factors must be < 1");
267     }
268     this.maxSize = maxSize;
269     this.blockSize = blockSize;
270     this.forceInMemory = forceInMemory;
271     map = new ConcurrentHashMap<BlockCacheKey,LruCachedBlock>(mapInitialSize,
272         mapLoadFactor, mapConcurrencyLevel);
273     this.minFactor = minFactor;
274     this.acceptableFactor = acceptableFactor;
275     this.singleFactor = singleFactor;
276     this.multiFactor = multiFactor;
277     this.memoryFactor = memoryFactor;
278     this.stats = new CacheStats();
279     this.count = new AtomicLong(0);
280     this.elements = new AtomicLong(0);
281     this.overhead = calculateOverhead(maxSize, blockSize, mapConcurrencyLevel);
282     this.size = new AtomicLong(this.overhead);
283     if(evictionThread) {
284       this.evictionThread = new EvictionThread(this);
285       this.evictionThread.start(); // FindBugs SC_START_IN_CTOR
286     } else {
287       this.evictionThread = null;
288     }
289     this.scheduleThreadPool.scheduleAtFixedRate(new StatisticsThread(this),
290         statThreadPeriod, statThreadPeriod, TimeUnit.SECONDS);
291   }
292 
293   public void setMaxSize(long maxSize) {
294     this.maxSize = maxSize;
295     if(this.size.get() > acceptableSize() && !evictionInProgress) {
296       runEviction();
297     }
298   }
299 
300   // BlockCache implementation
301 
302   /**
303    * Cache the block with the specified name and buffer.
304    * <p>
305    * It is assumed this will NOT be called on an already cached block. In rare cases (HBASE-8547)
306    * this can happen, for which we compare the buffer contents.
307    * @param cacheKey block's cache key
308    * @param buf block buffer
309    * @param inMemory if block is in-memory
310    */
311   @Override
312   public void cacheBlock(BlockCacheKey cacheKey, Cacheable buf, boolean inMemory) {
313     LruCachedBlock cb = map.get(cacheKey);
314     if(cb != null) {
315       // compare the contents, if they are not equal, we are in big trouble
316       if (compare(buf, cb.getBuffer()) != 0) {
317         throw new RuntimeException("Cached block contents differ, which should not have happened."
318           + "cacheKey:" + cacheKey);
319       }
320       String msg = "Cached an already cached block: " + cacheKey + " cb:" + cb.getCacheKey();
321       msg += ". This is harmless and can happen in rare cases (see HBASE-8547)";
322       LOG.warn(msg);
323       return;
324     }
325     cb = new LruCachedBlock(cacheKey, buf, count.incrementAndGet(), inMemory);
326     long newSize = updateSizeMetrics(cb, false);
327     map.put(cacheKey, cb);
328     elements.incrementAndGet();
329     if(newSize > acceptableSize() && !evictionInProgress) {
330       runEviction();
331     }
332   }
333 
334   private int compare(Cacheable left, Cacheable right) {
335     ByteBuffer l = ByteBuffer.allocate(left.getSerializedLength());
336     left.serialize(l);
337     ByteBuffer r = ByteBuffer.allocate(right.getSerializedLength());
338     right.serialize(r);
339     return Bytes.compareTo(l.array(), l.arrayOffset(), l.limit(),
340       r.array(), r.arrayOffset(), r.limit());
341   }
342 
343   /**
344    * Cache the block with the specified name and buffer.
345    * <p>
346    * It is assumed this will NEVER be called on an already cached block.  If
347    * that is done, it is assumed that you are reinserting the same exact
348    * block due to a race condition and will update the buffer but not modify
349    * the size of the cache.
350    * @param cacheKey block's cache key
351    * @param buf block buffer
352    */
353   public void cacheBlock(BlockCacheKey cacheKey, Cacheable buf) {
354     cacheBlock(cacheKey, buf, false);
355   }
356 
357   /**
358    * Helper function that updates the local size counter and also updates any
359    * per-cf or per-blocktype metrics it can discern from given
360    * {@link LruCachedBlock}
361    *
362    * @param cb
363    * @param evict
364    */
365   protected long updateSizeMetrics(LruCachedBlock cb, boolean evict) {
366     long heapsize = cb.heapSize();
367     if (evict) {
368       heapsize *= -1;
369     }
370     return size.addAndGet(heapsize);
371   }
372 
373   /**
374    * Get the buffer of the block with the specified name.
375    * @param cacheKey block's cache key
376    * @param caching true if the caller caches blocks on cache misses
377    * @param repeat Whether this is a repeat lookup for the same block
378    *        (used to avoid double counting cache misses when doing double-check locking)
379    * @param updateCacheMetrics Whether to update cache metrics or not
380    * @return buffer of specified cache key, or null if not in cache
381    */
382   @Override
383   public Cacheable getBlock(BlockCacheKey cacheKey, boolean caching, boolean repeat,
384       boolean updateCacheMetrics) {
385     LruCachedBlock cb = map.get(cacheKey);
386     if(cb == null) {
387       if (!repeat && updateCacheMetrics) stats.miss(caching);
388       if (victimHandler != null)
389         return victimHandler.getBlock(cacheKey, caching, repeat, updateCacheMetrics);
390       return null;
391     }
392     if (updateCacheMetrics) stats.hit(caching);
393     cb.access(count.incrementAndGet());
394     return cb.getBuffer();
395   }
396 
397   /**
398    * Whether the cache contains block with specified cacheKey
399    * @param cacheKey
400    * @return true if contains the block
401    */
402   public boolean containsBlock(BlockCacheKey cacheKey) {
403     return map.containsKey(cacheKey);
404   }
405 
406   @Override
407   public boolean evictBlock(BlockCacheKey cacheKey) {
408     LruCachedBlock cb = map.get(cacheKey);
409     if (cb == null) return false;
410     evictBlock(cb, false);
411     return true;
412   }
413 
414   /**
415    * Evicts all blocks for a specific HFile. This is an
416    * expensive operation implemented as a linear-time search through all blocks
417    * in the cache. Ideally this should be a search in a log-access-time map.
418    *
419    * <p>
420    * This is used for evict-on-close to remove all blocks of a specific HFile.
421    *
422    * @return the number of blocks evicted
423    */
424   @Override
425   public int evictBlocksByHfileName(String hfileName) {
426     int numEvicted = 0;
427     for (BlockCacheKey key : map.keySet()) {
428       if (key.getHfileName().equals(hfileName)) {
429         if (evictBlock(key))
430           ++numEvicted;
431       }
432     }
433     if (victimHandler != null) {
434       numEvicted += victimHandler.evictBlocksByHfileName(hfileName);
435     }
436     return numEvicted;
437   }
438 
439   /**
440    * Evict the block, and it will be cached by the victim handler if exists &&
441    * block may be read again later
442    * @param block
443    * @param evictedByEvictionProcess true if the given block is evicted by
444    *          EvictionThread
445    * @return the heap size of evicted block
446    */
447   protected long evictBlock(LruCachedBlock block, boolean evictedByEvictionProcess) {
448     map.remove(block.getCacheKey());
449     updateSizeMetrics(block, true);
450     elements.decrementAndGet();
451     stats.evicted();
452     if (evictedByEvictionProcess && victimHandler != null) {
453       boolean wait = getCurrentSize() < acceptableSize();
454       boolean inMemory = block.getPriority() == BlockPriority.MEMORY;
455       victimHandler.cacheBlockWithWait(block.getCacheKey(), block.getBuffer(),
456           inMemory, wait);
457     }
458     return block.heapSize();
459   }
460 
461   /**
462    * Multi-threaded call to run the eviction process.
463    */
464   private void runEviction() {
465     if(evictionThread == null) {
466       evict();
467     } else {
468       evictionThread.evict();
469     }
470   }
471 
472   /**
473    * Eviction method.
474    */
475   void evict() {
476 
477     // Ensure only one eviction at a time
478     if(!evictionLock.tryLock()) return;
479 
480     try {
481       evictionInProgress = true;
482       long currentSize = this.size.get();
483       long bytesToFree = currentSize - minSize();
484 
485       if (LOG.isTraceEnabled()) {
486         LOG.trace("Block cache LRU eviction started; Attempting to free " +
487           StringUtils.byteDesc(bytesToFree) + " of total=" +
488           StringUtils.byteDesc(currentSize));
489       }
490 
491       if(bytesToFree <= 0) return;
492 
493       // Instantiate priority buckets
494       BlockBucket bucketSingle = new BlockBucket(bytesToFree, blockSize, singleSize());
495       BlockBucket bucketMulti = new BlockBucket(bytesToFree, blockSize, multiSize());
496       BlockBucket bucketMemory = new BlockBucket(bytesToFree, blockSize, memorySize());
497 
498       // Scan entire map putting into appropriate buckets
499       for(LruCachedBlock cachedBlock : map.values()) {
500         switch(cachedBlock.getPriority()) {
501           case SINGLE: {
502             bucketSingle.add(cachedBlock);
503             break;
504           }
505           case MULTI: {
506             bucketMulti.add(cachedBlock);
507             break;
508           }
509           case MEMORY: {
510             bucketMemory.add(cachedBlock);
511             break;
512           }
513         }
514       }
515 
516       long bytesFreed = 0;
517       if (forceInMemory || memoryFactor > 0.999f) {
518         long s = bucketSingle.totalSize();
519         long m = bucketMulti.totalSize();
520         if (bytesToFree > (s + m)) {
521           // this means we need to evict blocks in memory bucket to make room,
522           // so the single and multi buckets will be emptied
523           bytesFreed = bucketSingle.free(s);
524           bytesFreed += bucketMulti.free(m);
525           bytesFreed += bucketMemory.free(bytesToFree - bytesFreed);
526         } else {
527           // this means no need to evict block in memory bucket,
528           // and we try best to make the ratio between single-bucket and
529           // multi-bucket is 1:2
530           long bytesRemain = s + m - bytesToFree;
531           if (3 * s <= bytesRemain) {
532             // single-bucket is small enough that no eviction happens for it
533             // hence all eviction goes from multi-bucket
534             bytesFreed = bucketMulti.free(bytesToFree);
535           } else if (3 * m <= 2 * bytesRemain) {
536             // multi-bucket is small enough that no eviction happens for it
537             // hence all eviction goes from single-bucket
538             bytesFreed = bucketSingle.free(bytesToFree);
539           } else {
540             // both buckets need to evict some blocks
541             bytesFreed = bucketSingle.free(s - bytesRemain / 3);
542             if (bytesFreed < bytesToFree) {
543               bytesFreed += bucketMulti.free(bytesToFree - bytesFreed);
544             }
545           }
546         }
547       } else {
548         PriorityQueue<BlockBucket> bucketQueue =
549           new PriorityQueue<BlockBucket>(3);
550 
551         bucketQueue.add(bucketSingle);
552         bucketQueue.add(bucketMulti);
553         bucketQueue.add(bucketMemory);
554 
555         int remainingBuckets = 3;
556 
557         BlockBucket bucket;
558         while((bucket = bucketQueue.poll()) != null) {
559           long overflow = bucket.overflow();
560           if(overflow > 0) {
561             long bucketBytesToFree = Math.min(overflow,
562                 (bytesToFree - bytesFreed) / remainingBuckets);
563             bytesFreed += bucket.free(bucketBytesToFree);
564           }
565           remainingBuckets--;
566         }
567       }
568 
569       if (LOG.isTraceEnabled()) {
570         long single = bucketSingle.totalSize();
571         long multi = bucketMulti.totalSize();
572         long memory = bucketMemory.totalSize();
573         LOG.trace("Block cache LRU eviction completed; " +
574           "freed=" + StringUtils.byteDesc(bytesFreed) + ", " +
575           "total=" + StringUtils.byteDesc(this.size.get()) + ", " +
576           "single=" + StringUtils.byteDesc(single) + ", " +
577           "multi=" + StringUtils.byteDesc(multi) + ", " +
578           "memory=" + StringUtils.byteDesc(memory));
579       }
580     } finally {
581       stats.evict();
582       evictionInProgress = false;
583       evictionLock.unlock();
584     }
585   }
586 
587   /**
588    * Used to group blocks into priority buckets.  There will be a BlockBucket
589    * for each priority (single, multi, memory).  Once bucketed, the eviction
590    * algorithm takes the appropriate number of elements out of each according
591    * to configuration parameters and their relatives sizes.
592    */
593   private class BlockBucket implements Comparable<BlockBucket> {
594     private LruCachedBlockQueue queue;
595     private long totalSize = 0;
596     private long bucketSize;
597 
598     public BlockBucket(long bytesToFree, long blockSize, long bucketSize) {
599       this.bucketSize = bucketSize;
600       queue = new LruCachedBlockQueue(bytesToFree, blockSize);
601       totalSize = 0;
602     }
603 
604     public void add(LruCachedBlock block) {
605       totalSize += block.heapSize();
606       queue.add(block);
607     }
608 
609     public long free(long toFree) {
610       LruCachedBlock cb;
611       long freedBytes = 0;
612       while ((cb = queue.pollLast()) != null) {
613         freedBytes += evictBlock(cb, true);
614         if (freedBytes >= toFree) {
615           return freedBytes;
616         }
617       }
618       return freedBytes;
619     }
620 
621     public long overflow() {
622       return totalSize - bucketSize;
623     }
624 
625     public long totalSize() {
626       return totalSize;
627     }
628 
629     public int compareTo(BlockBucket that) {
630       if(this.overflow() == that.overflow()) return 0;
631       return this.overflow() > that.overflow() ? 1 : -1;
632     }
633 
634     @Override
635     public boolean equals(Object that) {
636       if (that == null || !(that instanceof BlockBucket)){
637         return false;
638       }
639       return compareTo((BlockBucket)that) == 0;
640     }
641 
642     @Override
643     public int hashCode() {
644       // Nothing distingushing about each instance unless I pass in a 'name' or something
645       return super.hashCode();
646     }
647   }
648 
649   /**
650    * Get the maximum size of this cache.
651    * @return max size in bytes
652    */
653   public long getMaxSize() {
654     return this.maxSize;
655   }
656 
657   @Override
658   public long getCurrentSize() {
659     return this.size.get();
660   }
661 
662   @Override
663   public long getFreeSize() {
664     return getMaxSize() - getCurrentSize();
665   }
666 
667   @Override
668   public long size() {
669     return getMaxSize();
670   }
671 
672   @Override
673   public long getBlockCount() {
674     return this.elements.get();
675   }
676 
677   EvictionThread getEvictionThread() {
678     return this.evictionThread;
679   }
680 
681   /*
682    * Eviction thread.  Sits in waiting state until an eviction is triggered
683    * when the cache size grows above the acceptable level.<p>
684    *
685    * Thread is triggered into action by {@link LruBlockCache#runEviction()}
686    */
687   static class EvictionThread extends HasThread {
688     private WeakReference<LruBlockCache> cache;
689     private boolean go = true;
690     // flag set after enter the run method, used for test
691     private boolean enteringRun = false;
692 
693     public EvictionThread(LruBlockCache cache) {
694       super(Thread.currentThread().getName() + ".LruBlockCache.EvictionThread");
695       setDaemon(true);
696       this.cache = new WeakReference<LruBlockCache>(cache);
697     }
698 
699     @Override
700     public void run() {
701       enteringRun = true;
702       while (this.go) {
703         synchronized(this) {
704           try {
705             this.wait(1000 * 10/*Don't wait for ever*/);
706           } catch(InterruptedException e) {}
707         }
708         LruBlockCache cache = this.cache.get();
709         if (cache == null) break;
710         cache.evict();
711       }
712     }
713 
714     @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="NN_NAKED_NOTIFY",
715         justification="This is what we want")
716     public void evict() {
717       synchronized(this) {
718         this.notifyAll();
719       }
720     }
721 
722     synchronized void shutdown() {
723       this.go = false;
724       this.notifyAll();
725     }
726 
727     /**
728      * Used for the test.
729      */
730     boolean isEnteringRun() {
731       return this.enteringRun;
732     }
733   }
734 
735   /*
736    * Statistics thread.  Periodically prints the cache statistics to the log.
737    */
738   static class StatisticsThread extends Thread {
739     LruBlockCache lru;
740 
741     public StatisticsThread(LruBlockCache lru) {
742       super("LruBlockCache.StatisticsThread");
743       setDaemon(true);
744       this.lru = lru;
745     }
746     @Override
747     public void run() {
748       lru.logStats();
749     }
750   }
751 
752   public void logStats() {
753     if (!LOG.isDebugEnabled()) return;
754     // Log size
755     long totalSize = heapSize();
756     long freeSize = maxSize - totalSize;
757     LruBlockCache.LOG.debug("Total=" + StringUtils.byteDesc(totalSize) + ", " +
758         "free=" + StringUtils.byteDesc(freeSize) + ", " +
759         "max=" + StringUtils.byteDesc(this.maxSize) + ", " +
760         "blocks=" + size() +", " +
761         "accesses=" + stats.getRequestCount() + ", " +
762         "hits=" + stats.getHitCount() + ", " +
763         "hitRatio=" +
764           (stats.getHitCount() == 0 ? "0" : (StringUtils.formatPercent(stats.getHitRatio(), 2)+ ", ")) + ", " +
765         "cachingAccesses=" + stats.getRequestCachingCount() + ", " +
766         "cachingHits=" + stats.getHitCachingCount() + ", " +
767         "cachingHitsRatio=" +
768           (stats.getHitCachingCount() == 0 ? "0,": (StringUtils.formatPercent(stats.getHitCachingRatio(), 2) + ", ")) +
769         "evictions=" + stats.getEvictionCount() + ", " +
770         "evicted=" + stats.getEvictedCount() + ", " +
771         "evictedPerRun=" + stats.evictedPerEviction());
772   }
773 
774   /**
775    * Get counter statistics for this cache.
776    *
777    * <p>Includes: total accesses, hits, misses, evicted blocks, and runs
778    * of the eviction processes.
779    */
780   public CacheStats getStats() {
781     return this.stats;
782   }
783 
784   public final static long CACHE_FIXED_OVERHEAD = ClassSize.align(
785       (3 * Bytes.SIZEOF_LONG) + (9 * ClassSize.REFERENCE) +
786       (5 * Bytes.SIZEOF_FLOAT) + Bytes.SIZEOF_BOOLEAN
787       + ClassSize.OBJECT);
788 
789   // HeapSize implementation
790   public long heapSize() {
791     return getCurrentSize();
792   }
793 
794   public static long calculateOverhead(long maxSize, long blockSize, int concurrency){
795     // FindBugs ICAST_INTEGER_MULTIPLY_CAST_TO_LONG
796     return CACHE_FIXED_OVERHEAD + ClassSize.CONCURRENT_HASHMAP +
797         ((long)Math.ceil(maxSize*1.2/blockSize)
798             * ClassSize.CONCURRENT_HASHMAP_ENTRY) +
799         ((long)concurrency * ClassSize.CONCURRENT_HASHMAP_SEGMENT);
800   }
801 
802   @Override
803   public Iterator<CachedBlock> iterator() {
804     final Iterator<LruCachedBlock> iterator = map.values().iterator();
805 
806     return new Iterator<CachedBlock>() {
807       private final long now = System.nanoTime();
808 
809       @Override
810       public boolean hasNext() {
811         return iterator.hasNext();
812       }
813 
814       @Override
815       public CachedBlock next() {
816         final LruCachedBlock b = iterator.next();
817         return new CachedBlock() {
818           @Override
819           public String toString() {
820             return BlockCacheUtil.toString(this, now);
821           }
822 
823           @Override
824           public BlockPriority getBlockPriority() {
825             return b.getPriority();
826           }
827 
828           @Override
829           public BlockType getBlockType() {
830             return b.getBuffer().getBlockType();
831           }
832 
833           @Override
834           public long getOffset() {
835             return b.getCacheKey().getOffset();
836           }
837 
838           @Override
839           public long getSize() {
840             return b.getBuffer().heapSize();
841           }
842 
843           @Override
844           public long getCachedTime() {
845             return b.getCachedTime();
846           }
847 
848           @Override
849           public String getFilename() {
850             return b.getCacheKey().getHfileName();
851           }
852 
853           @Override
854           public int compareTo(CachedBlock other) {
855             int diff = this.getFilename().compareTo(other.getFilename());
856             if (diff != 0) return diff;
857             diff = (int)(this.getOffset() - other.getOffset());
858             if (diff != 0) return diff;
859             if (other.getCachedTime() < 0 || this.getCachedTime() < 0) {
860               throw new IllegalStateException("" + this.getCachedTime() + ", " +
861                 other.getCachedTime());
862             }
863             return (int)(other.getCachedTime() - this.getCachedTime());
864           }
865 
866           @Override
867           public int hashCode() {
868             return b.hashCode();
869           }
870 
871           @Override
872           public boolean equals(Object obj) {
873             if (obj instanceof CachedBlock) {
874               CachedBlock cb = (CachedBlock)obj;
875               return compareTo(cb) == 0;
876             } else {
877               return false;
878             }
879           }
880         };
881       }
882 
883       @Override
884       public void remove() {
885         throw new UnsupportedOperationException();
886       }
887     };
888   }
889 
890   // Simple calculators of sizes given factors and maxSize
891 
892   private long acceptableSize() {
893     return (long)Math.floor(this.maxSize * this.acceptableFactor);
894   }
895   private long minSize() {
896     return (long)Math.floor(this.maxSize * this.minFactor);
897   }
898   private long singleSize() {
899     return (long)Math.floor(this.maxSize * this.singleFactor * this.minFactor);
900   }
901   private long multiSize() {
902     return (long)Math.floor(this.maxSize * this.multiFactor * this.minFactor);
903   }
904   private long memorySize() {
905     return (long)Math.floor(this.maxSize * this.memoryFactor * this.minFactor);
906   }
907 
908   public void shutdown() {
909     if (victimHandler != null)
910       victimHandler.shutdown();
911     this.scheduleThreadPool.shutdown();
912     for (int i = 0; i < 10; i++) {
913       if (!this.scheduleThreadPool.isShutdown()) Threads.sleep(10);
914     }
915     if (!this.scheduleThreadPool.isShutdown()) {
916       List<Runnable> runnables = this.scheduleThreadPool.shutdownNow();
917       LOG.debug("Still running " + runnables);
918     }
919     this.evictionThread.shutdown();
920   }
921 
922   /** Clears the cache. Used in tests. */
923   public void clearCache() {
924     map.clear();
925   }
926 
927   /**
928    * Used in testing. May be very inefficient.
929    * @return the set of cached file names
930    */
931   SortedSet<String> getCachedFileNamesForTest() {
932     SortedSet<String> fileNames = new TreeSet<String>();
933     for (BlockCacheKey cacheKey : map.keySet()) {
934       fileNames.add(cacheKey.getHfileName());
935     }
936     return fileNames;
937   }
938 
939   @VisibleForTesting
940   Map<BlockType, Integer> getBlockTypeCountsForTest() {
941     Map<BlockType, Integer> counts =
942         new EnumMap<BlockType, Integer>(BlockType.class);
943     for (LruCachedBlock cb : map.values()) {
944       BlockType blockType = ((HFileBlock) cb.getBuffer()).getBlockType();
945       Integer count = counts.get(blockType);
946       counts.put(blockType, (count == null ? 0 : count) + 1);
947     }
948     return counts;
949   }
950 
951   public Map<DataBlockEncoding, Integer> getEncodingCountsForTest() {
952     Map<DataBlockEncoding, Integer> counts =
953         new EnumMap<DataBlockEncoding, Integer>(DataBlockEncoding.class);
954     for (BlockCacheKey cacheKey : map.keySet()) {
955       DataBlockEncoding encoding = cacheKey.getDataBlockEncoding();
956       Integer count = counts.get(encoding);
957       counts.put(encoding, (count == null ? 0 : count) + 1);
958     }
959     return counts;
960   }
961 
962   public void setVictimCache(BucketCache handler) {
963     assert victimHandler == null;
964     victimHandler = handler;
965   }
966 
967   @Override
968   public BlockCache[] getBlockCaches() {
969     return null;
970   }
971 }