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