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  
20  package org.apache.hadoop.hbase.regionserver;
21  
22  import java.io.FileNotFoundException;
23  import java.io.IOException;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.List;
27  import java.util.concurrent.atomic.AtomicLong;
28  
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.apache.hadoop.hbase.classification.InterfaceAudience;
32  import org.apache.hadoop.hbase.Cell;
33  import org.apache.hadoop.hbase.CellUtil;
34  import org.apache.hadoop.hbase.HConstants;
35  import org.apache.hadoop.hbase.KeyValue;
36  import org.apache.hadoop.hbase.KeyValueUtil;
37  import org.apache.hadoop.hbase.client.Scan;
38  import org.apache.hadoop.hbase.io.TimeRange;
39  import org.apache.hadoop.hbase.io.hfile.HFileScanner;
40  import org.apache.hadoop.hbase.regionserver.StoreFile.Reader;
41  
42  /**
43   * KeyValueScanner adaptor over the Reader.  It also provides hooks into
44   * bloom filter things.
45   */
46  @InterfaceAudience.LimitedPrivate("Coprocessor")
47  public class StoreFileScanner implements KeyValueScanner {
48    private static final Log LOG = LogFactory.getLog(HStore.class);
49  
50    // the reader it comes from:
51    private final StoreFile.Reader reader;
52    private final HFileScanner hfs;
53    private Cell cur = null;
54    private boolean closed = false;
55  
56    private boolean realSeekDone;
57    private boolean delayedReseek;
58    private Cell delayedSeekKV;
59  
60    private boolean enforceMVCC = false;
61    private boolean hasMVCCInfo = false;
62    // A flag represents whether could stop skipping KeyValues for MVCC
63    // if have encountered the next row. Only used for reversed scan
64    private boolean stopSkippingKVsIfNextRow = false;
65  
66    private static AtomicLong seekCount;
67  
68    private ScanQueryMatcher matcher;
69  
70    private long readPt;
71  
72    /**
73     * Implements a {@link KeyValueScanner} on top of the specified {@link HFileScanner}
74     * @param hfs HFile scanner
75     */
76    public StoreFileScanner(StoreFile.Reader reader, HFileScanner hfs, boolean useMVCC,
77        boolean hasMVCC, long readPt) {
78      this.readPt = readPt;
79      this.reader = reader;
80      this.hfs = hfs;
81      this.enforceMVCC = useMVCC;
82      this.hasMVCCInfo = hasMVCC;
83    }
84  
85    boolean isPrimaryReplica() {
86      return reader.isPrimaryReplicaReader();
87    }
88  
89    /**
90     * Return an array of scanners corresponding to the given
91     * set of store files.
92     */
93    public static List<StoreFileScanner> getScannersForStoreFiles(
94        Collection<StoreFile> files,
95        boolean cacheBlocks,
96        boolean usePread, long readPt) throws IOException {
97      return getScannersForStoreFiles(files, cacheBlocks,
98                                     usePread, false, false, readPt);
99    }
100 
101   /**
102    * Return an array of scanners corresponding to the given set of store files.
103    */
104   public static List<StoreFileScanner> getScannersForStoreFiles(
105       Collection<StoreFile> files, boolean cacheBlocks, boolean usePread,
106       boolean isCompaction, boolean useDropBehind, long readPt) throws IOException {
107     return getScannersForStoreFiles(files, cacheBlocks, usePread, isCompaction,
108         useDropBehind, null, readPt);
109   }
110 
111   /**
112    * Return an array of scanners corresponding to the given set of store files,
113    * And set the ScanQueryMatcher for each store file scanner for further
114    * optimization
115    */
116   public static List<StoreFileScanner> getScannersForStoreFiles(
117       Collection<StoreFile> files, boolean cacheBlocks, boolean usePread,
118       boolean isCompaction, boolean canUseDrop,
119       ScanQueryMatcher matcher, long readPt, boolean isPrimaryReplica) throws IOException {
120     List<StoreFileScanner> scanners = new ArrayList<StoreFileScanner>(
121         files.size());
122     for (StoreFile file : files) {
123       StoreFile.Reader r = file.createReader(canUseDrop);
124       r.setReplicaStoreFile(isPrimaryReplica);
125       StoreFileScanner scanner = r.getStoreFileScanner(cacheBlocks, usePread,
126           isCompaction, readPt);
127       scanner.setScanQueryMatcher(matcher);
128       scanners.add(scanner);
129     }
130     return scanners;
131   }
132 
133   public static List<StoreFileScanner> getScannersForStoreFiles(
134     Collection<StoreFile> files, boolean cacheBlocks, boolean usePread,
135     boolean isCompaction, boolean canUseDrop,
136     ScanQueryMatcher matcher, long readPt) throws IOException {
137     return getScannersForStoreFiles(files, cacheBlocks, usePread, isCompaction, canUseDrop,
138       matcher, readPt, true);
139   }
140 
141   public String toString() {
142     return "StoreFileScanner[" + hfs.toString() + ", cur=" + cur + "]";
143   }
144 
145   public Cell peek() {
146     return cur;
147   }
148 
149   public Cell next() throws IOException {
150     Cell retKey = cur;
151 
152     try {
153       // only seek if we aren't at the end. cur == null implies 'end'.
154       if (cur != null) {
155         hfs.next();
156         setCurrentCell(hfs.getKeyValue());
157         if (hasMVCCInfo || this.reader.isBulkLoaded()) {
158           skipKVsNewerThanReadpoint();
159         }
160       }
161     } catch (FileNotFoundException e) {
162       throw e;
163     } catch(IOException e) {
164       throw new IOException("Could not iterate " + this, e);
165     }
166     return retKey;
167   }
168 
169   public boolean seek(Cell key) throws IOException {
170     if (seekCount != null) seekCount.incrementAndGet();
171 
172     try {
173       try {
174         if(!seekAtOrAfter(hfs, key)) {
175           this.cur = null;
176           return false;
177         }
178 
179         setCurrentCell(hfs.getKeyValue());
180 
181         if (!hasMVCCInfo && this.reader.isBulkLoaded()) {
182           return skipKVsNewerThanReadpoint();
183         } else {
184           return !hasMVCCInfo ? true : skipKVsNewerThanReadpoint();
185         }
186       } finally {
187         realSeekDone = true;
188       }
189     } catch (FileNotFoundException e) {
190       throw e;
191     } catch (IOException ioe) {
192       throw new IOException("Could not seek " + this + " to key " + key, ioe);
193     }
194   }
195 
196   public boolean reseek(Cell key) throws IOException {
197     if (seekCount != null) seekCount.incrementAndGet();
198 
199     try {
200       try {
201         if (!reseekAtOrAfter(hfs, key)) {
202           this.cur = null;
203           return false;
204         }
205         setCurrentCell(hfs.getKeyValue());
206 
207         if (!hasMVCCInfo && this.reader.isBulkLoaded()) {
208           return skipKVsNewerThanReadpoint();
209         } else {
210           return !hasMVCCInfo ? true : skipKVsNewerThanReadpoint();
211         }
212       } finally {
213         realSeekDone = true;
214       }
215     } catch (FileNotFoundException e) {
216       throw e;
217     } catch (IOException ioe) {
218       throw new IOException("Could not reseek " + this + " to key " + key,
219           ioe);
220     }
221   }
222 
223   protected void setCurrentCell(Cell newVal) throws IOException {
224     this.cur = newVal;
225     if (this.cur != null && this.reader.isBulkLoaded()) {
226       CellUtil.setSequenceId(cur, this.reader.getSequenceID());
227     }
228   }
229 
230   protected boolean skipKVsNewerThanReadpoint() throws IOException {
231     // We want to ignore all key-values that are newer than our current
232     // readPoint
233     Cell startKV = cur;
234     while(enforceMVCC
235         && cur != null
236         && (cur.getMvccVersion() > readPt)) {
237       boolean hasNext = hfs.next();
238       setCurrentCell(hfs.getKeyValue());
239       if (hasNext && this.stopSkippingKVsIfNextRow
240           && getComparator().compareRows(cur.getRowArray(), cur.getRowOffset(),
241               cur.getRowLength(), startKV.getRowArray(), startKV.getRowOffset(),
242               startKV.getRowLength()) > 0) {
243         return false;
244       }
245     }
246 
247     if (cur == null) {
248       return false;
249     }
250 
251     return true;
252   }
253 
254   public void close() {
255     cur = null;
256     if (closed) return;    
257     if (this.reader != null) {
258       this.reader.decrementRefCount();
259     }
260     closed = true;
261   }
262 
263   /**
264    *
265    * @param s
266    * @param k
267    * @return false if not found or if k is after the end.
268    * @throws IOException
269    */
270   public static boolean seekAtOrAfter(HFileScanner s, Cell k)
271   throws IOException {
272     int result = s.seekTo(k);
273     if(result < 0) {
274       if (result == HConstants.INDEX_KEY_MAGIC) {
275         // using faked key
276         return true;
277       }
278       // Passed KV is smaller than first KV in file, work from start of file
279       return s.seekTo();
280     } else if(result > 0) {
281       // Passed KV is larger than current KV in file, if there is a next
282       // it is the "after", if not then this scanner is done.
283       return s.next();
284     }
285     // Seeked to the exact key
286     return true;
287   }
288 
289   static boolean reseekAtOrAfter(HFileScanner s, Cell k)
290   throws IOException {
291     //This function is similar to seekAtOrAfter function
292     int result = s.reseekTo(k);
293     if (result <= 0) {
294       if (result == HConstants.INDEX_KEY_MAGIC) {
295         // using faked key
296         return true;
297       }
298       // If up to now scanner is not seeked yet, this means passed KV is smaller
299       // than first KV in file, and it is the first time we seek on this file.
300       // So we also need to work from the start of file.
301       if (!s.isSeeked()) {
302         return  s.seekTo();
303       }
304       return true;
305     }
306     // passed KV is larger than current KV in file, if there is a next
307     // it is after, if not then this scanner is done.
308     return s.next();
309   }
310 
311   @Override
312   public long getSequenceID() {
313     return reader.getSequenceID();
314   }
315 
316   /**
317    * Pretend we have done a seek but don't do it yet, if possible. The hope is
318    * that we find requested columns in more recent files and won't have to seek
319    * in older files. Creates a fake key/value with the given row/column and the
320    * highest (most recent) possible timestamp we might get from this file. When
321    * users of such "lazy scanner" need to know the next KV precisely (e.g. when
322    * this scanner is at the top of the heap), they run {@link #enforceSeek()}.
323    * <p>
324    * Note that this function does guarantee that the current KV of this scanner
325    * will be advanced to at least the given KV. Because of this, it does have
326    * to do a real seek in cases when the seek timestamp is older than the
327    * highest timestamp of the file, e.g. when we are trying to seek to the next
328    * row/column and use OLDEST_TIMESTAMP in the seek key.
329    */
330   @Override
331   public boolean requestSeek(Cell kv, boolean forward, boolean useBloom)
332       throws IOException {
333     if (kv.getFamilyLength() == 0) {
334       useBloom = false;
335     }
336 
337     boolean haveToSeek = true;
338     if (useBloom) {
339       // check ROWCOL Bloom filter first.
340       if (reader.getBloomFilterType() == BloomType.ROWCOL) {
341         haveToSeek = reader.passesGeneralBloomFilter(kv.getRowArray(),
342             kv.getRowOffset(), kv.getRowLength(), kv.getQualifierArray(),
343             kv.getQualifierOffset(), kv.getQualifierLength());
344       } else if (this.matcher != null && !matcher.hasNullColumnInQuery() &&
345           ((CellUtil.isDeleteFamily(kv) || CellUtil.isDeleteFamilyVersion(kv)))) {
346         // if there is no such delete family kv in the store file,
347         // then no need to seek.
348         haveToSeek = reader.passesDeleteFamilyBloomFilter(kv.getRowArray(),
349             kv.getRowOffset(), kv.getRowLength());
350       }
351     }
352 
353     delayedReseek = forward;
354     delayedSeekKV = kv;
355 
356     if (haveToSeek) {
357       // This row/column might be in this store file (or we did not use the
358       // Bloom filter), so we still need to seek.
359       realSeekDone = false;
360       long maxTimestampInFile = reader.getMaxTimestamp();
361       long seekTimestamp = kv.getTimestamp();
362       if (seekTimestamp > maxTimestampInFile) {
363         // Create a fake key that is not greater than the real next key.
364         // (Lower timestamps correspond to higher KVs.)
365         // To understand this better, consider that we are asked to seek to
366         // a higher timestamp than the max timestamp in this file. We know that
367         // the next point when we have to consider this file again is when we
368         // pass the max timestamp of this file (with the same row/column).
369         setCurrentCell(KeyValueUtil.createFirstOnRowColTS(kv, maxTimestampInFile));
370       } else {
371         // This will be the case e.g. when we need to seek to the next
372         // row/column, and we don't know exactly what they are, so we set the
373         // seek key's timestamp to OLDEST_TIMESTAMP to skip the rest of this
374         // row/column.
375         enforceSeek();
376       }
377       return cur != null;
378     }
379 
380     // Multi-column Bloom filter optimization.
381     // Create a fake key/value, so that this scanner only bubbles up to the top
382     // of the KeyValueHeap in StoreScanner after we scanned this row/column in
383     // all other store files. The query matcher will then just skip this fake
384     // key/value and the store scanner will progress to the next column. This
385     // is obviously not a "real real" seek, but unlike the fake KV earlier in
386     // this method, we want this to be propagated to ScanQueryMatcher.
387     setCurrentCell(KeyValueUtil.createLastOnRowCol(kv));
388 
389     realSeekDone = true;
390     return true;
391   }
392 
393   Reader getReader() {
394     return reader;
395   }
396 
397   KeyValue.KVComparator getComparator() {
398     return reader.getComparator();
399   }
400 
401   @Override
402   public boolean realSeekDone() {
403     return realSeekDone;
404   }
405 
406   @Override
407   public void enforceSeek() throws IOException {
408     if (realSeekDone)
409       return;
410 
411     if (delayedReseek) {
412       reseek(delayedSeekKV);
413     } else {
414       seek(delayedSeekKV);
415     }
416   }
417 
418   public void setScanQueryMatcher(ScanQueryMatcher matcher) {
419     this.matcher = matcher;
420   }
421 
422   @Override
423   public boolean isFileScanner() {
424     return true;
425   }
426 
427   // Test methods
428 
429   static final long getSeekCount() {
430     return seekCount.get();
431   }
432   static final void instrument() {
433     seekCount = new AtomicLong();
434   }
435 
436   @Override
437   public boolean shouldUseScanner(Scan scan, Store store, long oldestUnexpiredTS) {
438     // if the file has no entries, no need to validate or create a scanner.
439     byte[] cf = store.getFamily().getName();
440     TimeRange timeRange = scan.getColumnFamilyTimeRange().get(cf);
441     if (timeRange == null) {
442       timeRange = scan.getTimeRange();
443     }
444     return reader.passesTimerangeFilter(timeRange, oldestUnexpiredTS) && reader
445         .passesKeyRangeFilter(scan) && reader.passesBloomFilter(scan, scan.getFamilyMap().get(cf));
446   }
447 
448   @Override
449   @SuppressWarnings("deprecation")
450   public boolean seekToPreviousRow(Cell originalKey) throws IOException {
451     try {
452       try {
453         boolean keepSeeking = false;
454         Cell key = originalKey;
455         do {
456           KeyValue seekKey = KeyValueUtil.createFirstOnRow(key.getRowArray(), key.getRowOffset(),
457               key.getRowLength());
458           if (seekCount != null) seekCount.incrementAndGet();
459           if (!hfs.seekBefore(seekKey.getBuffer(), seekKey.getKeyOffset(),
460               seekKey.getKeyLength())) {
461             this.cur = null;
462             return false;
463           }
464           KeyValue firstKeyOfPreviousRow = KeyValueUtil.createFirstOnRow(hfs.getKeyValue()
465               .getRowArray(), hfs.getKeyValue().getRowOffset(), hfs.getKeyValue().getRowLength());
466 
467           if (seekCount != null) seekCount.incrementAndGet();
468           if (!seekAtOrAfter(hfs, firstKeyOfPreviousRow)) {
469             close();
470             return false;
471           }
472 
473           setCurrentCell(hfs.getKeyValue());
474           this.stopSkippingKVsIfNextRow = true;
475           boolean resultOfSkipKVs;
476           try {
477             resultOfSkipKVs = skipKVsNewerThanReadpoint();
478           } finally {
479             this.stopSkippingKVsIfNextRow = false;
480           }
481           if (!resultOfSkipKVs
482               || getComparator().compareRows(cur, firstKeyOfPreviousRow) > 0) {
483             keepSeeking = true;
484             key = firstKeyOfPreviousRow;
485             continue;
486           } else {
487             keepSeeking = false;
488           }
489         } while (keepSeeking);
490         return true;
491       } finally {
492         realSeekDone = true;
493       }
494     } catch (FileNotFoundException e) {
495       throw e;
496     } catch (IOException ioe) {
497       throw new IOException("Could not seekToPreviousRow " + this + " to key "
498           + originalKey, ioe);
499     }
500   }
501 
502   @Override
503   public boolean seekToLastRow() throws IOException {
504     byte[] lastRow = reader.getLastRowKey();
505     if (lastRow == null) {
506       return false;
507     }
508     KeyValue seekKey = KeyValueUtil.createFirstOnRow(lastRow);
509     if (seek(seekKey)) {
510       return true;
511     } else {
512       return seekToPreviousRow(seekKey);
513     }
514   }
515 
516   @Override
517   public boolean backwardSeek(Cell key) throws IOException {
518     seek(key);
519     if (cur == null
520         || getComparator().compareRows(cur.getRowArray(), cur.getRowOffset(),
521             cur.getRowLength(), key.getRowArray(), key.getRowOffset(),
522             key.getRowLength()) > 0) {
523       return seekToPreviousRow(key);
524     }
525     return true;
526   }
527 
528   @Override
529   public Cell getNextIndexedKey() {
530     return hfs.getNextIndexedKey();
531   }
532 }