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