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