View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.regionserver;
20  
21  import java.io.DataInput;
22  import java.io.IOException;
23  import java.net.InetSocketAddress;
24  import java.nio.ByteBuffer;
25  import java.util.Arrays;
26  import java.util.Collection;
27  import java.util.Collections;
28  import java.util.Comparator;
29  import java.util.Map;
30  import java.util.SortedSet;
31  import java.util.UUID;
32  import java.util.concurrent.atomic.AtomicBoolean;
33  import java.util.concurrent.atomic.AtomicInteger;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.apache.hadoop.conf.Configuration;
38  import org.apache.hadoop.fs.FileSystem;
39  import org.apache.hadoop.fs.Path;
40  import org.apache.hadoop.hbase.Cell;
41  import org.apache.hadoop.hbase.CellUtil;
42  import org.apache.hadoop.hbase.HBaseInterfaceAudience;
43  import org.apache.hadoop.hbase.HConstants;
44  import org.apache.hadoop.hbase.HDFSBlocksDistribution;
45  import org.apache.hadoop.hbase.KeyValue;
46  import org.apache.hadoop.hbase.KeyValue.KVComparator;
47  import org.apache.hadoop.hbase.KeyValueUtil;
48  import org.apache.hadoop.hbase.classification.InterfaceAudience;
49  import org.apache.hadoop.hbase.client.Scan;
50  import org.apache.hadoop.hbase.io.FSDataInputStreamWrapper;
51  import org.apache.hadoop.hbase.io.TimeRange;
52  import org.apache.hadoop.hbase.io.hfile.BlockType;
53  import org.apache.hadoop.hbase.io.hfile.CacheConfig;
54  import org.apache.hadoop.hbase.io.hfile.HFile;
55  import org.apache.hadoop.hbase.io.hfile.HFileContext;
56  import org.apache.hadoop.hbase.io.hfile.HFileScanner;
57  import org.apache.hadoop.hbase.io.hfile.HFileWriterV2;
58  import org.apache.hadoop.hbase.regionserver.compactions.Compactor;
59  import org.apache.hadoop.hbase.util.BloomFilter;
60  import org.apache.hadoop.hbase.util.BloomFilterFactory;
61  import org.apache.hadoop.hbase.util.BloomFilterWriter;
62  import org.apache.hadoop.hbase.util.Bytes;
63  import org.apache.hadoop.io.WritableUtils;
64  
65  import com.google.common.annotations.VisibleForTesting;
66  import com.google.common.base.Function;
67  import com.google.common.base.Preconditions;
68  import com.google.common.collect.ImmutableList;
69  import com.google.common.collect.Ordering;
70  
71  /**
72   * A Store data file.  Stores usually have one or more of these files.  They
73   * are produced by flushing the memstore to disk.  To
74   * create, instantiate a writer using {@link StoreFile.WriterBuilder}
75   * and append data. Be sure to add any metadata before calling close on the
76   * Writer (Use the appendMetadata convenience methods). On close, a StoreFile
77   * is sitting in the Filesystem.  To refer to it, create a StoreFile instance
78   * passing filesystem and path.  To read, call {@link #createReader()}.
79   * <p>StoreFiles may also reference store files in another Store.
80   *
81   * The reason for this weird pattern where you use a different instance for the
82   * writer and a reader is that we write once but read a lot more.
83   */
84  @InterfaceAudience.LimitedPrivate("Coprocessor")
85  public class StoreFile {
86    private static final Log LOG = LogFactory.getLog(StoreFile.class.getName());
87  
88    // Keys for fileinfo values in HFile
89  
90    /** Max Sequence ID in FileInfo */
91    public static final byte [] MAX_SEQ_ID_KEY = Bytes.toBytes("MAX_SEQ_ID_KEY");
92  
93    /** Major compaction flag in FileInfo */
94    public static final byte[] MAJOR_COMPACTION_KEY =
95        Bytes.toBytes("MAJOR_COMPACTION_KEY");
96  
97    /** Minor compaction flag in FileInfo */
98    public static final byte[] EXCLUDE_FROM_MINOR_COMPACTION_KEY =
99        Bytes.toBytes("EXCLUDE_FROM_MINOR_COMPACTION");
100 
101   /** Bloom filter Type in FileInfo */
102   public static final byte[] BLOOM_FILTER_TYPE_KEY =
103       Bytes.toBytes("BLOOM_FILTER_TYPE");
104 
105   /** Delete Family Count in FileInfo */
106   public static final byte[] DELETE_FAMILY_COUNT =
107       Bytes.toBytes("DELETE_FAMILY_COUNT");
108 
109   /** Last Bloom filter key in FileInfo */
110   private static final byte[] LAST_BLOOM_KEY = Bytes.toBytes("LAST_BLOOM_KEY");
111 
112   /** Key for Timerange information in metadata*/
113   public static final byte[] TIMERANGE_KEY = Bytes.toBytes("TIMERANGE");
114 
115   /** Key for timestamp of earliest-put in metadata*/
116   public static final byte[] EARLIEST_PUT_TS = Bytes.toBytes("EARLIEST_PUT_TS");
117 
118   private final StoreFileInfo fileInfo;
119   private final FileSystem fs;
120 
121   // Block cache configuration and reference.
122   private final CacheConfig cacheConf;
123 
124   // Keys for metadata stored in backing HFile.
125   // Set when we obtain a Reader.
126   private long sequenceid = -1;
127 
128   // max of the MemstoreTS in the KV's in this store
129   // Set when we obtain a Reader.
130   private long maxMemstoreTS = -1;
131 
132   // firstKey, lastkey and cellComparator will be set when openReader.
133   private byte[] firstKey;
134 
135   private byte[] lastKey;
136 
137   private KVComparator comparator;
138 
139   CacheConfig getCacheConf() {
140     return cacheConf;
141   }
142 
143   public byte[] getFirstKey() {
144     return firstKey;
145   }
146 
147   public byte[] getLastKey() {
148     return lastKey;
149   }
150 
151   public KVComparator getComparator() {
152     return comparator;
153   }
154 
155   public long getMaxMemstoreTS() {
156     return maxMemstoreTS;
157   }
158 
159   public void setMaxMemstoreTS(long maxMemstoreTS) {
160     this.maxMemstoreTS = maxMemstoreTS;
161   }
162 
163   // If true, this file was product of a major compaction.  Its then set
164   // whenever you get a Reader.
165   private AtomicBoolean majorCompaction = null;
166 
167   // If true, this file should not be included in minor compactions.
168   // It's set whenever you get a Reader.
169   private boolean excludeFromMinorCompaction = false;
170 
171   /** Meta key set when store file is a result of a bulk load */
172   public static final byte[] BULKLOAD_TASK_KEY =
173     Bytes.toBytes("BULKLOAD_SOURCE_TASK");
174   public static final byte[] BULKLOAD_TIME_KEY =
175     Bytes.toBytes("BULKLOAD_TIMESTAMP");
176 
177   /**
178    * Map of the metadata entries in the corresponding HFile. Populated when Reader is opened
179    * after which it is not modified again.
180    */
181   private Map<byte[], byte[]> metadataMap;
182 
183   // StoreFile.Reader
184   private volatile Reader reader;
185 
186   /**
187    * Bloom filter type specified in column family configuration. Does not
188    * necessarily correspond to the Bloom filter type present in the HFile.
189    */
190   private final BloomType cfBloomType;
191 
192   /**
193    * Constructor, loads a reader and it's indices, etc. May allocate a
194    * substantial amount of ram depending on the underlying files (10-20MB?).
195    *
196    * @param fs  The current file system to use.
197    * @param p  The path of the file.
198    * @param conf  The current configuration.
199    * @param cacheConf  The cache configuration and block cache reference.
200    * @param cfBloomType The bloom type to use for this store file as specified
201    *          by column family configuration. This may or may not be the same
202    *          as the Bloom filter type actually present in the HFile, because
203    *          column family configuration might change. If this is
204    *          {@link BloomType#NONE}, the existing Bloom filter is ignored.
205    * @throws IOException When opening the reader fails.
206    */
207   public StoreFile(final FileSystem fs, final Path p, final Configuration conf,
208         final CacheConfig cacheConf, final BloomType cfBloomType) throws IOException {
209     this(fs, new StoreFileInfo(conf, fs, p), conf, cacheConf, cfBloomType);
210   }
211 
212 
213   /**
214    * Constructor, loads a reader and it's indices, etc. May allocate a
215    * substantial amount of ram depending on the underlying files (10-20MB?).
216    *
217    * @param fs  The current file system to use.
218    * @param fileInfo  The store file information.
219    * @param conf  The current configuration.
220    * @param cacheConf  The cache configuration and block cache reference.
221    * @param cfBloomType The bloom type to use for this store file as specified
222    *          by column family configuration. This may or may not be the same
223    *          as the Bloom filter type actually present in the HFile, because
224    *          column family configuration might change. If this is
225    *          {@link BloomType#NONE}, the existing Bloom filter is ignored.
226    * @throws IOException When opening the reader fails.
227    */
228   public StoreFile(final FileSystem fs, final StoreFileInfo fileInfo, final Configuration conf,
229       final CacheConfig cacheConf,  final BloomType cfBloomType) throws IOException {
230     this.fs = fs;
231     this.fileInfo = fileInfo;
232     this.cacheConf = cacheConf;
233 
234     if (BloomFilterFactory.isGeneralBloomEnabled(conf)) {
235       this.cfBloomType = cfBloomType;
236     } else {
237       LOG.info("Ignoring bloom filter check for file " + this.getPath() + ": " +
238           "cfBloomType=" + cfBloomType + " (disabled in config)");
239       this.cfBloomType = BloomType.NONE;
240     }
241   }
242 
243   /**
244    * Clone
245    * @param other The StoreFile to clone from
246    */
247   public StoreFile(final StoreFile other) {
248     this.fs = other.fs;
249     this.fileInfo = other.fileInfo;
250     this.cacheConf = other.cacheConf;
251     this.cfBloomType = other.cfBloomType;
252     this.metadataMap = other.metadataMap;
253   }
254 
255   /**
256    * Clone a StoreFile for opening private reader.
257    */
258   public StoreFile cloneForReader() {
259     return new StoreFile(this);
260   }
261 
262   /**
263    * @return the StoreFile object associated to this StoreFile.
264    *         null if the StoreFile is not a reference.
265    */
266   public StoreFileInfo getFileInfo() {
267     return this.fileInfo;
268   }
269 
270   /**
271    * @return Path or null if this StoreFile was made with a Stream.
272    */
273   public Path getPath() {
274     return this.fileInfo.getPath();
275   }
276 
277   /**
278    * @return Returns the qualified path of this StoreFile
279    */
280   public Path getQualifiedPath() {
281     return this.fileInfo.getPath().makeQualified(fs);
282   }
283 
284   /**
285    * @return True if this is a StoreFile Reference; call
286    * after {@link #open(boolean canUseDropBehind)} else may get wrong answer.
287    */
288   public boolean isReference() {
289     return this.fileInfo.isReference();
290   }
291 
292   /**
293    * @return True if this is HFile.
294    */
295   public boolean isHFile() {
296     return StoreFileInfo.isHFile(this.fileInfo.getPath());
297   }
298 
299   /**
300    * @return True if this file was made by a major compaction.
301    */
302   public boolean isMajorCompaction() {
303     if (this.majorCompaction == null) {
304       throw new NullPointerException("This has not been set yet");
305     }
306     return this.majorCompaction.get();
307   }
308 
309   /**
310    * @return True if this file should not be part of a minor compaction.
311    */
312   public boolean excludeFromMinorCompaction() {
313     return this.excludeFromMinorCompaction;
314   }
315 
316   /**
317    * @return This files maximum edit sequence id.
318    */
319   public long getMaxSequenceId() {
320     return this.sequenceid;
321   }
322 
323   public long getModificationTimeStamp() throws IOException {
324     return (fileInfo == null) ? 0 : fileInfo.getModificationTime();
325   }
326 
327   /**
328    * Only used by the Striped Compaction Policy
329    * @param key
330    * @return value associated with the metadata key
331    */
332   public byte[] getMetadataValue(byte[] key) {
333     return metadataMap.get(key);
334   }
335 
336   /**
337    * Return the largest memstoreTS found across all storefiles in
338    * the given list. Store files that were created by a mapreduce
339    * bulk load are ignored, as they do not correspond to any specific
340    * put operation, and thus do not have a memstoreTS associated with them.
341    * @return 0 if no non-bulk-load files are provided or, this is Store that
342    * does not yet have any store files.
343    */
344   public static long getMaxMemstoreTSInList(Collection<StoreFile> sfs) {
345     long max = 0;
346     for (StoreFile sf : sfs) {
347       if (!sf.isBulkLoadResult()) {
348         max = Math.max(max, sf.getMaxMemstoreTS());
349       }
350     }
351     return max;
352   }
353 
354   /**
355    * Return the highest sequence ID found across all storefiles in
356    * the given list.
357    * @param sfs
358    * @return 0 if no non-bulk-load files are provided or, this is Store that
359    * does not yet have any store files.
360    */
361   public static long getMaxSequenceIdInList(Collection<StoreFile> sfs) {
362     long max = 0;
363     for (StoreFile sf : sfs) {
364       max = Math.max(max, sf.getMaxSequenceId());
365     }
366     return max;
367   }
368 
369   /**
370    * Check if this storefile was created by bulk load.
371    * When a hfile is bulk loaded into HBase, we append
372    * '_SeqId_<id-when-loaded>' to the hfile name, unless
373    * "hbase.mapreduce.bulkload.assign.sequenceNumbers" is
374    * explicitly turned off.
375    * If "hbase.mapreduce.bulkload.assign.sequenceNumbers"
376    * is turned off, fall back to BULKLOAD_TIME_KEY.
377    * @return true if this storefile was created by bulk load.
378    */
379   public boolean isBulkLoadResult() {
380     boolean bulkLoadedHFile = false;
381     String fileName = this.getPath().getName();
382     int startPos = fileName.indexOf("SeqId_");
383     if (startPos != -1) {
384       bulkLoadedHFile = true;
385     }
386     return bulkLoadedHFile || (metadataMap != null && metadataMap.containsKey(BULKLOAD_TIME_KEY));
387   }
388 
389   @VisibleForTesting
390   public boolean isCompactedAway() {
391     if (this.reader != null) {
392       return this.reader.isCompactedAway();
393     }
394     return true;
395   }
396 
397   @VisibleForTesting
398   public int getRefCount() {
399     return this.reader.refCount.get();
400   }
401 
402   /**
403    * Return the timestamp at which this bulk load file was generated.
404    */
405   public long getBulkLoadTimestamp() {
406     byte[] bulkLoadTimestamp = metadataMap.get(BULKLOAD_TIME_KEY);
407     return (bulkLoadTimestamp == null) ? 0 : Bytes.toLong(bulkLoadTimestamp);
408   }
409 
410   /**
411    * @return the cached value of HDFS blocks distribution. The cached value is
412    * calculated when store file is opened.
413    */
414   public HDFSBlocksDistribution getHDFSBlockDistribution() {
415     return this.fileInfo.getHDFSBlockDistribution();
416   }
417 
418   /**
419    * Opens reader on this store file.  Called by Constructor.
420    * @return Reader for the store file.
421    * @throws IOException
422    * @see #closeReader(boolean)
423    */
424   private Reader open(boolean canUseDropBehind) throws IOException {
425     if (this.reader != null) {
426       throw new IllegalAccessError("Already open");
427     }
428 
429     // Open the StoreFile.Reader
430     this.reader = fileInfo.open(this.fs, this.cacheConf, canUseDropBehind);
431 
432     // Load up indices and fileinfo. This also loads Bloom filter type.
433     metadataMap = Collections.unmodifiableMap(this.reader.loadFileInfo());
434 
435     // Read in our metadata.
436     byte [] b = metadataMap.get(MAX_SEQ_ID_KEY);
437     if (b != null) {
438       // By convention, if halfhfile, top half has a sequence number > bottom
439       // half. Thats why we add one in below. Its done for case the two halves
440       // are ever merged back together --rare.  Without it, on open of store,
441       // since store files are distinguished by sequence id, the one half would
442       // subsume the other.
443       this.sequenceid = Bytes.toLong(b);
444       if (fileInfo.isTopReference()) {
445         this.sequenceid += 1;
446       }
447     }
448 
449     if (isBulkLoadResult()){
450       // generate the sequenceId from the fileName
451       // fileName is of the form <randomName>_SeqId_<id-when-loaded>_
452       String fileName = this.getPath().getName();
453       // Use lastIndexOf() to get the last, most recent bulk load seqId.
454       int startPos = fileName.lastIndexOf("SeqId_");
455       if (startPos != -1) {
456         this.sequenceid = Long.parseLong(fileName.substring(startPos + 6,
457             fileName.indexOf('_', startPos + 6)));
458         // Handle reference files as done above.
459         if (fileInfo.isTopReference()) {
460           this.sequenceid += 1;
461         }
462       }
463       this.reader.setBulkLoaded(true);
464     }
465     this.reader.setSequenceID(this.sequenceid);
466 
467     b = metadataMap.get(HFileWriterV2.MAX_MEMSTORE_TS_KEY);
468     if (b != null) {
469       this.maxMemstoreTS = Bytes.toLong(b);
470     }
471 
472     b = metadataMap.get(MAJOR_COMPACTION_KEY);
473     if (b != null) {
474       boolean mc = Bytes.toBoolean(b);
475       if (this.majorCompaction == null) {
476         this.majorCompaction = new AtomicBoolean(mc);
477       } else {
478         this.majorCompaction.set(mc);
479       }
480     } else {
481       // Presume it is not major compacted if it doesn't explicity say so
482       // HFileOutputFormat explicitly sets the major compacted key.
483       this.majorCompaction = new AtomicBoolean(false);
484     }
485 
486     b = metadataMap.get(EXCLUDE_FROM_MINOR_COMPACTION_KEY);
487     this.excludeFromMinorCompaction = (b != null && Bytes.toBoolean(b));
488 
489     BloomType hfileBloomType = reader.getBloomFilterType();
490     if (cfBloomType != BloomType.NONE) {
491       reader.loadBloomfilter(BlockType.GENERAL_BLOOM_META);
492       if (hfileBloomType != cfBloomType) {
493         LOG.info("HFile Bloom filter type for "
494             + reader.getHFileReader().getName() + ": " + hfileBloomType
495             + ", but " + cfBloomType + " specified in column family "
496             + "configuration");
497       }
498     } else if (hfileBloomType != BloomType.NONE) {
499       LOG.info("Bloom filter turned off by CF config for "
500           + reader.getHFileReader().getName());
501     }
502 
503     // load delete family bloom filter
504     reader.loadBloomfilter(BlockType.DELETE_FAMILY_BLOOM_META);
505 
506     try {
507       this.reader.timeRange = TimeRangeTracker.getTimeRange(metadataMap.get(TIMERANGE_KEY));
508     } catch (IllegalArgumentException e) {
509       LOG.error("Error reading timestamp range data from meta -- " +
510           "proceeding without", e);
511       this.reader.timeRange = null;
512     }
513     // initialize so we can reuse them after reader closed.
514     firstKey = reader.getFirstKey();
515     lastKey = reader.getLastKey();
516     comparator = reader.getComparator();
517     return this.reader;
518   }
519 
520   public Reader createReader() throws IOException {
521     return createReader(false);
522   }
523 
524   /**
525    * @return Reader for StoreFile. creates if necessary
526    * @throws IOException
527    */
528   public Reader createReader(boolean canUseDropBehind) throws IOException {
529     if (this.reader == null) {
530       try {
531         this.reader = open(canUseDropBehind);
532       } catch (IOException e) {
533         try {
534           boolean evictOnClose =
535               cacheConf != null? cacheConf.shouldEvictOnClose(): true;
536           this.closeReader(evictOnClose);
537         } catch (IOException ee) {
538         }
539         throw e;
540       }
541 
542     }
543     return this.reader;
544   }
545 
546   /**
547    * @return Current reader.  Must call createReader first else returns null.
548    * @see #createReader()
549    */
550   public Reader getReader() {
551     return this.reader;
552   }
553 
554   /**
555    * @param evictOnClose whether to evict blocks belonging to this file
556    * @throws IOException
557    */
558   public synchronized void closeReader(boolean evictOnClose)
559       throws IOException {
560     if (this.reader != null) {
561       this.reader.close(evictOnClose);
562       this.reader = null;
563     }
564   }
565 
566   /**
567    * Marks the status of the file as compactedAway.
568    */
569   public void markCompactedAway() {
570     if (this.reader != null) {
571       this.reader.markCompactedAway();
572     }
573   }
574 
575   /**
576    * Delete this file
577    * @throws IOException
578    */
579   public void deleteReader() throws IOException {
580     boolean evictOnClose =
581         cacheConf != null? cacheConf.shouldEvictOnClose(): true;
582     closeReader(evictOnClose);
583     this.fs.delete(getPath(), true);
584   }
585 
586   @Override
587   public String toString() {
588     return this.fileInfo.toString();
589   }
590 
591   /**
592    * @return a length description of this StoreFile, suitable for debug output
593    */
594   public String toStringDetailed() {
595     StringBuilder sb = new StringBuilder();
596     sb.append(this.getPath().toString());
597     sb.append(", isReference=").append(isReference());
598     sb.append(", isBulkLoadResult=").append(isBulkLoadResult());
599     if (isBulkLoadResult()) {
600       sb.append(", bulkLoadTS=").append(getBulkLoadTimestamp());
601     } else {
602       sb.append(", seqid=").append(getMaxSequenceId());
603     }
604     sb.append(", majorCompaction=").append(isMajorCompaction());
605 
606     return sb.toString();
607   }
608 
609   @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="ICAST_INTEGER_MULTIPLY_CAST_TO_LONG",
610       justification="Will not overflow")
611   public static class WriterBuilder {
612     private final Configuration conf;
613     private final CacheConfig cacheConf;
614     private final FileSystem fs;
615 
616     private KeyValue.KVComparator comparator = KeyValue.COMPARATOR;
617     private BloomType bloomType = BloomType.NONE;
618     private long maxKeyCount = 0;
619     private Path dir;
620     private Path filePath;
621     private InetSocketAddress[] favoredNodes;
622     private HFileContext fileContext;
623     private TimeRangeTracker trt;
624     private boolean shouldDropCacheBehind;
625 
626     public WriterBuilder(Configuration conf, CacheConfig cacheConf,
627         FileSystem fs) {
628       this.conf = conf;
629       this.cacheConf = cacheConf;
630       this.fs = fs;
631     }
632 
633     /**
634      * @param trt A premade TimeRangeTracker to use rather than build one per append (building one
635      * of these is expensive so good to pass one in if you have one).
636      * @return this (for chained invocation)
637      */
638     public WriterBuilder withTimeRangeTracker(final TimeRangeTracker trt) {
639       Preconditions.checkNotNull(trt);
640       this.trt = trt;
641       return this;
642     }
643 
644     /**
645      * Use either this method or {@link #withFilePath}, but not both.
646      * @param dir Path to column family directory. The directory is created if
647      *          does not exist. The file is given a unique name within this
648      *          directory.
649      * @return this (for chained invocation)
650      */
651     public WriterBuilder withOutputDir(Path dir) {
652       Preconditions.checkNotNull(dir);
653       this.dir = dir;
654       return this;
655     }
656 
657     /**
658      * Use either this method or {@link #withOutputDir}, but not both.
659      * @param filePath the StoreFile path to write
660      * @return this (for chained invocation)
661      */
662     public WriterBuilder withFilePath(Path filePath) {
663       Preconditions.checkNotNull(filePath);
664       this.filePath = filePath;
665       return this;
666     }
667 
668     /**
669      * @param favoredNodes an array of favored nodes or possibly null
670      * @return this (for chained invocation)
671      */
672     public WriterBuilder withFavoredNodes(InetSocketAddress[] favoredNodes) {
673       this.favoredNodes = favoredNodes;
674       return this;
675     }
676 
677     public WriterBuilder withComparator(KeyValue.KVComparator comparator) {
678       Preconditions.checkNotNull(comparator);
679       this.comparator = comparator;
680       return this;
681     }
682 
683     public WriterBuilder withBloomType(BloomType bloomType) {
684       Preconditions.checkNotNull(bloomType);
685       this.bloomType = bloomType;
686       return this;
687     }
688 
689     /**
690      * @param maxKeyCount estimated maximum number of keys we expect to add
691      * @return this (for chained invocation)
692      */
693     public WriterBuilder withMaxKeyCount(long maxKeyCount) {
694       this.maxKeyCount = maxKeyCount;
695       return this;
696     }
697 
698     public WriterBuilder withFileContext(HFileContext fileContext) {
699       this.fileContext = fileContext;
700       return this;
701     }
702 
703     public WriterBuilder withShouldDropCacheBehind(boolean shouldDropCacheBehind) {
704       this.shouldDropCacheBehind = shouldDropCacheBehind;
705       return this;
706     }
707 
708     /**
709      * Create a store file writer. Client is responsible for closing file when
710      * done. If metadata, add BEFORE closing using
711      * {@link Writer#appendMetadata}.
712      */
713     public Writer build() throws IOException {
714       if ((dir == null ? 0 : 1) + (filePath == null ? 0 : 1) != 1) {
715         throw new IllegalArgumentException("Either specify parent directory " +
716             "or file path");
717       }
718 
719       if (dir == null) {
720         dir = filePath.getParent();
721       }
722 
723       if (!fs.exists(dir)) {
724         // Handle permission for non-HDFS filesystem properly
725         // See HBASE-17710
726         HRegionFileSystem.mkdirs(fs, conf, dir);
727       }
728 
729       if (filePath == null) {
730         filePath = getUniqueFile(fs, dir);
731         if (!BloomFilterFactory.isGeneralBloomEnabled(conf)) {
732           bloomType = BloomType.NONE;
733         }
734       }
735 
736       if (comparator == null) {
737         comparator = KeyValue.COMPARATOR;
738       }
739       return new Writer(fs, filePath,
740           conf, cacheConf, comparator, bloomType, maxKeyCount, favoredNodes, fileContext,
741           shouldDropCacheBehind, trt);
742     }
743   }
744 
745   /**
746    * @param fs
747    * @param dir Directory to create file in.
748    * @return random filename inside passed <code>dir</code>
749    */
750   public static Path getUniqueFile(final FileSystem fs, final Path dir)
751       throws IOException {
752     if (!fs.getFileStatus(dir).isDirectory()) {
753       throw new IOException("Expecting " + dir.toString() +
754         " to be a directory");
755     }
756     return new Path(dir, UUID.randomUUID().toString().replaceAll("-", ""));
757   }
758 
759   public Long getMinimumTimestamp() {
760     return getReader().timeRange == null? null: getReader().timeRange.getMin();
761   }
762 
763   public Long getMaximumTimestamp() {
764     return getReader().timeRange == null? null: getReader().timeRange.getMax();
765   }
766 
767 
768   /**
769    * Gets the approximate mid-point of this file that is optimal for use in splitting it.
770    * @param comparator Comparator used to compare KVs.
771    * @return The split point row, or null if splitting is not possible, or reader is null.
772    */
773   @SuppressWarnings("deprecation")
774   byte[] getFileSplitPoint(KVComparator comparator) throws IOException {
775     if (this.reader == null) {
776       LOG.warn("Storefile " + this + " Reader is null; cannot get split point");
777       return null;
778     }
779     // Get first, last, and mid keys.  Midkey is the key that starts block
780     // in middle of hfile.  Has column and timestamp.  Need to return just
781     // the row we want to split on as midkey.
782     byte [] midkey = this.reader.midkey();
783     if (midkey != null) {
784       KeyValue mk = KeyValue.createKeyValueFromKey(midkey, 0, midkey.length);
785       byte [] fk = this.reader.getFirstKey();
786       KeyValue firstKey = KeyValue.createKeyValueFromKey(fk, 0, fk.length);
787       byte [] lk = this.reader.getLastKey();
788       KeyValue lastKey = KeyValue.createKeyValueFromKey(lk, 0, lk.length);
789       // if the midkey is the same as the first or last keys, we cannot (ever) split this region.
790       if (comparator.compareRows(mk, firstKey) == 0 || comparator.compareRows(mk, lastKey) == 0) {
791         if (LOG.isDebugEnabled()) {
792           LOG.debug("cannot split because midkey is the same as first or last row");
793         }
794         return null;
795       }
796       return mk.getRow();
797     }
798     return null;
799   }
800 
801   /**
802    * A StoreFile writer.  Use this to read/write HBase Store Files. It is package
803    * local because it is an implementation detail of the HBase regionserver.
804    */
805   public static class Writer implements Compactor.CellSink {
806     private final BloomFilterWriter generalBloomFilterWriter;
807     private final BloomFilterWriter deleteFamilyBloomFilterWriter;
808     private final BloomType bloomType;
809     private byte[] lastBloomKey;
810     private int lastBloomKeyOffset, lastBloomKeyLen;
811     private KVComparator kvComparator;
812     private Cell lastCell = null;
813     private long earliestPutTs = HConstants.LATEST_TIMESTAMP;
814     private Cell lastDeleteFamilyCell = null;
815     private long deleteFamilyCnt = 0;
816 
817     /**
818      * timeRangeTrackerSet is used to figure if we were passed a filled-out TimeRangeTracker or not.
819      * When flushing a memstore, we set the TimeRangeTracker that it accumulated during updates to
820      * memstore in here into this Writer and use this variable to indicate that we do not need to
821      * recalculate the timeRangeTracker bounds; it was done already as part of add-to-memstore.
822      * A completed TimeRangeTracker is not set in cases of compactions when it is recalculated.
823      */
824     private final boolean timeRangeTrackerSet;
825     final TimeRangeTracker timeRangeTracker;
826 
827     protected HFile.Writer writer;
828 
829     /**
830      * Creates an HFile.Writer that also write helpful meta data.
831      * @param fs file system to write to
832      * @param path file name to create
833      * @param conf user configuration
834      * @param comparator key comparator
835      * @param bloomType bloom filter setting
836      * @param maxKeys the expected maximum number of keys to be added. Was used
837      *        for Bloom filter size in {@link HFile} format version 1.
838      * @param favoredNodes
839      * @param fileContext - The HFile context
840      * @param shouldDropCacheBehind Drop pages written to page cache after writing the store file.
841      * @throws IOException problem writing to FS
842      */
843     private Writer(FileSystem fs, Path path,
844         final Configuration conf,
845         CacheConfig cacheConf,
846         final KVComparator comparator, BloomType bloomType, long maxKeys,
847         InetSocketAddress[] favoredNodes, HFileContext fileContext, boolean shouldDropCacheBehind)
848             throws IOException {
849       this(fs, path, conf, cacheConf, comparator, bloomType, maxKeys, favoredNodes, fileContext,
850           shouldDropCacheBehind, null);
851     }
852 
853     /**
854      * Creates an HFile.Writer that also write helpful meta data.
855      * @param fs file system to write to
856      * @param path file name to create
857      * @param conf user configuration
858      * @param comparator key comparator
859      * @param bloomType bloom filter setting
860      * @param maxKeys the expected maximum number of keys to be added. Was used
861      *        for Bloom filter size in {@link HFile} format version 1.
862      * @param favoredNodes
863      * @param fileContext - The HFile context
864      * @param shouldDropCacheBehind Drop pages written to page cache after writing the store file.
865      * @param trt Ready-made timetracker to use.
866      * @throws IOException problem writing to FS
867      */
868     private Writer(FileSystem fs, Path path,
869         final Configuration conf,
870         CacheConfig cacheConf,
871         final KVComparator comparator, BloomType bloomType, long maxKeys,
872         InetSocketAddress[] favoredNodes, HFileContext fileContext,
873         boolean shouldDropCacheBehind, final TimeRangeTracker trt)
874             throws IOException {
875       // If passed a TimeRangeTracker, use it. Set timeRangeTrackerSet so we don't destroy it.
876       // TODO: put the state of the TRT on the TRT; i.e. make a read-only version (TimeRange) when
877       // it no longer writable.
878       this.timeRangeTrackerSet = trt != null;
879       this.timeRangeTracker = this.timeRangeTrackerSet? trt: new TimeRangeTracker();
880       writer = HFile.getWriterFactory(conf, cacheConf)
881           .withPath(fs, path)
882           .withComparator(comparator)
883           .withFavoredNodes(favoredNodes)
884           .withFileContext(fileContext)
885           .withShouldDropCacheBehind(shouldDropCacheBehind)
886           .create();
887 
888       this.kvComparator = comparator;
889 
890       generalBloomFilterWriter = BloomFilterFactory.createGeneralBloomAtWrite(
891           conf, cacheConf, bloomType,
892           (int) Math.min(maxKeys, Integer.MAX_VALUE), writer);
893 
894       if (generalBloomFilterWriter != null) {
895         this.bloomType = bloomType;
896         if (LOG.isTraceEnabled()) LOG.trace("Bloom filter type for " + path + ": " +
897           this.bloomType + ", " + generalBloomFilterWriter.getClass().getSimpleName());
898       } else {
899         // Not using Bloom filters.
900         this.bloomType = BloomType.NONE;
901       }
902 
903       // initialize delete family Bloom filter when there is NO RowCol Bloom
904       // filter
905       if (this.bloomType != BloomType.ROWCOL) {
906         this.deleteFamilyBloomFilterWriter = BloomFilterFactory
907             .createDeleteBloomAtWrite(conf, cacheConf,
908                 (int) Math.min(maxKeys, Integer.MAX_VALUE), writer);
909       } else {
910         deleteFamilyBloomFilterWriter = null;
911       }
912       if (deleteFamilyBloomFilterWriter != null) {
913         if (LOG.isTraceEnabled()) LOG.trace("Delete Family Bloom filter type for " + path + ": "
914             + deleteFamilyBloomFilterWriter.getClass().getSimpleName());
915       }
916     }
917 
918     /**
919      * Writes meta data.
920      * Call before {@link #close()} since its written as meta data to this file.
921      * @param maxSequenceId Maximum sequence id.
922      * @param majorCompaction True if this file is product of a major compaction
923      * @throws IOException problem writing to FS
924      */
925     public void appendMetadata(final long maxSequenceId, final boolean majorCompaction)
926     throws IOException {
927       writer.appendFileInfo(MAX_SEQ_ID_KEY, Bytes.toBytes(maxSequenceId));
928       writer.appendFileInfo(MAJOR_COMPACTION_KEY,
929           Bytes.toBytes(majorCompaction));
930       appendTrackedTimestampsToMetadata();
931     }
932 
933     /**
934      * Add TimestampRange and earliest put timestamp to Metadata
935      */
936     public void appendTrackedTimestampsToMetadata() throws IOException {
937       appendFileInfo(TIMERANGE_KEY,WritableUtils.toByteArray(timeRangeTracker));
938       appendFileInfo(EARLIEST_PUT_TS, Bytes.toBytes(earliestPutTs));
939     }
940 
941     /**
942      * Record the earlest Put timestamp.
943      *
944      * If the timeRangeTracker is not set,
945      * update TimeRangeTracker to include the timestamp of this key
946      * @param cell
947      */
948     public void trackTimestamps(final Cell cell) {
949       if (KeyValue.Type.Put.getCode() == cell.getTypeByte()) {
950         earliestPutTs = Math.min(earliestPutTs, cell.getTimestamp());
951       }
952       if (!timeRangeTrackerSet) {
953         timeRangeTracker.includeTimestamp(cell);
954       }
955     }
956 
957     private void appendGeneralBloomfilter(final Cell cell) throws IOException {
958       if (this.generalBloomFilterWriter != null) {
959         // only add to the bloom filter on a new, unique key
960         boolean newKey = true;
961         if (this.lastCell != null) {
962           switch(bloomType) {
963           case ROW:
964             newKey = ! kvComparator.matchingRows(cell, lastCell);
965             break;
966           case ROWCOL:
967             newKey = ! kvComparator.matchingRowColumn(cell, lastCell);
968             break;
969           case NONE:
970             newKey = false;
971             break;
972           default:
973             throw new IOException("Invalid Bloom filter type: " + bloomType +
974                 " (ROW or ROWCOL expected)");
975           }
976         }
977         if (newKey) {
978           /*
979            * http://2.bp.blogspot.com/_Cib_A77V54U/StZMrzaKufI/AAAAAAAAADo/ZhK7bGoJdMQ/s400/KeyValue.png
980            * Key = RowLen + Row + FamilyLen + Column [Family + Qualifier] + TimeStamp
981            *
982            * 2 Types of Filtering:
983            *  1. Row = Row
984            *  2. RowCol = Row + Qualifier
985            */
986           byte[] bloomKey;
987           int bloomKeyOffset, bloomKeyLen;
988 
989           switch (bloomType) {
990           case ROW:
991             bloomKey = cell.getRowArray();
992             bloomKeyOffset = cell.getRowOffset();
993             bloomKeyLen = cell.getRowLength();
994             break;
995           case ROWCOL:
996             // merge(row, qualifier)
997             // TODO: could save one buffer copy in case of compound Bloom
998             // filters when this involves creating a KeyValue
999             bloomKey = generalBloomFilterWriter.createBloomKey(cell.getRowArray(),
1000                 cell.getRowOffset(), cell.getRowLength(), cell.getQualifierArray(),
1001                 cell.getQualifierOffset(), cell.getQualifierLength());
1002             bloomKeyOffset = 0;
1003             bloomKeyLen = bloomKey.length;
1004             break;
1005           default:
1006             throw new IOException("Invalid Bloom filter type: " + bloomType +
1007                 " (ROW or ROWCOL expected)");
1008           }
1009           generalBloomFilterWriter.add(bloomKey, bloomKeyOffset, bloomKeyLen);
1010           if (lastBloomKey != null
1011               && generalBloomFilterWriter.getComparator().compareFlatKey(bloomKey,
1012                   bloomKeyOffset, bloomKeyLen, lastBloomKey,
1013                   lastBloomKeyOffset, lastBloomKeyLen) <= 0) {
1014             throw new IOException("Non-increasing Bloom keys: "
1015                 + Bytes.toStringBinary(bloomKey, bloomKeyOffset, bloomKeyLen)
1016                 + " after "
1017                 + Bytes.toStringBinary(lastBloomKey, lastBloomKeyOffset,
1018                     lastBloomKeyLen));
1019           }
1020           lastBloomKey = bloomKey;
1021           lastBloomKeyOffset = bloomKeyOffset;
1022           lastBloomKeyLen = bloomKeyLen;
1023           this.lastCell = cell;
1024         }
1025       }
1026     }
1027 
1028     private void appendDeleteFamilyBloomFilter(final Cell cell)
1029         throws IOException {
1030       if (!CellUtil.isDeleteFamily(cell) && !CellUtil.isDeleteFamilyVersion(cell)) {
1031         return;
1032       }
1033 
1034       // increase the number of delete family in the store file
1035       deleteFamilyCnt++;
1036       if (null != this.deleteFamilyBloomFilterWriter) {
1037         boolean newKey = true;
1038         if (lastDeleteFamilyCell != null) {
1039           newKey = !kvComparator.matchingRows(cell, lastDeleteFamilyCell);
1040         }
1041         if (newKey) {
1042           this.deleteFamilyBloomFilterWriter.add(cell.getRowArray(),
1043               cell.getRowOffset(), cell.getRowLength());
1044           this.lastDeleteFamilyCell = cell;
1045         }
1046       }
1047     }
1048 
1049     @Override
1050     public void append(final Cell cell) throws IOException {
1051       appendGeneralBloomfilter(cell);
1052       appendDeleteFamilyBloomFilter(cell);
1053       writer.append(cell);
1054       trackTimestamps(cell);
1055     }
1056 
1057     public Path getPath() {
1058       return this.writer.getPath();
1059     }
1060 
1061     boolean hasGeneralBloom() {
1062       return this.generalBloomFilterWriter != null;
1063     }
1064 
1065     /**
1066      * For unit testing only.
1067      *
1068      * @return the Bloom filter used by this writer.
1069      */
1070     BloomFilterWriter getGeneralBloomWriter() {
1071       return generalBloomFilterWriter;
1072     }
1073 
1074     private boolean closeBloomFilter(BloomFilterWriter bfw) throws IOException {
1075       boolean haveBloom = (bfw != null && bfw.getKeyCount() > 0);
1076       if (haveBloom) {
1077         bfw.compactBloom();
1078       }
1079       return haveBloom;
1080     }
1081 
1082     private boolean closeGeneralBloomFilter() throws IOException {
1083       boolean hasGeneralBloom = closeBloomFilter(generalBloomFilterWriter);
1084 
1085       // add the general Bloom filter writer and append file info
1086       if (hasGeneralBloom) {
1087         writer.addGeneralBloomFilter(generalBloomFilterWriter);
1088         writer.appendFileInfo(BLOOM_FILTER_TYPE_KEY,
1089             Bytes.toBytes(bloomType.toString()));
1090         if (lastBloomKey != null) {
1091           writer.appendFileInfo(LAST_BLOOM_KEY, Arrays.copyOfRange(
1092               lastBloomKey, lastBloomKeyOffset, lastBloomKeyOffset
1093                   + lastBloomKeyLen));
1094         }
1095       }
1096       return hasGeneralBloom;
1097     }
1098 
1099     private boolean closeDeleteFamilyBloomFilter() throws IOException {
1100       boolean hasDeleteFamilyBloom = closeBloomFilter(deleteFamilyBloomFilterWriter);
1101 
1102       // add the delete family Bloom filter writer
1103       if (hasDeleteFamilyBloom) {
1104         writer.addDeleteFamilyBloomFilter(deleteFamilyBloomFilterWriter);
1105       }
1106 
1107       // append file info about the number of delete family kvs
1108       // even if there is no delete family Bloom.
1109       writer.appendFileInfo(DELETE_FAMILY_COUNT,
1110           Bytes.toBytes(this.deleteFamilyCnt));
1111 
1112       return hasDeleteFamilyBloom;
1113     }
1114 
1115     public void close() throws IOException {
1116       boolean hasGeneralBloom = this.closeGeneralBloomFilter();
1117       boolean hasDeleteFamilyBloom = this.closeDeleteFamilyBloomFilter();
1118 
1119       writer.close();
1120 
1121       // Log final Bloom filter statistics. This needs to be done after close()
1122       // because compound Bloom filters might be finalized as part of closing.
1123       if (StoreFile.LOG.isTraceEnabled()) {
1124         StoreFile.LOG.trace((hasGeneralBloom ? "" : "NO ") + "General Bloom and " +
1125           (hasDeleteFamilyBloom ? "" : "NO ") + "DeleteFamily" + " was added to HFile " +
1126           getPath());
1127       }
1128 
1129     }
1130 
1131     public void appendFileInfo(byte[] key, byte[] value) throws IOException {
1132       writer.appendFileInfo(key, value);
1133     }
1134 
1135     /** For use in testing, e.g. {@link org.apache.hadoop.hbase.regionserver.CreateRandomStoreFile}
1136      */
1137     HFile.Writer getHFileWriter() {
1138       return writer;
1139     }
1140   }
1141 
1142   /**
1143    * Reader for a StoreFile.
1144    */
1145   @InterfaceAudience.LimitedPrivate({HBaseInterfaceAudience.COPROC, HBaseInterfaceAudience.PHOENIX})
1146   public static class Reader {
1147     private static final Log LOG = LogFactory.getLog(Reader.class.getName());
1148 
1149     protected BloomFilter generalBloomFilter = null;
1150     protected BloomFilter deleteFamilyBloomFilter = null;
1151     protected BloomType bloomFilterType;
1152     private final HFile.Reader reader;
1153     protected TimeRange timeRange;
1154     protected long sequenceID = -1;
1155     private byte[] lastBloomKey;
1156     private long deleteFamilyCnt = -1;
1157     private boolean bulkLoadResult = false;
1158     // Counter that is incremented every time a scanner is created on the
1159     // store file. It is decremented when the scan on the store file is
1160     // done.
1161     private AtomicInteger refCount = new AtomicInteger(0);
1162     // Indicates if the file got compacted
1163     private volatile boolean compactedAway = false;
1164 
1165     public Reader(FileSystem fs, Path path, CacheConfig cacheConf, Configuration conf)
1166         throws IOException {
1167       reader = HFile.createReader(fs, path, cacheConf, conf);
1168       bloomFilterType = BloomType.NONE;
1169     }
1170 
1171     void markCompactedAway() {
1172       this.compactedAway = true;
1173     }
1174 
1175     public Reader(FileSystem fs, Path path, FSDataInputStreamWrapper in, long size,
1176         CacheConfig cacheConf, Configuration conf) throws IOException {
1177       reader = HFile.createReader(fs, path, in, size, cacheConf, conf);
1178       bloomFilterType = BloomType.NONE;
1179     }
1180 
1181     public void setReplicaStoreFile(boolean isPrimaryReplicaStoreFile) {
1182       reader.setPrimaryReplicaReader(isPrimaryReplicaStoreFile);
1183     }
1184     public boolean isPrimaryReplicaReader() {
1185       return reader.isPrimaryReplicaReader();
1186     }
1187 
1188     /**
1189      * ONLY USE DEFAULT CONSTRUCTOR FOR UNIT TESTS
1190      */
1191     Reader() {
1192       this.reader = null;
1193     }
1194 
1195     public KVComparator getComparator() {
1196       return reader.getComparator();
1197     }
1198 
1199     /**
1200      * Get a scanner to scan over this StoreFile.
1201      * @param cacheBlocks should this scanner cache blocks?
1202      * @param pread use pread (for highly concurrent small readers)
1203      * @param isCompaction is scanner being used for compaction?
1204      * @param scannerOrder Order of this scanner relative to other scanners. See
1205      *          {@link KeyValueScanner#getScannerOrder()}.
1206      * @param canOptimizeForNonNullColumn {@code true} if we can make sure there is no null column,
1207      *          otherwise {@code false}. This is a hint for optimization.
1208      * @return a scanner
1209      */
1210     public StoreFileScanner getStoreFileScanner(boolean cacheBlocks, boolean pread,
1211         boolean isCompaction, long readPt, long scannerOrder, boolean canOptimizeForNonNullColumn) {
1212       return new StoreFileScanner(this, getScanner(cacheBlocks, pread, isCompaction), !isCompaction,
1213           reader.hasMVCCInfo(), readPt, scannerOrder, canOptimizeForNonNullColumn);
1214     }
1215 
1216     /**
1217      * Return the ref count associated with the reader whenever a scanner associated with the
1218      * reader is opened.
1219      */
1220     int getRefCount() {
1221       return refCount.get();
1222     }
1223 
1224     /**
1225      * Increment the ref count associated with the reader when ever a scanner associated with the
1226      * reader is opened
1227      */
1228     void incrementRefCount() {
1229       refCount.incrementAndGet();
1230     }
1231 
1232     /**
1233      * Decrement the ref count associated with the reader when ever a scanner associated
1234      * with the reader is closed
1235      */
1236     void decrementRefCount() {
1237       refCount.decrementAndGet();
1238     }
1239 
1240     /**
1241      * @return true if the file is still used in reads
1242      */
1243     public boolean isReferencedInReads() {
1244       return refCount.get() != 0;
1245     }
1246 
1247     /**
1248      * @return true if the file is compacted
1249      */
1250     public boolean isCompactedAway() {
1251       return this.compactedAway;
1252     }
1253 
1254     /**
1255      * @deprecated Do not write further code which depends on this call. Instead
1256      * use getStoreFileScanner() which uses the StoreFileScanner class/interface
1257      * which is the preferred way to scan a store with higher level concepts.
1258      *
1259      * @param cacheBlocks should we cache the blocks?
1260      * @param pread use pread (for concurrent small readers)
1261      * @return the underlying HFileScanner
1262      */
1263     @Deprecated
1264     public HFileScanner getScanner(boolean cacheBlocks, boolean pread) {
1265       return getScanner(cacheBlocks, pread, false);
1266     }
1267 
1268     /**
1269      * @deprecated Do not write further code which depends on this call. Instead
1270      * use getStoreFileScanner() which uses the StoreFileScanner class/interface
1271      * which is the preferred way to scan a store with higher level concepts.
1272      *
1273      * @param cacheBlocks
1274      *          should we cache the blocks?
1275      * @param pread
1276      *          use pread (for concurrent small readers)
1277      * @param isCompaction
1278      *          is scanner being used for compaction?
1279      * @return the underlying HFileScanner
1280      */
1281     @Deprecated
1282     public HFileScanner getScanner(boolean cacheBlocks, boolean pread,
1283         boolean isCompaction) {
1284       return reader.getScanner(cacheBlocks, pread, isCompaction);
1285     }
1286 
1287     public void close(boolean evictOnClose) throws IOException {
1288       reader.close(evictOnClose);
1289     }
1290 
1291     /**
1292      * Check if this storeFile may contain keys within the TimeRange that
1293      * have not expired (i.e. not older than oldestUnexpiredTS).
1294      * @param tr the timeRange to restrict
1295      * @param oldestUnexpiredTS the oldest timestamp that is not expired, as
1296      *          determined by the column family's TTL
1297      * @return false if queried keys definitely don't exist in this StoreFile
1298      */
1299     boolean passesTimerangeFilter(TimeRange tr, long oldestUnexpiredTS) {
1300       return this.timeRange == null? true:
1301         this.timeRange.includesTimeRange(tr) && this.timeRange.getMax() >= oldestUnexpiredTS;
1302     }
1303 
1304     /**
1305      * Checks whether the given scan passes the Bloom filter (if present). Only
1306      * checks Bloom filters for single-row or single-row-column scans. Bloom
1307      * filter checking for multi-gets is implemented as part of the store
1308      * scanner system (see {@link StoreFileScanner#seekExactly}) and uses
1309      * the lower-level API {@link #passesGeneralBloomFilter(byte[], int, int, byte[],
1310      * int, int)}.
1311      *
1312      * @param scan the scan specification. Used to determine the row, and to
1313      *          check whether this is a single-row ("get") scan.
1314      * @param columns the set of columns. Only used for row-column Bloom
1315      *          filters.
1316      * @return true if the scan with the given column set passes the Bloom
1317      *         filter, or if the Bloom filter is not applicable for the scan.
1318      *         False if the Bloom filter is applicable and the scan fails it.
1319      */
1320      boolean passesBloomFilter(Scan scan,
1321         final SortedSet<byte[]> columns) {
1322       // Multi-column non-get scans will use Bloom filters through the
1323       // lower-level API function that this function calls.
1324       if (!scan.isGetScan()) {
1325         return true;
1326       }
1327 
1328       byte[] row = scan.getStartRow();
1329       switch (this.bloomFilterType) {
1330         case ROW:
1331           return passesGeneralBloomFilter(row, 0, row.length, null, 0, 0);
1332 
1333         case ROWCOL:
1334           if (columns != null && columns.size() == 1) {
1335             byte[] column = columns.first();
1336             return passesGeneralBloomFilter(row, 0, row.length, column, 0,
1337                 column.length);
1338           }
1339 
1340           // For multi-column queries the Bloom filter is checked from the
1341           // seekExact operation.
1342           return true;
1343 
1344         default:
1345           return true;
1346       }
1347     }
1348 
1349     public boolean passesDeleteFamilyBloomFilter(byte[] row, int rowOffset,
1350         int rowLen) {
1351       // Cache Bloom filter as a local variable in case it is set to null by
1352       // another thread on an IO error.
1353       BloomFilter bloomFilter = this.deleteFamilyBloomFilter;
1354 
1355       // Empty file or there is no delete family at all
1356       if (reader.getTrailer().getEntryCount() == 0 || deleteFamilyCnt == 0) {
1357         return false;
1358       }
1359 
1360       if (bloomFilter == null) {
1361         return true;
1362       }
1363 
1364       try {
1365         if (!bloomFilter.supportsAutoLoading()) {
1366           return true;
1367         }
1368         return bloomFilter.contains(row, rowOffset, rowLen, null);
1369       } catch (IllegalArgumentException e) {
1370         LOG.error("Bad Delete Family bloom filter data -- proceeding without",
1371             e);
1372         setDeleteFamilyBloomFilterFaulty();
1373       }
1374 
1375       return true;
1376     }
1377 
1378     /**
1379      * A method for checking Bloom filters. Called directly from
1380      * StoreFileScanner in case of a multi-column query.
1381      *
1382      * @param row
1383      * @param rowOffset
1384      * @param rowLen
1385      * @param col
1386      * @param colOffset
1387      * @param colLen
1388      * @return True if passes
1389      */
1390     public boolean passesGeneralBloomFilter(byte[] row, int rowOffset,
1391         int rowLen, byte[] col, int colOffset, int colLen) {
1392       // Cache Bloom filter as a local variable in case it is set to null by
1393       // another thread on an IO error.
1394       BloomFilter bloomFilter = this.generalBloomFilter;
1395       if (bloomFilter == null) {
1396         return true;
1397       }
1398 
1399       byte[] key;
1400       switch (bloomFilterType) {
1401         case ROW:
1402           if (col != null) {
1403             throw new RuntimeException("Row-only Bloom filter called with " +
1404                 "column specified");
1405           }
1406           if (rowOffset != 0 || rowLen != row.length) {
1407               throw new AssertionError("For row-only Bloom filters the row "
1408                   + "must occupy the whole array");
1409           }
1410           key = row;
1411           break;
1412 
1413         case ROWCOL:
1414           key = bloomFilter.createBloomKey(row, rowOffset, rowLen, col,
1415               colOffset, colLen);
1416 
1417           break;
1418 
1419         default:
1420           return true;
1421       }
1422 
1423       // Empty file
1424       if (reader.getTrailer().getEntryCount() == 0)
1425         return false;
1426 
1427       try {
1428         boolean shouldCheckBloom;
1429         ByteBuffer bloom;
1430         if (bloomFilter.supportsAutoLoading()) {
1431           bloom = null;
1432           shouldCheckBloom = true;
1433         } else {
1434           bloom = reader.getMetaBlock(HFile.BLOOM_FILTER_DATA_KEY,
1435               true);
1436           shouldCheckBloom = bloom != null;
1437         }
1438 
1439         if (shouldCheckBloom) {
1440           boolean exists;
1441 
1442           // Whether the primary Bloom key is greater than the last Bloom key
1443           // from the file info. For row-column Bloom filters this is not yet
1444           // a sufficient condition to return false.
1445           boolean keyIsAfterLast = lastBloomKey != null
1446               && bloomFilter.getComparator().compareFlatKey(key, lastBloomKey) > 0;
1447 
1448           if (bloomFilterType == BloomType.ROWCOL) {
1449             // Since a Row Delete is essentially a DeleteFamily applied to all
1450             // columns, a file might be skipped if using row+col Bloom filter.
1451             // In order to ensure this file is included an additional check is
1452             // required looking only for a row bloom.
1453             byte[] rowBloomKey = bloomFilter.createBloomKey(row, rowOffset, rowLen,
1454                 null, 0, 0);
1455 
1456             if (keyIsAfterLast
1457                 && bloomFilter.getComparator().compareFlatKey(rowBloomKey,
1458                     lastBloomKey) > 0) {
1459               exists = false;
1460             } else {
1461               exists =
1462                   bloomFilter.contains(key, 0, key.length, bloom) ||
1463                   bloomFilter.contains(rowBloomKey, 0, rowBloomKey.length,
1464                       bloom);
1465             }
1466           } else {
1467             exists = !keyIsAfterLast
1468                 && bloomFilter.contains(key, 0, key.length, bloom);
1469           }
1470 
1471           return exists;
1472         }
1473       } catch (IOException e) {
1474         LOG.error("Error reading bloom filter data -- proceeding without",
1475             e);
1476         setGeneralBloomFilterFaulty();
1477       } catch (IllegalArgumentException e) {
1478         LOG.error("Bad bloom filter data -- proceeding without", e);
1479         setGeneralBloomFilterFaulty();
1480       }
1481 
1482       return true;
1483     }
1484 
1485     /**
1486      * Checks whether the given scan rowkey range overlaps with the current storefile's
1487      * @param scan the scan specification. Used to determine the rowkey range.
1488      * @return true if there is overlap, false otherwise
1489      */
1490     public boolean passesKeyRangeFilter(Scan scan) {
1491       if (this.getFirstKey() == null || this.getLastKey() == null) {
1492         // the file is empty
1493         return false;
1494       }
1495       if (Bytes.equals(scan.getStartRow(), HConstants.EMPTY_START_ROW)
1496           && Bytes.equals(scan.getStopRow(), HConstants.EMPTY_END_ROW)) {
1497         return true;
1498       }
1499       KeyValue smallestScanKeyValue = scan.isReversed() ? KeyValueUtil
1500           .createFirstOnRow(scan.getStopRow()) : KeyValueUtil.createFirstOnRow(scan
1501           .getStartRow());
1502       KeyValue largestScanKeyValue = scan.isReversed() ? KeyValueUtil
1503           .createLastOnRow(scan.getStartRow()) : KeyValueUtil.createLastOnRow(scan
1504           .getStopRow());
1505       boolean nonOverLapping = (getComparator().compareFlatKey(
1506           this.getFirstKey(), largestScanKeyValue.getKey()) > 0 && !Bytes
1507           .equals(scan.isReversed() ? scan.getStartRow() : scan.getStopRow(),
1508               HConstants.EMPTY_END_ROW))
1509           || getComparator().compareFlatKey(this.getLastKey(),
1510               smallestScanKeyValue.getKey()) < 0;
1511       return !nonOverLapping;
1512     }
1513 
1514     public Map<byte[], byte[]> loadFileInfo() throws IOException {
1515       Map<byte [], byte []> fi = reader.loadFileInfo();
1516 
1517       byte[] b = fi.get(BLOOM_FILTER_TYPE_KEY);
1518       if (b != null) {
1519         bloomFilterType = BloomType.valueOf(Bytes.toString(b));
1520       }
1521 
1522       lastBloomKey = fi.get(LAST_BLOOM_KEY);
1523       byte[] cnt = fi.get(DELETE_FAMILY_COUNT);
1524       if (cnt != null) {
1525         deleteFamilyCnt = Bytes.toLong(cnt);
1526       }
1527 
1528       return fi;
1529     }
1530 
1531     public void loadBloomfilter() {
1532       this.loadBloomfilter(BlockType.GENERAL_BLOOM_META);
1533       this.loadBloomfilter(BlockType.DELETE_FAMILY_BLOOM_META);
1534     }
1535 
1536     private void loadBloomfilter(BlockType blockType) {
1537       try {
1538         if (blockType == BlockType.GENERAL_BLOOM_META) {
1539           if (this.generalBloomFilter != null)
1540             return; // Bloom has been loaded
1541 
1542           DataInput bloomMeta = reader.getGeneralBloomFilterMetadata();
1543           if (bloomMeta != null) {
1544             // sanity check for NONE Bloom filter
1545             if (bloomFilterType == BloomType.NONE) {
1546               throw new IOException(
1547                   "valid bloom filter type not found in FileInfo");
1548             } else {
1549               generalBloomFilter = BloomFilterFactory.createFromMeta(bloomMeta,
1550                   reader);
1551               if (LOG.isTraceEnabled()) {
1552                 LOG.trace("Loaded " + bloomFilterType.toString() + " "
1553                   + generalBloomFilter.getClass().getSimpleName()
1554                   + " metadata for " + reader.getName());
1555               }
1556             }
1557           }
1558         } else if (blockType == BlockType.DELETE_FAMILY_BLOOM_META) {
1559           if (this.deleteFamilyBloomFilter != null)
1560             return; // Bloom has been loaded
1561 
1562           DataInput bloomMeta = reader.getDeleteBloomFilterMetadata();
1563           if (bloomMeta != null) {
1564             deleteFamilyBloomFilter = BloomFilterFactory.createFromMeta(
1565                 bloomMeta, reader);
1566             LOG.info("Loaded Delete Family Bloom ("
1567                 + deleteFamilyBloomFilter.getClass().getSimpleName()
1568                 + ") metadata for " + reader.getName());
1569           }
1570         } else {
1571           throw new RuntimeException("Block Type: " + blockType.toString()
1572               + "is not supported for Bloom filter");
1573         }
1574       } catch (IOException e) {
1575         LOG.error("Error reading bloom filter meta for " + blockType
1576             + " -- proceeding without", e);
1577         setBloomFilterFaulty(blockType);
1578       } catch (IllegalArgumentException e) {
1579         LOG.error("Bad bloom filter meta " + blockType
1580             + " -- proceeding without", e);
1581         setBloomFilterFaulty(blockType);
1582       }
1583     }
1584 
1585     private void setBloomFilterFaulty(BlockType blockType) {
1586       if (blockType == BlockType.GENERAL_BLOOM_META) {
1587         setGeneralBloomFilterFaulty();
1588       } else if (blockType == BlockType.DELETE_FAMILY_BLOOM_META) {
1589         setDeleteFamilyBloomFilterFaulty();
1590       }
1591     }
1592 
1593     /**
1594      * The number of Bloom filter entries in this store file, or an estimate
1595      * thereof, if the Bloom filter is not loaded. This always returns an upper
1596      * bound of the number of Bloom filter entries.
1597      *
1598      * @return an estimate of the number of Bloom filter entries in this file
1599      */
1600     public long getFilterEntries() {
1601       return generalBloomFilter != null ? generalBloomFilter.getKeyCount()
1602           : reader.getEntries();
1603     }
1604 
1605     public void setGeneralBloomFilterFaulty() {
1606       generalBloomFilter = null;
1607     }
1608 
1609     public void setDeleteFamilyBloomFilterFaulty() {
1610       this.deleteFamilyBloomFilter = null;
1611     }
1612 
1613     public byte[] getLastKey() {
1614       return reader.getLastKey();
1615     }
1616 
1617     public byte[] getLastRowKey() {
1618       return reader.getLastRowKey();
1619     }
1620 
1621     public byte[] midkey() throws IOException {
1622       return reader.midkey();
1623     }
1624 
1625     public long length() {
1626       return reader.length();
1627     }
1628 
1629     public long getTotalUncompressedBytes() {
1630       return reader.getTrailer().getTotalUncompressedBytes();
1631     }
1632 
1633     public long getEntries() {
1634       return reader.getEntries();
1635     }
1636 
1637     public long getDeleteFamilyCnt() {
1638       return deleteFamilyCnt;
1639     }
1640 
1641     public byte[] getFirstKey() {
1642       return reader.getFirstKey();
1643     }
1644 
1645     public long indexSize() {
1646       return reader.indexSize();
1647     }
1648 
1649     public BloomType getBloomFilterType() {
1650       return this.bloomFilterType;
1651     }
1652 
1653     public long getSequenceID() {
1654       return sequenceID;
1655     }
1656 
1657     public void setSequenceID(long sequenceID) {
1658       this.sequenceID = sequenceID;
1659     }
1660 
1661     public void setBulkLoaded(boolean bulkLoadResult) {
1662       this.bulkLoadResult = bulkLoadResult;
1663     }
1664 
1665     public boolean isBulkLoaded() {
1666       return this.bulkLoadResult;
1667     }
1668 
1669     BloomFilter getGeneralBloomFilter() {
1670       return generalBloomFilter;
1671     }
1672 
1673     long getUncompressedDataIndexSize() {
1674       return reader.getTrailer().getUncompressedDataIndexSize();
1675     }
1676 
1677     public long getTotalBloomSize() {
1678       if (generalBloomFilter == null)
1679         return 0;
1680       return generalBloomFilter.getByteSize();
1681     }
1682 
1683     public int getHFileVersion() {
1684       return reader.getTrailer().getMajorVersion();
1685     }
1686 
1687     public int getHFileMinorVersion() {
1688       return reader.getTrailer().getMinorVersion();
1689     }
1690 
1691     public HFile.Reader getHFileReader() {
1692       return reader;
1693     }
1694 
1695     void disableBloomFilterForTesting() {
1696       generalBloomFilter = null;
1697       this.deleteFamilyBloomFilter = null;
1698     }
1699 
1700     public long getMaxTimestamp() {
1701       return timeRange == null ? TimeRange.INITIAL_MAX_TIMESTAMP: timeRange.getMax();
1702     }
1703   }
1704 
1705   /**
1706    * Useful comparators for comparing StoreFiles.
1707    */
1708   public abstract static class Comparators {
1709     /**
1710      * Comparator that compares based on the Sequence Ids of the
1711      * the StoreFiles. Bulk loads that did not request a seq ID
1712      * are given a seq id of -1; thus, they are placed before all non-
1713      * bulk loads, and bulk loads with sequence Id. Among these files,
1714      * the size is used to determine the ordering, then bulkLoadTime.
1715      * If there are ties, the path name is used as a tie-breaker.
1716      */
1717     public static final Comparator<StoreFile> SEQ_ID =
1718       Ordering.compound(ImmutableList.of(
1719           Ordering.natural().onResultOf(new GetSeqId()),
1720           Ordering.natural().onResultOf(new GetFileSize()).reverse(),
1721           Ordering.natural().onResultOf(new GetBulkTime()),
1722           Ordering.natural().onResultOf(new GetPathName())
1723       ));
1724 
1725     /**
1726      * Comparator for time-aware compaction. SeqId is still the first
1727      *   ordering criterion to maintain MVCC.
1728      */
1729     public static final Comparator<StoreFile> SEQ_ID_MAX_TIMESTAMP =
1730       Ordering.compound(ImmutableList.of(
1731         Ordering.natural().onResultOf(new GetSeqId()),
1732         Ordering.natural().onResultOf(new GetMaxTimestamp()),
1733         Ordering.natural().onResultOf(new GetFileSize()).reverse(),
1734         Ordering.natural().onResultOf(new GetBulkTime()),
1735         Ordering.natural().onResultOf(new GetPathName())
1736       ));
1737 
1738     private static class GetSeqId implements Function<StoreFile, Long> {
1739       @Override
1740       public Long apply(StoreFile sf) {
1741         return sf.getMaxSequenceId();
1742       }
1743     }
1744 
1745     private static class GetFileSize implements Function<StoreFile, Long> {
1746       @Override
1747       public Long apply(StoreFile sf) {
1748         if (sf.getReader() != null) {
1749           return sf.getReader().length();
1750         } else {
1751           // the reader may be null for the compacted files and if the archiving
1752           // had failed.
1753           return -1L;
1754         }
1755       }
1756     }
1757 
1758     private static class GetBulkTime implements Function<StoreFile, Long> {
1759       @Override
1760       public Long apply(StoreFile sf) {
1761         if (!sf.isBulkLoadResult()) return Long.MAX_VALUE;
1762         return sf.getBulkLoadTimestamp();
1763       }
1764     }
1765 
1766     private static class GetPathName implements Function<StoreFile, String> {
1767       @Override
1768       public String apply(StoreFile sf) {
1769         return sf.getPath().getName();
1770       }
1771     }
1772 
1773     private static class GetMaxTimestamp implements Function<StoreFile, Long> {
1774       @Override
1775       public Long apply(StoreFile sf) {
1776         return sf.getMaximumTimestamp() == null? (Long)Long.MAX_VALUE : sf.getMaximumTimestamp();
1777       }
1778     }
1779   }
1780 }