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.io;
20  
21  import java.io.IOException;
22  import java.nio.ByteBuffer;
23  
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  import org.apache.hadoop.hbase.classification.InterfaceAudience;
27  import org.apache.hadoop.conf.Configuration;
28  import org.apache.hadoop.fs.FileSystem;
29  import org.apache.hadoop.fs.Path;
30  import org.apache.hadoop.hbase.Cell;
31  import org.apache.hadoop.hbase.HConstants;
32  import org.apache.hadoop.hbase.KeyValue;
33  import org.apache.hadoop.hbase.client.Scan;
34  import org.apache.hadoop.hbase.io.hfile.CacheConfig;
35  import org.apache.hadoop.hbase.io.hfile.HFileScanner;
36  import org.apache.hadoop.hbase.regionserver.StoreFile;
37  import org.apache.hadoop.hbase.util.Bytes;
38  
39  /**
40   * A facade for a {@link org.apache.hadoop.hbase.io.hfile.HFile.Reader} that serves up
41   * either the top or bottom half of a HFile where 'bottom' is the first half
42   * of the file containing the keys that sort lowest and 'top' is the second half
43   * of the file with keys that sort greater than those of the bottom half.
44   * The top includes the split files midkey, of the key that follows if it does
45   * not exist in the file.
46   *
47   * <p>This type works in tandem with the {@link Reference} type.  This class
48   * is used reading while Reference is used writing.
49   *
50   * <p>This file is not splitable.  Calls to {@link #midkey()} return null.
51   */
52  @InterfaceAudience.Private
53  public class HalfStoreFileReader extends StoreFile.Reader {
54    private static final Log LOG = LogFactory.getLog(HalfStoreFileReader.class);
55    final boolean top;
56    // This is the key we split around.  Its the first possible entry on a row:
57    // i.e. empty column and a timestamp of LATEST_TIMESTAMP.
58    protected final byte [] splitkey;
59  
60    protected final Cell splitCell;
61  
62    private Cell firstKey = null;
63  
64    private boolean firstKeySeeked = false;
65  
66    /**
67     * Creates a half file reader for a normal hfile.
68     * @param fs fileystem to read from
69     * @param p path to hfile
70     * @param cacheConf
71     * @param r original reference file (contains top or bottom)
72     * @param conf Configuration
73     * @throws IOException
74     */
75    public HalfStoreFileReader(final FileSystem fs, final Path p,
76        final CacheConfig cacheConf, final Reference r, final Configuration conf)
77        throws IOException {
78      super(fs, p, cacheConf, conf);
79      // This is not actual midkey for this half-file; its just border
80      // around which we split top and bottom.  Have to look in files to find
81      // actual last and first keys for bottom and top halves.  Half-files don't
82      // have an actual midkey themselves. No midkey is how we indicate file is
83      // not splittable.
84      this.splitkey = r.getSplitKey();
85      this.splitCell = new KeyValue.KeyOnlyKeyValue(this.splitkey, 0, this.splitkey.length);
86      // Is it top or bottom half?
87      this.top = Reference.isTopFileRegion(r.getFileRegion());
88    }
89  
90    /**
91     * Creates a half file reader for a hfile referred to by an hfilelink.
92     * @param fs fileystem to read from
93     * @param p path to hfile
94     * @param in {@link FSDataInputStreamWrapper}
95     * @param size Full size of the hfile file
96     * @param cacheConf
97     * @param r original reference file (contains top or bottom)
98     * @param conf Configuration
99     * @throws IOException
100    */
101   public HalfStoreFileReader(final FileSystem fs, final Path p, final FSDataInputStreamWrapper in,
102       long size, final CacheConfig cacheConf,  final Reference r, final Configuration conf)
103       throws IOException {
104     super(fs, p, in, size, cacheConf, conf);
105     // This is not actual midkey for this half-file; its just border
106     // around which we split top and bottom.  Have to look in files to find
107     // actual last and first keys for bottom and top halves.  Half-files don't
108     // have an actual midkey themselves. No midkey is how we indicate file is
109     // not splittable.
110     this.splitkey = r.getSplitKey();
111     this.splitCell = new KeyValue.KeyOnlyKeyValue(this.splitkey, 0, this.splitkey.length);
112     // Is it top or bottom half?
113     this.top = Reference.isTopFileRegion(r.getFileRegion());
114   }
115 
116   protected boolean isTop() {
117     return this.top;
118   }
119 
120   @Override
121   public HFileScanner getScanner(final boolean cacheBlocks,
122       final boolean pread, final boolean isCompaction) {
123     final HFileScanner s = super.getScanner(cacheBlocks, pread, isCompaction);
124     return new HFileScanner() {
125       final HFileScanner delegate = s;
126       public boolean atEnd = false;
127 
128       public Cell getKey() {
129         if (atEnd) return null;
130         return delegate.getKey();
131       }
132 
133       public String getKeyString() {
134         if (atEnd) return null;
135 
136         return delegate.getKeyString();
137       }
138 
139       public ByteBuffer getValue() {
140         if (atEnd) return null;
141 
142         return delegate.getValue();
143       }
144 
145       public String getValueString() {
146         if (atEnd) return null;
147 
148         return delegate.getValueString();
149       }
150 
151       public Cell getCell() {
152         if (atEnd) return null;
153 
154         return delegate.getCell();
155       }
156 
157       public boolean next() throws IOException {
158         if (atEnd) return false;
159 
160         boolean b = delegate.next();
161         if (!b) {
162           return b;
163         }
164         // constrain the bottom.
165         if (!top) {
166           if (getComparator().compare(splitCell, getKey()) <= 0) {
167             atEnd = true;
168             return false;
169           }
170         }
171         return true;
172       }
173 
174       @Override
175       public boolean seekTo() throws IOException {
176         if (top) {
177           int r = this.delegate.seekTo(splitCell);
178           if (r == HConstants.INDEX_KEY_MAGIC) {
179             return true;
180           }
181           if (r < 0) {
182             // midkey is < first key in file
183             return this.delegate.seekTo();
184           }
185           if (r > 0) {
186             return this.delegate.next();
187           }
188           return true;
189         }
190 
191         boolean b = delegate.seekTo();
192         if (!b) {
193           return b;
194         }
195         // Check key.
196         return (this.delegate.getReader().getComparator().compare(splitCell, getKey())) > 0;
197       }
198 
199       public org.apache.hadoop.hbase.io.hfile.HFile.Reader getReader() {
200         return this.delegate.getReader();
201       }
202 
203       public boolean isSeeked() {
204         return this.delegate.isSeeked();
205       }
206 
207       @Override
208       public int seekTo(Cell key) throws IOException {
209         if (top) {
210           if (getComparator().compareKeyIgnoresMvcc(key, splitCell) < 0) {
211             return -1;
212           }
213         } else {
214           if (getComparator().compareKeyIgnoresMvcc(key, splitCell) >= 0) {
215             // we would place the scanner in the second half.
216             // it might be an error to return false here ever...
217             boolean res = delegate.seekBefore(splitCell);
218             if (!res) {
219               throw new IOException(
220                   "Seeking for a key in bottom of file, but key exists in top of file, " +
221                   "failed on seekBefore(midkey)");
222             }
223             return 1;
224           }
225         }
226         return delegate.seekTo(key);
227       }
228 
229       @Override
230       public int reseekTo(Cell key) throws IOException {
231         // This function is identical to the corresponding seekTo function
232         // except
233         // that we call reseekTo (and not seekTo) on the delegate.
234         if (top) {
235           if (getComparator().compareKeyIgnoresMvcc(key, splitCell) < 0) {
236             return -1;
237           }
238         } else {
239           if (getComparator().compareKeyIgnoresMvcc(key, splitCell) >= 0) {
240             // we would place the scanner in the second half.
241             // it might be an error to return false here ever...
242             boolean res = delegate.seekBefore(splitCell);
243             if (!res) {
244               throw new IOException("Seeking for a key in bottom of file, but"
245                   + " key exists in top of file, failed on seekBefore(midkey)");
246             }
247             return 1;
248           }
249         }
250         if (atEnd) {
251           // skip the 'reseek' and just return 1.
252           return 1;
253         }
254         return delegate.reseekTo(key);
255       }
256 
257       @Override
258       public boolean seekBefore(Cell key) throws IOException {
259         if (top) {
260           Cell fk = getFirstKey();
261           if (getComparator().compareKeyIgnoresMvcc(key, fk) <= 0) {
262             return false;
263           }
264         } else {
265           // The equals sign isn't strictly necessary just here to be consistent
266           // with seekTo
267           if (getComparator().compareKeyIgnoresMvcc(key, splitCell) >= 0) {
268             boolean ret = this.delegate.seekBefore(splitCell);
269             if (ret) {
270               atEnd = false;
271             }
272             return ret;
273           }
274         }
275         boolean ret = this.delegate.seekBefore(key);
276         if (ret) {
277           atEnd = false;
278         }
279         return ret;
280       }
281 
282       @Override
283       public Cell getNextIndexedKey() {
284         return null;
285       }
286 
287       @Override
288       public void close() {
289         this.delegate.close();
290       }
291 
292       @Override
293       public void shipped() throws IOException {
294         this.delegate.shipped();
295       }
296     };
297   }
298   
299   @Override
300   public boolean passesKeyRangeFilter(Scan scan) {
301     return true;
302   }
303   
304   @Override
305   public Cell getLastKey() {
306     if (top) {
307       return super.getLastKey();
308     }
309     // Get a scanner that caches the block and that uses pread.
310     HFileScanner scanner = getScanner(true, true);
311     try {
312       if (scanner.seekBefore(this.splitCell)) {
313         return scanner.getKey();
314       }
315     } catch (IOException e) {
316       LOG.warn("Failed seekBefore " + Bytes.toStringBinary(this.splitkey), e);
317     } finally {
318       if (scanner != null) {
319         scanner.close();
320       }
321     }
322     return null;
323   }
324 
325   @Override
326   public Cell midkey() throws IOException {
327     // Returns null to indicate file is not splitable.
328     return null;
329   }
330 
331   @Override
332   public Cell getFirstKey() {
333     if (!firstKeySeeked) {
334       HFileScanner scanner = getScanner(true, true, false);
335       try {
336         if (scanner.seekTo()) {
337           this.firstKey = scanner.getKey();
338         }
339         firstKeySeeked = true;
340       } catch (IOException e) {
341         LOG.warn("Failed seekTo first KV in the file", e);
342       } finally {
343         if(scanner != null) {
344           scanner.close();
345         }
346       }
347     }
348     return this.firstKey;
349   }
350 
351   @Override
352   public long getEntries() {
353     // Estimate the number of entries as half the original file; this may be wildly inaccurate.
354     return super.getEntries() / 2;
355   }
356 
357   @Override
358   public long getFilterEntries() {
359     // Estimate the number of entries as half the original file; this may be wildly inaccurate.
360     return super.getFilterEntries() / 2;
361   }
362 }