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