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 java.util.Iterator;
021import java.util.Map;
022import java.util.Optional;
023import org.apache.commons.lang3.mutable.Mutable;
024import org.apache.commons.lang3.mutable.MutableBoolean;
025import org.apache.hadoop.fs.Path;
026import org.apache.hadoop.hbase.io.HeapSize;
027import org.apache.hadoop.hbase.io.hfile.bucket.BucketCache;
028import org.apache.hadoop.hbase.util.Pair;
029import org.apache.yetus.audience.InterfaceAudience;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033/**
034 * CombinedBlockCache is an abstraction layer that combines {@link FirstLevelBlockCache} and
035 * {@link BucketCache}. The smaller lruCache is used to cache bloom blocks and index blocks. The
036 * larger Cache is used to cache data blocks.
037 * {@link #getBlock(BlockCacheKey, boolean, boolean, boolean)} reads first from the smaller l1Cache
038 * before looking for the block in the l2Cache. Blocks evicted from l1Cache are put into the bucket
039 * cache. Metrics are the combined size and hits and misses of both caches.
040 */
041@InterfaceAudience.Private
042public class CombinedBlockCache implements ResizableBlockCache, HeapSize {
043  protected final FirstLevelBlockCache l1Cache;
044  protected final BlockCache l2Cache;
045  protected final CombinedCacheStats combinedCacheStats;
046
047  private static final Logger LOG = LoggerFactory.getLogger(CombinedBlockCache.class);
048
049  public CombinedBlockCache(FirstLevelBlockCache l1Cache, BlockCache l2Cache) {
050    this.l1Cache = l1Cache;
051    this.l2Cache = l2Cache;
052    this.combinedCacheStats = new CombinedCacheStats(l1Cache.getStats(), l2Cache.getStats());
053  }
054
055  @Override
056  public long heapSize() {
057    long l2size = 0;
058    if (l2Cache instanceof HeapSize) {
059      l2size = ((HeapSize) l2Cache).heapSize();
060    }
061    return l1Cache.heapSize() + l2size;
062  }
063
064  @Override
065  public void cacheBlock(BlockCacheKey cacheKey, Cacheable buf, boolean inMemory) {
066    cacheBlock(cacheKey, buf, inMemory, false);
067  }
068
069  @Override
070  public void cacheBlock(BlockCacheKey cacheKey, Cacheable buf, boolean inMemory,
071    boolean waitWhenCache) {
072    boolean metaBlock = isMetaBlock(buf.getBlockType());
073    if (metaBlock) {
074      l1Cache.cacheBlock(cacheKey, buf, inMemory);
075    } else {
076      l2Cache.cacheBlock(cacheKey, buf, inMemory, waitWhenCache);
077    }
078  }
079
080  @Override
081  public void cacheBlock(BlockCacheKey cacheKey, Cacheable buf) {
082    cacheBlock(cacheKey, buf, false);
083  }
084
085  @Override
086  public Cacheable getBlock(BlockCacheKey cacheKey, boolean caching, boolean repeat,
087    boolean updateCacheMetrics) {
088    Cacheable block = null;
089    // We don't know the block type. We should try to get it on one of the caches only,
090    // but not both otherwise we'll over compute on misses. Here we check if the key is on L1,
091    // if so, call getBlock on L1 and that will compute the hit. Otherwise, we'll try to get it from
092    // L2 and whatever happens, we'll update the stats there.
093    boolean existInL1 = l1Cache.containsBlock(cacheKey);
094    // if we know it's in L1, just delegate call to l1 and return it
095    if (existInL1) {
096      block = l1Cache.getBlock(cacheKey, caching, repeat, false);
097    } else {
098      block = l2Cache.getBlock(cacheKey, caching, repeat, false);
099    }
100    if (updateCacheMetrics) {
101      boolean metaBlock = isMetaBlock(cacheKey.getBlockType());
102      if (metaBlock) {
103        if (!existInL1 && block != null) {
104          LOG.warn("Cache key {} had block type {}, but was found in L2 cache.", cacheKey,
105            cacheKey.getBlockType());
106          updateBlockMetrics(block, cacheKey, l2Cache, caching);
107        } else {
108          updateBlockMetrics(block, cacheKey, l1Cache, caching);
109        }
110      } else {
111        if (existInL1) {
112          updateBlockMetrics(block, cacheKey, l1Cache, caching);
113        } else {
114          updateBlockMetrics(block, cacheKey, l2Cache, caching);
115        }
116      }
117    }
118    return block;
119  }
120
121  private void updateBlockMetrics(Cacheable block, BlockCacheKey key, BlockCache cache,
122    boolean caching) {
123    if (block == null) {
124      cache.getStats().miss(caching, key.isPrimary(), key.getBlockType());
125    } else {
126      cache.getStats().hit(caching, key.isPrimary(), key.getBlockType());
127
128    }
129  }
130
131  @Override
132  public Cacheable getBlock(BlockCacheKey cacheKey, boolean caching, boolean repeat,
133    boolean updateCacheMetrics, BlockType blockType) {
134    if (blockType == null) {
135      return getBlock(cacheKey, caching, repeat, updateCacheMetrics);
136    }
137    cacheKey.setBlockType(blockType);
138    return getBlockWithType(cacheKey, caching, repeat, updateCacheMetrics);
139  }
140
141  private Cacheable getBlockWithType(BlockCacheKey cacheKey, boolean caching, boolean repeat,
142    boolean updateCacheMetrics) {
143    boolean metaBlock = isMetaBlock(cacheKey.getBlockType());
144    if (metaBlock) {
145      return l1Cache.getBlock(cacheKey, caching, repeat, updateCacheMetrics);
146    } else {
147      return l2Cache.getBlock(cacheKey, caching, repeat, updateCacheMetrics);
148    }
149  }
150
151  @Override
152  public boolean evictBlock(BlockCacheKey cacheKey) {
153    return l1Cache.evictBlock(cacheKey) || l2Cache.evictBlock(cacheKey);
154  }
155
156  @Override
157  public int evictBlocksByHfileName(String hfileName) {
158    return l1Cache.evictBlocksByHfileName(hfileName) + l2Cache.evictBlocksByHfileName(hfileName);
159  }
160
161  @Override
162  public CacheStats getStats() {
163    return this.combinedCacheStats;
164  }
165
166  @Override
167  public void shutdown() {
168    l1Cache.shutdown();
169    l2Cache.shutdown();
170  }
171
172  @Override
173  public long size() {
174    return l1Cache.size() + l2Cache.size();
175  }
176
177  @Override
178  public long getMaxSize() {
179    return l1Cache.getMaxSize() + l2Cache.getMaxSize();
180  }
181
182  @Override
183  public long getCurrentDataSize() {
184    return l1Cache.getCurrentDataSize() + l2Cache.getCurrentDataSize();
185  }
186
187  @Override
188  public long getFreeSize() {
189    return l1Cache.getFreeSize() + l2Cache.getFreeSize();
190  }
191
192  @Override
193  public long getCurrentSize() {
194    return l1Cache.getCurrentSize() + l2Cache.getCurrentSize();
195  }
196
197  @Override
198  public long getBlockCount() {
199    return l1Cache.getBlockCount() + l2Cache.getBlockCount();
200  }
201
202  @Override
203  public long getDataBlockCount() {
204    return l1Cache.getDataBlockCount() + l2Cache.getDataBlockCount();
205  }
206
207  public static class CombinedCacheStats extends CacheStats {
208    private final CacheStats lruCacheStats;
209    private final CacheStats bucketCacheStats;
210
211    CombinedCacheStats(CacheStats lbcStats, CacheStats fcStats) {
212      super("CombinedBlockCache");
213      this.lruCacheStats = lbcStats;
214      this.bucketCacheStats = fcStats;
215    }
216
217    public CacheStats getLruCacheStats() {
218      return this.lruCacheStats;
219    }
220
221    public CacheStats getBucketCacheStats() {
222      return this.bucketCacheStats;
223    }
224
225    @Override
226    public long getDataMissCount() {
227      return lruCacheStats.getDataMissCount() + bucketCacheStats.getDataMissCount();
228    }
229
230    @Override
231    public long getLeafIndexMissCount() {
232      return lruCacheStats.getLeafIndexMissCount() + bucketCacheStats.getLeafIndexMissCount();
233    }
234
235    @Override
236    public long getBloomChunkMissCount() {
237      return lruCacheStats.getBloomChunkMissCount() + bucketCacheStats.getBloomChunkMissCount();
238    }
239
240    @Override
241    public long getMetaMissCount() {
242      return lruCacheStats.getMetaMissCount() + bucketCacheStats.getMetaMissCount();
243    }
244
245    @Override
246    public long getRootIndexMissCount() {
247      return lruCacheStats.getRootIndexMissCount() + bucketCacheStats.getRootIndexMissCount();
248    }
249
250    @Override
251    public long getIntermediateIndexMissCount() {
252      return lruCacheStats.getIntermediateIndexMissCount()
253        + bucketCacheStats.getIntermediateIndexMissCount();
254    }
255
256    @Override
257    public long getFileInfoMissCount() {
258      return lruCacheStats.getFileInfoMissCount() + bucketCacheStats.getFileInfoMissCount();
259    }
260
261    @Override
262    public long getGeneralBloomMetaMissCount() {
263      return lruCacheStats.getGeneralBloomMetaMissCount()
264        + bucketCacheStats.getGeneralBloomMetaMissCount();
265    }
266
267    @Override
268    public long getDeleteFamilyBloomMissCount() {
269      return lruCacheStats.getDeleteFamilyBloomMissCount()
270        + bucketCacheStats.getDeleteFamilyBloomMissCount();
271    }
272
273    @Override
274    public long getTrailerMissCount() {
275      return lruCacheStats.getTrailerMissCount() + bucketCacheStats.getTrailerMissCount();
276    }
277
278    @Override
279    public long getDataHitCount() {
280      return lruCacheStats.getDataHitCount() + bucketCacheStats.getDataHitCount();
281    }
282
283    @Override
284    public long getLeafIndexHitCount() {
285      return lruCacheStats.getLeafIndexHitCount() + bucketCacheStats.getLeafIndexHitCount();
286    }
287
288    @Override
289    public long getBloomChunkHitCount() {
290      return lruCacheStats.getBloomChunkHitCount() + bucketCacheStats.getBloomChunkHitCount();
291    }
292
293    @Override
294    public long getMetaHitCount() {
295      return lruCacheStats.getMetaHitCount() + bucketCacheStats.getMetaHitCount();
296    }
297
298    @Override
299    public long getRootIndexHitCount() {
300      return lruCacheStats.getRootIndexHitCount() + bucketCacheStats.getRootIndexHitCount();
301    }
302
303    @Override
304    public long getIntermediateIndexHitCount() {
305      return lruCacheStats.getIntermediateIndexHitCount()
306        + bucketCacheStats.getIntermediateIndexHitCount();
307    }
308
309    @Override
310    public long getFileInfoHitCount() {
311      return lruCacheStats.getFileInfoHitCount() + bucketCacheStats.getFileInfoHitCount();
312    }
313
314    @Override
315    public long getGeneralBloomMetaHitCount() {
316      return lruCacheStats.getGeneralBloomMetaHitCount()
317        + bucketCacheStats.getGeneralBloomMetaHitCount();
318    }
319
320    @Override
321    public long getDeleteFamilyBloomHitCount() {
322      return lruCacheStats.getDeleteFamilyBloomHitCount()
323        + bucketCacheStats.getDeleteFamilyBloomHitCount();
324    }
325
326    @Override
327    public long getTrailerHitCount() {
328      return lruCacheStats.getTrailerHitCount() + bucketCacheStats.getTrailerHitCount();
329    }
330
331    @Override
332    public long getRequestCount() {
333      return lruCacheStats.getRequestCount() + bucketCacheStats.getRequestCount();
334    }
335
336    @Override
337    public long getRequestCachingCount() {
338      return lruCacheStats.getRequestCachingCount() + bucketCacheStats.getRequestCachingCount();
339    }
340
341    @Override
342    public long getMissCount() {
343      return lruCacheStats.getMissCount() + bucketCacheStats.getMissCount();
344    }
345
346    @Override
347    public long getPrimaryMissCount() {
348      return lruCacheStats.getPrimaryMissCount() + bucketCacheStats.getPrimaryMissCount();
349    }
350
351    @Override
352    public long getMissCachingCount() {
353      return lruCacheStats.getMissCachingCount() + bucketCacheStats.getMissCachingCount();
354    }
355
356    @Override
357    public long getHitCount() {
358      return lruCacheStats.getHitCount() + bucketCacheStats.getHitCount();
359    }
360
361    @Override
362    public long getPrimaryHitCount() {
363      return lruCacheStats.getPrimaryHitCount() + bucketCacheStats.getPrimaryHitCount();
364    }
365
366    @Override
367    public long getHitCachingCount() {
368      return lruCacheStats.getHitCachingCount() + bucketCacheStats.getHitCachingCount();
369    }
370
371    @Override
372    public long getEvictionCount() {
373      return lruCacheStats.getEvictionCount() + bucketCacheStats.getEvictionCount();
374    }
375
376    @Override
377    public long getEvictedCount() {
378      return lruCacheStats.getEvictedCount() + bucketCacheStats.getEvictedCount();
379    }
380
381    @Override
382    public long getPrimaryEvictedCount() {
383      return lruCacheStats.getPrimaryEvictedCount() + bucketCacheStats.getPrimaryEvictedCount();
384    }
385
386    @Override
387    public void rollMetricsPeriod() {
388      lruCacheStats.rollMetricsPeriod();
389      bucketCacheStats.rollMetricsPeriod();
390    }
391
392    @Override
393    public long getFailedInserts() {
394      return lruCacheStats.getFailedInserts() + bucketCacheStats.getFailedInserts();
395    }
396
397    @Override
398    public long getSumHitCountsPastNPeriods() {
399      return lruCacheStats.getSumHitCountsPastNPeriods()
400        + bucketCacheStats.getSumHitCountsPastNPeriods();
401    }
402
403    @Override
404    public long getSumRequestCountsPastNPeriods() {
405      return lruCacheStats.getSumRequestCountsPastNPeriods()
406        + bucketCacheStats.getSumRequestCountsPastNPeriods();
407    }
408
409    @Override
410    public long getSumHitCachingCountsPastNPeriods() {
411      return lruCacheStats.getSumHitCachingCountsPastNPeriods()
412        + bucketCacheStats.getSumHitCachingCountsPastNPeriods();
413    }
414
415    @Override
416    public long getSumRequestCachingCountsPastNPeriods() {
417      return lruCacheStats.getSumRequestCachingCountsPastNPeriods()
418        + bucketCacheStats.getSumRequestCachingCountsPastNPeriods();
419    }
420  }
421
422  @Override
423  public Iterator<CachedBlock> iterator() {
424    return new BlockCachesIterator(getBlockCaches());
425  }
426
427  @Override
428  public BlockCache[] getBlockCaches() {
429    return new BlockCache[] { this.l1Cache, this.l2Cache };
430  }
431
432  /**
433   * Returns the list of fully cached files
434   */
435  @Override
436  public Optional<Map<String, Pair<String, Long>>> getFullyCachedFiles() {
437    return this.l2Cache.getFullyCachedFiles();
438  }
439
440  @Override
441  public Optional<Map<String, Long>> getRegionCachedInfo() {
442    return l2Cache.getRegionCachedInfo();
443  }
444
445  @Override
446  public void setMaxSize(long size) {
447    this.l1Cache.setMaxSize(size);
448  }
449
450  public int getRpcRefCount(BlockCacheKey cacheKey) {
451    return (this.l2Cache instanceof BucketCache)
452      ? ((BucketCache) this.l2Cache).getRpcRefCount(cacheKey)
453      : 0;
454  }
455
456  public FirstLevelBlockCache getFirstLevelCache() {
457    return l1Cache;
458  }
459
460  public BlockCache getSecondLevelCache() {
461    return l2Cache;
462  }
463
464  @Override
465  public void notifyFileCachingCompleted(Path fileName, int totalBlockCount, int dataBlockCount,
466    long size) {
467    l1Cache.getBlockCount();
468    l1Cache.notifyFileCachingCompleted(fileName, totalBlockCount, dataBlockCount, size);
469    l2Cache.notifyFileCachingCompleted(fileName, totalBlockCount, dataBlockCount, size);
470
471  }
472
473  @Override
474  public Optional<Boolean> blockFitsIntoTheCache(HFileBlock block) {
475    if (isMetaBlock(block.getBlockType())) {
476      return l1Cache.blockFitsIntoTheCache(block);
477    } else {
478      return l2Cache.blockFitsIntoTheCache(block);
479    }
480  }
481
482  @Override
483  public Optional<Boolean> shouldCacheFile(String fileName) {
484    Optional<Boolean> l1Result = l1Cache.shouldCacheFile(fileName);
485    Optional<Boolean> l2Result = l2Cache.shouldCacheFile(fileName);
486    final Mutable<Boolean> combinedResult = new MutableBoolean(true);
487    l1Result.ifPresent(b -> combinedResult.setValue(b && combinedResult.getValue()));
488    l2Result.ifPresent(b -> combinedResult.setValue(b && combinedResult.getValue()));
489    return Optional.of(combinedResult.getValue());
490  }
491
492  @Override
493  public Optional<Boolean> isAlreadyCached(BlockCacheKey key) {
494    boolean result =
495      l1Cache.isAlreadyCached(key).orElseGet(() -> l2Cache.isAlreadyCached(key).orElse(false));
496    return Optional.of(result);
497  }
498
499  @Override
500  public Optional<Integer> getBlockSize(BlockCacheKey key) {
501    Optional<Integer> l1Result = l1Cache.getBlockSize(key);
502    return l1Result.isPresent() ? l1Result : l2Cache.getBlockSize(key);
503  }
504
505  @Override
506  public int evictBlocksRangeByHfileName(String hfileName, long initOffset, long endOffset) {
507    return l1Cache.evictBlocksRangeByHfileName(hfileName, initOffset, endOffset)
508      + l2Cache.evictBlocksRangeByHfileName(hfileName, initOffset, endOffset);
509  }
510
511  @Override
512  public boolean waitForCacheInitialization(long timeout) {
513    return this.l1Cache.waitForCacheInitialization(timeout)
514      && this.l2Cache.waitForCacheInitialization(timeout);
515  }
516
517  @Override
518  public boolean isCacheEnabled() {
519    return l1Cache.isCacheEnabled() && l2Cache.isCacheEnabled();
520  }
521}