View Javadoc

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