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