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 byte[] 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 ByteBuffer 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 getKeyValue() {
152         if (atEnd) return null;
153 
154         return delegate.getKeyValue();
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           ByteBuffer bb = getKey();
167           if (getComparator().compareFlatKey(bb.array(), bb.arrayOffset(), bb.limit(),
168               splitkey, 0, splitkey.length) >= 0) {
169             atEnd = true;
170             return false;
171           }
172         }
173         return true;
174       }
175 
176       @Override
177       public boolean seekBefore(byte[] key) throws IOException {
178         return seekBefore(key, 0, key.length);
179       }
180 
181       @Override
182       public boolean seekBefore(byte [] key, int offset, int length)
183       throws IOException {
184         return seekBefore(new KeyValue.KeyOnlyKeyValue(key, offset, length));
185       }
186 
187       @Override
188       public boolean seekTo() throws IOException {
189         if (top) {
190           int r = this.delegate.seekTo(new KeyValue.KeyOnlyKeyValue(splitkey, 0, splitkey.length));
191           if (r == HConstants.INDEX_KEY_MAGIC) {
192             return true;
193           }
194           if (r < 0) {
195             // midkey is < first key in file
196             return this.delegate.seekTo();
197           }
198           if (r > 0) {
199             return this.delegate.next();
200           }
201           return true;
202         }
203 
204         boolean b = delegate.seekTo();
205         if (!b) {
206           return b;
207         }
208         // Check key.
209         ByteBuffer k = this.delegate.getKey();
210         return this.delegate.getReader().getComparator().
211           compareFlatKey(k.array(), k.arrayOffset(), k.limit(),
212             splitkey, 0, splitkey.length) < 0;
213       }
214 
215       @Override
216       public int seekTo(byte[] key) throws IOException {
217         return seekTo(key, 0, key.length);
218       }
219 
220       @Override
221       public int seekTo(byte[] key, int offset, int length) throws IOException {
222         return seekTo(new KeyValue.KeyOnlyKeyValue(key, offset, length));
223       }
224 
225       @Override
226       public int reseekTo(byte[] key) throws IOException {
227         return reseekTo(key, 0, key.length);
228       }
229 
230       @Override
231       public int reseekTo(byte[] key, int offset, int length)
232       throws IOException {
233         //This function is identical to the corresponding seekTo function except
234         //that we call reseekTo (and not seekTo) on the delegate.
235         return reseekTo(new KeyValue.KeyOnlyKeyValue(key, offset, length));
236       }
237 
238       public org.apache.hadoop.hbase.io.hfile.HFile.Reader getReader() {
239         return this.delegate.getReader();
240       }
241 
242       public boolean isSeeked() {
243         return this.delegate.isSeeked();
244       }
245 
246       @Override
247       public int seekTo(Cell key) throws IOException {
248         if (top) {
249           if (getComparator().compareOnlyKeyPortion(key, splitCell) < 0) {
250             return -1;
251           }
252         } else {
253           if (getComparator().compareOnlyKeyPortion(key, splitCell) >= 0) {
254             // we would place the scanner in the second half.
255             // it might be an error to return false here ever...
256             boolean res = delegate.seekBefore(splitCell);
257             if (!res) {
258               throw new IOException(
259                   "Seeking for a key in bottom of file, but key exists in top of file, " +
260                   "failed on seekBefore(midkey)");
261             }
262             return 1;
263           }
264         }
265         return delegate.seekTo(key);
266       }
267 
268       @Override
269       public int reseekTo(Cell key) throws IOException {
270         // This function is identical to the corresponding seekTo function
271         // except
272         // that we call reseekTo (and not seekTo) on the delegate.
273         if (top) {
274           if (getComparator().compareOnlyKeyPortion(key, splitCell) < 0) {
275             return -1;
276           }
277         } else {
278           if (getComparator().compareOnlyKeyPortion(key, splitCell) >= 0) {
279             // we would place the scanner in the second half.
280             // it might be an error to return false here ever...
281             boolean res = delegate.seekBefore(splitCell);
282             if (!res) {
283               throw new IOException("Seeking for a key in bottom of file, but"
284                   + " key exists in top of file, failed on seekBefore(midkey)");
285             }
286             return 1;
287           }
288         }
289         if (atEnd) {
290           // skip the 'reseek' and just return 1.
291           return 1;
292         }
293         return delegate.reseekTo(key);
294       }
295 
296       @Override
297       public boolean seekBefore(Cell key) throws IOException {
298         if (top) {
299           Cell fk = new KeyValue.KeyOnlyKeyValue(getFirstKey(), 0, getFirstKey().length);
300           if (getComparator().compareOnlyKeyPortion(key, fk) <= 0) {
301             return false;
302           }
303         } else {
304           // The equals sign isn't strictly necessary just here to be consistent
305           // with seekTo
306           if (getComparator().compareOnlyKeyPortion(key, splitCell) >= 0) {
307             boolean ret = this.delegate.seekBefore(splitCell);
308             if (ret) {
309               atEnd = false;
310             }
311             return ret;
312           }
313         }
314         boolean ret = this.delegate.seekBefore(key);
315         if (ret) {
316           atEnd = false;
317         }
318         return ret;
319       }
320 
321       @Override
322       public Cell getNextIndexedKey() {
323         return null;
324       }
325 
326       @Override
327       public void close() {
328       }
329     };
330   }
331   
332   @Override
333   public boolean passesKeyRangeFilter(Scan scan) {
334     return true;
335   }
336   
337   @Override
338   public byte[] getLastKey() {
339     if (top) {
340       return super.getLastKey();
341     }
342     // Get a scanner that caches the block and that uses pread.
343     HFileScanner scanner = getScanner(true, true);
344     try {
345       if (scanner.seekBefore(this.splitkey)) {
346         return Bytes.toBytes(scanner.getKey());
347       }
348     } catch (IOException e) {
349       LOG.warn("Failed seekBefore " + Bytes.toStringBinary(this.splitkey), e);
350     }
351     return null;
352   }
353 
354   @Override
355   public byte[] midkey() throws IOException {
356     // Returns null to indicate file is not splitable.
357     return null;
358   }
359 
360   @Override
361   public byte[] getFirstKey() {
362     if (!firstKeySeeked) {
363       HFileScanner scanner = getScanner(true, true, false);
364       try {
365         if (scanner.seekTo()) {
366           this.firstKey = Bytes.toBytes(scanner.getKey());
367         }
368         firstKeySeeked = true;
369       } catch (IOException e) {
370         LOG.warn("Failed seekTo first KV in the file", e);
371       }
372     }
373     return this.firstKey;
374   }
375 
376   @Override
377   public long getEntries() {
378     // Estimate the number of entries as half the original file; this may be wildly inaccurate.
379     return super.getEntries() / 2;
380   }
381 
382   @Override
383   public long getFilterEntries() {
384     // Estimate the number of entries as half the original file; this may be wildly inaccurate.
385     return super.getFilterEntries() / 2;
386   }
387 }