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