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