001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.hbase.io;
020
021import java.io.IOException;
022import java.nio.ByteBuffer;
023import java.util.Optional;
024import java.util.concurrent.atomic.AtomicInteger;
025
026import org.apache.yetus.audience.InterfaceAudience;
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.fs.FileSystem;
031import org.apache.hadoop.fs.Path;
032import org.apache.hadoop.hbase.Cell;
033import org.apache.hadoop.hbase.CellUtil;
034import org.apache.hadoop.hbase.HConstants;
035import org.apache.hadoop.hbase.PrivateCellUtil;
036import org.apache.hadoop.hbase.KeyValue;
037import org.apache.hadoop.hbase.client.Scan;
038import org.apache.hadoop.hbase.io.hfile.CacheConfig;
039import org.apache.hadoop.hbase.io.hfile.HFileScanner;
040import org.apache.hadoop.hbase.regionserver.StoreFileReader;
041import org.apache.hadoop.hbase.util.Bytes;
042
043/**
044 * A facade for a {@link org.apache.hadoop.hbase.io.hfile.HFile.Reader} that serves up
045 * either the top or bottom half of a HFile where 'bottom' is the first half
046 * of the file containing the keys that sort lowest and 'top' is the second half
047 * of the file with keys that sort greater than those of the bottom half.
048 * The top includes the split files midkey, of the key that follows if it does
049 * not exist in the file.
050 *
051 * <p>This type works in tandem with the {@link Reference} type.  This class
052 * is used reading while Reference is used writing.
053 *
054 * <p>This file is not splitable.  Calls to {@link #midKey()} return null.
055 */
056@InterfaceAudience.Private
057public class HalfStoreFileReader extends StoreFileReader {
058  private static final Logger LOG = LoggerFactory.getLogger(HalfStoreFileReader.class);
059  final boolean top;
060  // This is the key we split around.  Its the first possible entry on a row:
061  // i.e. empty column and a timestamp of LATEST_TIMESTAMP.
062  protected final byte [] splitkey;
063
064  protected final Cell splitCell;
065
066  private Optional<Cell> firstKey = null;
067
068  private boolean firstKeySeeked = false;
069
070  /**
071   * Creates a half file reader for a normal hfile.
072   * @param fs fileystem to read from
073   * @param p path to hfile
074   * @param cacheConf
075   * @param r original reference file (contains top or bottom)
076   * @param conf Configuration
077   * @throws IOException
078   */
079  public HalfStoreFileReader(FileSystem fs, Path p, CacheConfig cacheConf, Reference r,
080      boolean isPrimaryReplicaStoreFile, AtomicInteger refCount, boolean shared, Configuration conf)
081      throws IOException {
082    super(fs, p, cacheConf, isPrimaryReplicaStoreFile, refCount, shared, conf);
083    // This is not actual midkey for this half-file; its just border
084    // around which we split top and bottom.  Have to look in files to find
085    // actual last and first keys for bottom and top halves.  Half-files don't
086    // have an actual midkey themselves. No midkey is how we indicate file is
087    // not splittable.
088    this.splitkey = r.getSplitKey();
089    this.splitCell = new KeyValue.KeyOnlyKeyValue(this.splitkey, 0, this.splitkey.length);
090    // Is it top or bottom half?
091    this.top = Reference.isTopFileRegion(r.getFileRegion());
092  }
093
094  /**
095   * Creates a half file reader for a hfile referred to by an hfilelink.
096   * @param fs fileystem to read from
097   * @param p path to hfile
098   * @param in {@link FSDataInputStreamWrapper}
099   * @param size Full size of the hfile file
100   * @param cacheConf
101   * @param r original reference file (contains top or bottom)
102   * @param conf Configuration
103   * @throws IOException
104   */
105  public HalfStoreFileReader(final FileSystem fs, final Path p, final FSDataInputStreamWrapper in,
106      long size, final CacheConfig cacheConf, final Reference r, boolean isPrimaryReplicaStoreFile,
107      AtomicInteger refCount, boolean shared, final Configuration conf) throws IOException {
108    super(fs, p, in, size, cacheConf, isPrimaryReplicaStoreFile, refCount, shared, conf);
109    // This is not actual midkey for this half-file; its just border
110    // around which we split top and bottom.  Have to look in files to find
111    // actual last and first keys for bottom and top halves.  Half-files don't
112    // have an actual midkey themselves. No midkey is how we indicate file is
113    // not splittable.
114    this.splitkey = r.getSplitKey();
115    this.splitCell = new KeyValue.KeyOnlyKeyValue(this.splitkey, 0, this.splitkey.length);
116    // Is it top or bottom half?
117    this.top = Reference.isTopFileRegion(r.getFileRegion());
118  }
119
120  protected boolean isTop() {
121    return this.top;
122  }
123
124  @Override
125  public HFileScanner getScanner(final boolean cacheBlocks,
126      final boolean pread, final boolean isCompaction) {
127    final HFileScanner s = super.getScanner(cacheBlocks, pread, isCompaction);
128    return new HFileScanner() {
129      final HFileScanner delegate = s;
130      public boolean atEnd = false;
131
132      @Override
133      public Cell getKey() {
134        if (atEnd) return null;
135        return delegate.getKey();
136      }
137
138      @Override
139      public String getKeyString() {
140        if (atEnd) return null;
141
142        return delegate.getKeyString();
143      }
144
145      @Override
146      public ByteBuffer getValue() {
147        if (atEnd) return null;
148
149        return delegate.getValue();
150      }
151
152      @Override
153      public String getValueString() {
154        if (atEnd) return null;
155
156        return delegate.getValueString();
157      }
158
159      @Override
160      public Cell getCell() {
161        if (atEnd) return null;
162
163        return delegate.getCell();
164      }
165
166      @Override
167      public boolean next() throws IOException {
168        if (atEnd) return false;
169
170        boolean b = delegate.next();
171        if (!b) {
172          return b;
173        }
174        // constrain the bottom.
175        if (!top) {
176          if (getComparator().compare(splitCell, getKey()) <= 0) {
177            atEnd = true;
178            return false;
179          }
180        }
181        return true;
182      }
183
184      @Override
185      public boolean seekTo() throws IOException {
186        if (top) {
187          int r = this.delegate.seekTo(splitCell);
188          if (r == HConstants.INDEX_KEY_MAGIC) {
189            return true;
190          }
191          if (r < 0) {
192            // midkey is < first key in file
193            return this.delegate.seekTo();
194          }
195          if (r > 0) {
196            return this.delegate.next();
197          }
198          return true;
199        }
200
201        boolean b = delegate.seekTo();
202        if (!b) {
203          return b;
204        }
205        // Check key.
206        return (this.delegate.getReader().getComparator().compare(splitCell, getKey())) > 0;
207      }
208
209      @Override
210      public org.apache.hadoop.hbase.io.hfile.HFile.Reader getReader() {
211        return this.delegate.getReader();
212      }
213
214      @Override
215      public boolean isSeeked() {
216        return this.delegate.isSeeked();
217      }
218
219      @Override
220      public int seekTo(Cell key) throws IOException {
221        if (top) {
222          if (PrivateCellUtil.compareKeyIgnoresMvcc(getComparator(), key, splitCell) < 0) {
223            return -1;
224          }
225        } else {
226          if (PrivateCellUtil.compareKeyIgnoresMvcc(getComparator(), key, splitCell) >= 0) {
227            // we would place the scanner in the second half.
228            // it might be an error to return false here ever...
229            boolean res = delegate.seekBefore(splitCell);
230            if (!res) {
231              throw new IOException(
232                  "Seeking for a key in bottom of file, but key exists in top of file, " +
233                  "failed on seekBefore(midkey)");
234            }
235            return 1;
236          }
237        }
238        return delegate.seekTo(key);
239      }
240
241      @Override
242      public int reseekTo(Cell key) throws IOException {
243        // This function is identical to the corresponding seekTo function
244        // except
245        // that we call reseekTo (and not seekTo) on the delegate.
246        if (top) {
247          if (PrivateCellUtil.compareKeyIgnoresMvcc(getComparator(), key, splitCell) < 0) {
248            return -1;
249          }
250        } else {
251          if (PrivateCellUtil.compareKeyIgnoresMvcc(getComparator(), key, splitCell) >= 0) {
252            // we would place the scanner in the second half.
253            // it might be an error to return false here ever...
254            boolean res = delegate.seekBefore(splitCell);
255            if (!res) {
256              throw new IOException("Seeking for a key in bottom of file, but"
257                  + " key exists in top of file, failed on seekBefore(midkey)");
258            }
259            return 1;
260          }
261        }
262        if (atEnd) {
263          // skip the 'reseek' and just return 1.
264          return 1;
265        }
266        return delegate.reseekTo(key);
267      }
268
269      @Override
270      public boolean seekBefore(Cell key) throws IOException {
271        if (top) {
272          Optional<Cell> fk = getFirstKey();
273          if (PrivateCellUtil.compareKeyIgnoresMvcc(getComparator(), key, fk.get()) <= 0) {
274            return false;
275          }
276        } else {
277          // The equals sign isn't strictly necessary just here to be consistent
278          // with seekTo
279          if (PrivateCellUtil.compareKeyIgnoresMvcc(getComparator(), key, splitCell) >= 0) {
280            boolean ret = this.delegate.seekBefore(splitCell);
281            if (ret) {
282              atEnd = false;
283            }
284            return ret;
285          }
286        }
287        boolean ret = this.delegate.seekBefore(key);
288        if (ret) {
289          atEnd = false;
290        }
291        return ret;
292      }
293
294      @Override
295      public Cell getNextIndexedKey() {
296        return null;
297      }
298
299      @Override
300      public void close() {
301        this.delegate.close();
302      }
303
304      @Override
305      public void shipped() throws IOException {
306        this.delegate.shipped();
307      }
308    };
309  }
310  
311  @Override
312  public boolean passesKeyRangeFilter(Scan scan) {
313    return true;
314  }
315  
316  @Override
317  public Optional<Cell> getLastKey() {
318    if (top) {
319      return super.getLastKey();
320    }
321    // Get a scanner that caches the block and that uses pread.
322    HFileScanner scanner = getScanner(true, true);
323    try {
324      if (scanner.seekBefore(this.splitCell)) {
325        return Optional.ofNullable(scanner.getKey());
326      }
327    } catch (IOException e) {
328      LOG.warn("Failed seekBefore " + Bytes.toStringBinary(this.splitkey), e);
329    } finally {
330      if (scanner != null) {
331        scanner.close();
332      }
333    }
334    return Optional.empty();
335  }
336
337  @Override
338  public Optional<Cell> midKey() throws IOException {
339    // Returns null to indicate file is not splitable.
340    return Optional.empty();
341  }
342
343  @Override
344  public Optional<Cell> getFirstKey() {
345    if (!firstKeySeeked) {
346      HFileScanner scanner = getScanner(true, true, false);
347      try {
348        if (scanner.seekTo()) {
349          this.firstKey = Optional.ofNullable(scanner.getKey());
350        }
351        firstKeySeeked = true;
352      } catch (IOException e) {
353        LOG.warn("Failed seekTo first KV in the file", e);
354      } finally {
355        if(scanner != null) {
356          scanner.close();
357        }
358      }
359    }
360    return this.firstKey;
361  }
362
363  @Override
364  public long getEntries() {
365    // Estimate the number of entries as half the original file; this may be wildly inaccurate.
366    return super.getEntries() / 2;
367  }
368
369  @Override
370  public long getFilterEntries() {
371    // Estimate the number of entries as half the original file; this may be wildly inaccurate.
372    return super.getFilterEntries() / 2;
373  }
374}