View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.io.hfile;
19  
20  import java.io.IOException;
21  import java.nio.ByteBuffer;
22  import java.util.NavigableMap;
23  import java.util.NavigableSet;
24  import java.util.concurrent.ConcurrentSkipListMap;
25  import java.util.concurrent.ConcurrentSkipListSet;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.hbase.classification.InterfaceAudience;
30  import org.apache.hadoop.conf.Configuration;
31  import org.apache.hadoop.hbase.metrics.impl.FastLongHistogram;
32  import org.apache.hadoop.hbase.util.Bytes;
33  import org.codehaus.jackson.JsonGenerationException;
34  import org.codehaus.jackson.annotate.JsonIgnoreProperties;
35  import org.codehaus.jackson.map.JsonMappingException;
36  import org.codehaus.jackson.map.ObjectMapper;
37  import org.codehaus.jackson.map.SerializationConfig;
38  
39  /**
40   * Utilty for aggregating counts in CachedBlocks and toString/toJSON CachedBlocks and BlockCaches.
41   * No attempt has been made at making this thread safe.
42   */
43  @InterfaceAudience.Private
44  public class BlockCacheUtil {
45  
46    private static final Log LOG = LogFactory.getLog(BlockCacheUtil.class);
47  
48    public static final long NANOS_PER_SECOND = 1000000000;
49  
50    /**
51     * Needed generating JSON.
52     */
53    private static final ObjectMapper MAPPER = new ObjectMapper();
54    static {
55      MAPPER.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
56      MAPPER.configure(SerializationConfig.Feature.FLUSH_AFTER_WRITE_VALUE, true);
57      MAPPER.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
58    }
59  
60    /**
61     * @param cb
62     * @return The block content as String.
63     */
64    public static String toString(final CachedBlock cb, final long now) {
65      return "filename=" + cb.getFilename() + ", " + toStringMinusFileName(cb, now);
66    }
67  
68    /**
69     * Little data structure to hold counts for a file.
70     * Used doing a toJSON.
71     */
72    static class CachedBlockCountsPerFile {
73      private int count = 0;
74      private long size = 0;
75      private int countData = 0;
76      private long sizeData = 0;
77      private final String filename;
78  
79      CachedBlockCountsPerFile(final String filename) {
80        this.filename = filename;
81      }
82  
83      public int getCount() {
84        return count;
85      }
86  
87      public long getSize() {
88        return size;
89      }
90  
91      public int getCountData() {
92        return countData;
93      }
94  
95      public long getSizeData() {
96        return sizeData;
97      }
98  
99      public String getFilename() {
100       return filename;
101     }
102   }
103 
104   /**
105    * @param filename
106    * @param blocks
107    * @return A JSON String of <code>filename</code> and counts of <code>blocks</code>
108    * @throws JsonGenerationException
109    * @throws JsonMappingException
110    * @throws IOException
111    */
112   public static String toJSON(final String filename, final NavigableSet<CachedBlock> blocks)
113   throws JsonGenerationException, JsonMappingException, IOException {
114     CachedBlockCountsPerFile counts = new CachedBlockCountsPerFile(filename);
115     for (CachedBlock cb: blocks) {
116       counts.count++;
117       counts.size += cb.getSize();
118       BlockType bt = cb.getBlockType();
119       if (bt != null && bt.isData()) {
120         counts.countData++;
121         counts.sizeData += cb.getSize();
122       }
123     }
124     return MAPPER.writeValueAsString(counts);
125   }
126 
127   /**
128    * @param cbsbf
129    * @return JSON string of <code>cbsf</code> aggregated
130    * @throws JsonGenerationException
131    * @throws JsonMappingException
132    * @throws IOException
133    */
134   public static String toJSON(final CachedBlocksByFile cbsbf)
135   throws JsonGenerationException, JsonMappingException, IOException {
136     return MAPPER.writeValueAsString(cbsbf);
137   }
138 
139   /**
140    * @param bc
141    * @return JSON string of <code>bc</code> content.
142    * @throws JsonGenerationException
143    * @throws JsonMappingException
144    * @throws IOException
145    */
146   public static String toJSON(final BlockCache bc)
147   throws JsonGenerationException, JsonMappingException, IOException {
148     return MAPPER.writeValueAsString(bc);
149   }
150 
151   /**
152    * @param cb
153    * @return The block content of <code>bc</code> as a String minus the filename.
154    */
155   public static String toStringMinusFileName(final CachedBlock cb, final long now) {
156     return "offset=" + cb.getOffset() +
157       ", size=" + cb.getSize() +
158       ", age=" + (now - cb.getCachedTime()) +
159       ", type=" + cb.getBlockType() +
160       ", priority=" + cb.getBlockPriority();
161   }
162 
163   /**
164    * Get a {@link CachedBlocksByFile} instance and load it up by iterating content in
165    * {@link BlockCache}.
166    * @param conf Used to read configurations
167    * @param bc Block Cache to iterate.
168    * @return Laoded up instance of CachedBlocksByFile
169    */
170   public static CachedBlocksByFile getLoadedCachedBlocksByFile(final Configuration conf,
171       final BlockCache bc) {
172     CachedBlocksByFile cbsbf = new CachedBlocksByFile(conf);
173     for (CachedBlock cb: bc) {
174       if (cbsbf.update(cb)) break;
175     }
176     return cbsbf;
177   }
178 
179   private static int compareCacheBlock(Cacheable left, Cacheable right,
180                                        boolean includeNextBlockOnDiskSize) {
181     ByteBuffer l = ByteBuffer.allocate(left.getSerializedLength());
182     left.serialize(l, includeNextBlockOnDiskSize);
183     ByteBuffer r = ByteBuffer.allocate(right.getSerializedLength());
184     right.serialize(r, includeNextBlockOnDiskSize);
185     return Bytes.compareTo(l.array(), l.arrayOffset(), l.limit(),
186              r.array(), r.arrayOffset(), r.limit());
187   }
188 
189   /**
190    * Validate that the existing and newBlock are the same without including the nextBlockMetadata,
191    * if not, throw an exception. If they are the same without the nextBlockMetadata, return the
192    * comparison.
193    * @param existing block that is existing in the cache.
194    * @param newBlock block that is trying to be cached.
195    * @param cacheKey the cache key of the blocks.
196    * @return comparison of the existing block to the newBlock.
197    */
198   public static int validateBlockAddition(Cacheable existing, Cacheable newBlock,
199       BlockCacheKey cacheKey) {
200     int comparison = compareCacheBlock(existing, newBlock, false);
201     if (comparison != 0) {
202       throw new RuntimeException(
203           "Cached block contents differ, which should not have happened." + "cacheKey:" + cacheKey);
204     }
205     if ((existing instanceof HFileBlock) && (newBlock instanceof HFileBlock)) {
206       comparison = ((HFileBlock) existing).getNextBlockOnDiskSize()
207           - ((HFileBlock) newBlock).getNextBlockOnDiskSize();
208     }
209     return comparison;
210   }
211 
212   /**
213    * Because of the region splitting, it's possible that the split key locate in the middle of a
214    * block. So it's possible that both the daughter regions load the same block from their parent
215    * HFile. When pread, we don't force the read to read all of the next block header. So when two
216    * threads try to cache the same block, it's possible that one thread read all of the next block
217    * header but the other one didn't. if the already cached block hasn't next block header but the
218    * new block to cache has, then we can replace the existing block with the new block for better
219    * performance.(HBASE-20447)
220    * @param blockCache BlockCache to check
221    * @param cacheKey the block cache key
222    * @param newBlock the new block which try to put into the block cache.
223    * @return true means need to replace existing block with new block for the same block cache key.
224    *         false means just keep the existing block.
225    */
226   public static boolean shouldReplaceExistingCacheBlock(BlockCache blockCache,
227       BlockCacheKey cacheKey, Cacheable newBlock) {
228     Cacheable existingBlock = blockCache.getBlock(cacheKey, false, false, false);
229     int comparison = BlockCacheUtil.validateBlockAddition(existingBlock, newBlock, cacheKey);
230     if (comparison < 0) {
231       LOG.warn("Cached block contents differ by nextBlockOnDiskSize, the new block has "
232           + "nextBlockOnDiskSize set. Caching new block.");
233       return true;
234     } else if (comparison > 0) {
235       LOG.warn("Cached block contents differ by nextBlockOnDiskSize, the existing block has "
236           + "nextBlockOnDiskSize set, Keeping cached block.");
237       return false;
238     } else {
239       LOG.warn("Caching an already cached block: " + cacheKey
240           + ". This is harmless and can happen in rare " + "cases (see HBASE-8547)");
241       return false;
242     }
243   }
244 
245   /**
246    * Use one of these to keep a running account of cached blocks by file.  Throw it away when done.
247    * This is different than metrics in that it is stats on current state of a cache.
248    * See getLoadedCachedBlocksByFile
249    */
250   @JsonIgnoreProperties({"cachedBlockStatsByFile"})
251   public static class CachedBlocksByFile {
252     private int count;
253     private int dataBlockCount;
254     private long size;
255     private long dataSize;
256     private final long now = System.nanoTime();
257     /**
258      * How many blocks to look at before we give up.
259      * There could be many millions of blocks. We don't want the
260      * ui to freeze while we run through 1B blocks... users will
261      * think hbase dead. UI displays warning in red when stats
262      * are incomplete.
263      */
264     private final int max;
265     public static final int DEFAULT_MAX = 1000000;
266 
267     CachedBlocksByFile() {
268       this(null);
269     }
270 
271     CachedBlocksByFile(final Configuration c) {
272       this.max = c == null? DEFAULT_MAX: c.getInt("hbase.ui.blockcache.by.file.max", DEFAULT_MAX);
273     }
274 
275     /**
276      * Map by filename. use concurent utils because we want our Map and contained blocks sorted.
277      */
278     private NavigableMap<String, NavigableSet<CachedBlock>> cachedBlockByFile =
279       new ConcurrentSkipListMap<String, NavigableSet<CachedBlock>>();
280     FastLongHistogram hist = new FastLongHistogram();
281 
282     /**
283      * @param cb
284      * @return True if full.... if we won't be adding any more.
285      */
286     public boolean update(final CachedBlock cb) {
287       if (isFull()) return true;
288       NavigableSet<CachedBlock> set = this.cachedBlockByFile.get(cb.getFilename());
289       if (set == null) {
290         set = new ConcurrentSkipListSet<CachedBlock>();
291         this.cachedBlockByFile.put(cb.getFilename(), set);
292       }
293       set.add(cb);
294       this.size += cb.getSize();
295       this.count++;
296       BlockType bt = cb.getBlockType();
297       if (bt != null && bt.isData()) {
298         this.dataBlockCount++;
299         this.dataSize += cb.getSize();
300       }
301       long age = (this.now - cb.getCachedTime())/NANOS_PER_SECOND;
302       this.hist.add(age, 1);
303       return false;
304     }
305 
306     /**
307      * @return True if full; i.e. there are more items in the cache but we only loaded up
308      * the maximum set in configuration <code>hbase.ui.blockcache.by.file.max</code>
309      * (Default: DEFAULT_MAX).
310      */
311     public boolean isFull() {
312       return this.count >= this.max;
313     }
314 
315     public NavigableMap<String, NavigableSet<CachedBlock>> getCachedBlockStatsByFile() {
316       return this.cachedBlockByFile;
317     }
318 
319     /**
320      * @return count of blocks in the cache
321      */
322     public int getCount() {
323       return count;
324     }
325 
326     public int getDataCount() {
327       return dataBlockCount;
328     }
329 
330     /**
331      * @return size of blocks in the cache
332      */
333     public long getSize() {
334       return size;
335     }
336 
337     /**
338      * @return Size of data.
339      */
340     public long getDataSize() {
341       return dataSize;
342     }
343 
344     public AgeSnapshot getAgeInCacheSnapshot() {
345       return new AgeSnapshot(this.hist);
346     }
347 
348     @Override
349     public String toString() {
350       AgeSnapshot snapshot = getAgeInCacheSnapshot();
351       return "count=" + count + ", dataBlockCount=" + dataBlockCount + ", size=" + size +
352           ", dataSize=" + getDataSize() +
353           ", mean age=" + snapshot.getMean() +
354           ", min age=" + snapshot.getMin() +
355           ", max age=" + snapshot.getMax() +
356           ", 75th percentile age="   + snapshot.get75thPercentile() +
357           ", 95th percentile age="   + snapshot.get95thPercentile() +
358           ", 98th percentile age="   + snapshot.get98thPercentile() +
359           ", 99th percentile age="   + snapshot.get99thPercentile() +
360           ", 99.9th percentile age=" + snapshot.get99thPercentile();
361     }
362   }
363 }