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 CacheStats getStats() {
164    return this.combinedCacheStats;
165  }
166
167  @Override
168  public void shutdown() {
169    l1Cache.shutdown();
170    l2Cache.shutdown();
171  }
172
173  @Override
174  public long size() {
175    return l1Cache.size() + l2Cache.size();
176  }
177
178  @Override
179  public long getMaxSize() {
180    return l1Cache.getMaxSize() + l2Cache.getMaxSize();
181  }
182
183  @Override
184  public long getCurrentDataSize() {
185    return l1Cache.getCurrentDataSize() + l2Cache.getCurrentDataSize();
186  }
187
188  @Override
189  public long getFreeSize() {
190    return l1Cache.getFreeSize() + l2Cache.getFreeSize();
191  }
192
193  @Override
194  public long getCurrentSize() {
195    return l1Cache.getCurrentSize() + l2Cache.getCurrentSize();
196  }
197
198  @Override
199  public long getBlockCount() {
200    return l1Cache.getBlockCount() + l2Cache.getBlockCount();
201  }
202
203  @Override
204  public long getDataBlockCount() {
205    return l1Cache.getDataBlockCount() + l2Cache.getDataBlockCount();
206  }
207
208  public static class CombinedCacheStats extends CacheStats {
209    private final CacheStats lruCacheStats;
210    private final CacheStats bucketCacheStats;
211
212    CombinedCacheStats(CacheStats lbcStats, CacheStats fcStats) {
213      super("CombinedBlockCache");
214      this.lruCacheStats = lbcStats;
215      this.bucketCacheStats = fcStats;
216    }
217
218    public CacheStats getLruCacheStats() {
219      return this.lruCacheStats;
220    }
221
222    public CacheStats getBucketCacheStats() {
223      return this.bucketCacheStats;
224    }
225
226    @Override
227    public long getDataMissCount() {
228      return lruCacheStats.getDataMissCount() + bucketCacheStats.getDataMissCount();
229    }
230
231    @Override
232    public long getLeafIndexMissCount() {
233      return lruCacheStats.getLeafIndexMissCount() + bucketCacheStats.getLeafIndexMissCount();
234    }
235
236    @Override
237    public long getBloomChunkMissCount() {
238      return lruCacheStats.getBloomChunkMissCount() + bucketCacheStats.getBloomChunkMissCount();
239    }
240
241    @Override
242    public long getMetaMissCount() {
243      return lruCacheStats.getMetaMissCount() + bucketCacheStats.getMetaMissCount();
244    }
245
246    @Override
247    public long getRootIndexMissCount() {
248      return lruCacheStats.getRootIndexMissCount() + bucketCacheStats.getRootIndexMissCount();
249    }
250
251    @Override
252    public long getIntermediateIndexMissCount() {
253      return lruCacheStats.getIntermediateIndexMissCount()
254        + bucketCacheStats.getIntermediateIndexMissCount();
255    }
256
257    @Override
258    public long getFileInfoMissCount() {
259      return lruCacheStats.getFileInfoMissCount() + bucketCacheStats.getFileInfoMissCount();
260    }
261
262    @Override
263    public long getGeneralBloomMetaMissCount() {
264      return lruCacheStats.getGeneralBloomMetaMissCount()
265        + bucketCacheStats.getGeneralBloomMetaMissCount();
266    }
267
268    @Override
269    public long getDeleteFamilyBloomMissCount() {
270      return lruCacheStats.getDeleteFamilyBloomMissCount()
271        + bucketCacheStats.getDeleteFamilyBloomMissCount();
272    }
273
274    @Override
275    public long getTrailerMissCount() {
276      return lruCacheStats.getTrailerMissCount() + bucketCacheStats.getTrailerMissCount();
277    }
278
279    @Override
280    public long getDataHitCount() {
281      return lruCacheStats.getDataHitCount() + bucketCacheStats.getDataHitCount();
282    }
283
284    @Override
285    public long getLeafIndexHitCount() {
286      return lruCacheStats.getLeafIndexHitCount() + bucketCacheStats.getLeafIndexHitCount();
287    }
288
289    @Override
290    public long getBloomChunkHitCount() {
291      return lruCacheStats.getBloomChunkHitCount() + bucketCacheStats.getBloomChunkHitCount();
292    }
293
294    @Override
295    public long getMetaHitCount() {
296      return lruCacheStats.getMetaHitCount() + bucketCacheStats.getMetaHitCount();
297    }
298
299    @Override
300    public long getRootIndexHitCount() {
301      return lruCacheStats.getRootIndexHitCount() + bucketCacheStats.getRootIndexHitCount();
302    }
303
304    @Override
305    public long getIntermediateIndexHitCount() {
306      return lruCacheStats.getIntermediateIndexHitCount()
307        + bucketCacheStats.getIntermediateIndexHitCount();
308    }
309
310    @Override
311    public long getFileInfoHitCount() {
312      return lruCacheStats.getFileInfoHitCount() + bucketCacheStats.getFileInfoHitCount();
313    }
314
315    @Override
316    public long getGeneralBloomMetaHitCount() {
317      return lruCacheStats.getGeneralBloomMetaHitCount()
318        + bucketCacheStats.getGeneralBloomMetaHitCount();
319    }
320
321    @Override
322    public long getDeleteFamilyBloomHitCount() {
323      return lruCacheStats.getDeleteFamilyBloomHitCount()
324        + bucketCacheStats.getDeleteFamilyBloomHitCount();
325    }
326
327    @Override
328    public long getTrailerHitCount() {
329      return lruCacheStats.getTrailerHitCount() + bucketCacheStats.getTrailerHitCount();
330    }
331
332    @Override
333    public long getRequestCount() {
334      return lruCacheStats.getRequestCount() + bucketCacheStats.getRequestCount();
335    }
336
337    @Override
338    public long getRequestCachingCount() {
339      return lruCacheStats.getRequestCachingCount() + bucketCacheStats.getRequestCachingCount();
340    }
341
342    @Override
343    public long getMissCount() {
344      return lruCacheStats.getMissCount() + bucketCacheStats.getMissCount();
345    }
346
347    @Override
348    public long getPrimaryMissCount() {
349      return lruCacheStats.getPrimaryMissCount() + bucketCacheStats.getPrimaryMissCount();
350    }
351
352    @Override
353    public long getMissCachingCount() {
354      return lruCacheStats.getMissCachingCount() + bucketCacheStats.getMissCachingCount();
355    }
356
357    @Override
358    public long getHitCount() {
359      return lruCacheStats.getHitCount() + bucketCacheStats.getHitCount();
360    }
361
362    @Override
363    public long getPrimaryHitCount() {
364      return lruCacheStats.getPrimaryHitCount() + bucketCacheStats.getPrimaryHitCount();
365    }
366
367    @Override
368    public long getHitCachingCount() {
369      return lruCacheStats.getHitCachingCount() + bucketCacheStats.getHitCachingCount();
370    }
371
372    @Override
373    public long getEvictionCount() {
374      return lruCacheStats.getEvictionCount() + bucketCacheStats.getEvictionCount();
375    }
376
377    @Override
378    public long getEvictedCount() {
379      return lruCacheStats.getEvictedCount() + bucketCacheStats.getEvictedCount();
380    }
381
382    @Override
383    public long getPrimaryEvictedCount() {
384      return lruCacheStats.getPrimaryEvictedCount() + bucketCacheStats.getPrimaryEvictedCount();
385    }
386
387    @Override
388    public void rollMetricsPeriod() {
389      lruCacheStats.rollMetricsPeriod();
390      bucketCacheStats.rollMetricsPeriod();
391    }
392
393    @Override
394    public long getFailedInserts() {
395      return lruCacheStats.getFailedInserts() + bucketCacheStats.getFailedInserts();
396    }
397
398    @Override
399    public long getSumHitCountsPastNPeriods() {
400      return lruCacheStats.getSumHitCountsPastNPeriods()
401        + bucketCacheStats.getSumHitCountsPastNPeriods();
402    }
403
404    @Override
405    public long getSumRequestCountsPastNPeriods() {
406      return lruCacheStats.getSumRequestCountsPastNPeriods()
407        + bucketCacheStats.getSumRequestCountsPastNPeriods();
408    }
409
410    @Override
411    public long getSumHitCachingCountsPastNPeriods() {
412      return lruCacheStats.getSumHitCachingCountsPastNPeriods()
413        + bucketCacheStats.getSumHitCachingCountsPastNPeriods();
414    }
415
416    @Override
417    public long getSumRequestCachingCountsPastNPeriods() {
418      return lruCacheStats.getSumRequestCachingCountsPastNPeriods()
419        + bucketCacheStats.getSumRequestCachingCountsPastNPeriods();
420    }
421  }
422
423  @Override
424  public Iterator<CachedBlock> iterator() {
425    return new BlockCachesIterator(getBlockCaches());
426  }
427
428  @Override
429  public BlockCache[] getBlockCaches() {
430    return new BlockCache[] { this.l1Cache, this.l2Cache };
431  }
432
433  /**
434   * Returns the list of fully cached files
435   */
436  @Override
437  public Optional<Map<String, Pair<String, Long>>> getFullyCachedFiles() {
438    return this.l2Cache.getFullyCachedFiles();
439  }
440
441  @Override
442  public Optional<Map<String, Long>> getRegionCachedInfo() {
443    return l2Cache.getRegionCachedInfo();
444  }
445
446  @Override
447  public void setMaxSize(long size) {
448    this.l1Cache.setMaxSize(size);
449  }
450
451  public int getRpcRefCount(BlockCacheKey cacheKey) {
452    return (this.l2Cache instanceof BucketCache)
453      ? ((BucketCache) this.l2Cache).getRpcRefCount(cacheKey)
454      : 0;
455  }
456
457  public FirstLevelBlockCache getFirstLevelCache() {
458    return l1Cache;
459  }
460
461  public BlockCache getSecondLevelCache() {
462    return l2Cache;
463  }
464
465  @Override
466  public void notifyFileCachingCompleted(Path fileName, int totalBlockCount, int dataBlockCount,
467    long size) {
468    l1Cache.getBlockCount();
469    l1Cache.notifyFileCachingCompleted(fileName, totalBlockCount, dataBlockCount, size);
470    l2Cache.notifyFileCachingCompleted(fileName, totalBlockCount, dataBlockCount, size);
471
472  }
473
474  @Override
475  public void onConfigurationChange(Configuration config) {
476    l1Cache.onConfigurationChange(config);
477    l2Cache.onConfigurationChange(config);
478  }
479
480  @Override
481  public Optional<Boolean> blockFitsIntoTheCache(HFileBlock block) {
482    if (isMetaBlock(block.getBlockType())) {
483      return l1Cache.blockFitsIntoTheCache(block);
484    } else {
485      return l2Cache.blockFitsIntoTheCache(block);
486    }
487  }
488
489  @Override
490  public Optional<Boolean> shouldCacheFile(String fileName) {
491    Optional<Boolean> l1Result = l1Cache.shouldCacheFile(fileName);
492    Optional<Boolean> l2Result = l2Cache.shouldCacheFile(fileName);
493    final Mutable<Boolean> combinedResult = new MutableBoolean(true);
494    l1Result.ifPresent(b -> combinedResult.setValue(b && combinedResult.getValue()));
495    l2Result.ifPresent(b -> combinedResult.setValue(b && combinedResult.getValue()));
496    return Optional.of(combinedResult.getValue());
497  }
498
499  @Override
500  public Optional<Boolean> isAlreadyCached(BlockCacheKey key) {
501    boolean result =
502      l1Cache.isAlreadyCached(key).orElseGet(() -> l2Cache.isAlreadyCached(key).orElse(false));
503    return Optional.of(result);
504  }
505
506  @Override
507  public Optional<Integer> getBlockSize(BlockCacheKey key) {
508    Optional<Integer> l1Result = l1Cache.getBlockSize(key);
509    return l1Result.isPresent() ? l1Result : l2Cache.getBlockSize(key);
510  }
511
512  @Override
513  public int evictBlocksRangeByHfileName(String hfileName, long initOffset, long endOffset) {
514    return l1Cache.evictBlocksRangeByHfileName(hfileName, initOffset, endOffset)
515      + l2Cache.evictBlocksRangeByHfileName(hfileName, initOffset, endOffset);
516  }
517
518  @Override
519  public boolean waitForCacheInitialization(long timeout) {
520    return this.l1Cache.waitForCacheInitialization(timeout)
521      && this.l2Cache.waitForCacheInitialization(timeout);
522  }
523
524  @Override
525  public boolean isCacheEnabled() {
526    return l1Cache.isCacheEnabled() && l2Cache.isCacheEnabled();
527  }
528}