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.AtomicBoolean; 024import java.util.function.IntConsumer; 025import org.apache.hadoop.conf.Configuration; 026import org.apache.hadoop.hbase.ExtendedCell; 027import org.apache.hadoop.hbase.HConstants; 028import org.apache.hadoop.hbase.KeyValue; 029import org.apache.hadoop.hbase.PrivateCellUtil; 030import org.apache.hadoop.hbase.client.Scan; 031import org.apache.hadoop.hbase.io.hfile.CacheConfig; 032import org.apache.hadoop.hbase.io.hfile.HFileInfo; 033import org.apache.hadoop.hbase.io.hfile.HFileReaderImpl; 034import org.apache.hadoop.hbase.io.hfile.HFileScanner; 035import org.apache.hadoop.hbase.io.hfile.ReaderContext; 036import org.apache.hadoop.hbase.regionserver.StoreFileInfo; 037import org.apache.hadoop.hbase.regionserver.StoreFileReader; 038import org.apache.hadoop.hbase.util.Bytes; 039import org.apache.yetus.audience.InterfaceAudience; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042 043/** 044 * A facade for a {@link org.apache.hadoop.hbase.io.hfile.HFile.Reader} that serves up either the 045 * top or bottom half of a HFile where 'bottom' is the first half of the file containing the keys 046 * that sort lowest and 'top' is the second half of the file with keys that sort greater than those 047 * of the bottom half. The top includes the split files midkey, of the key that follows if it does 048 * not exist in the file. 049 * <p> 050 * This type works in tandem with the {@link Reference} type. This class is used reading while 051 * Reference is used writing. 052 * <p> 053 * This file is not splitable. Calls to {@link #midKey()} return null. 054 */ 055@InterfaceAudience.Private 056public class HalfStoreFileReader extends StoreFileReader { 057 private static final Logger LOG = LoggerFactory.getLogger(HalfStoreFileReader.class); 058 final boolean top; 059 // This is the key we split around. Its the first possible entry on a row: 060 // i.e. empty column and a timestamp of LATEST_TIMESTAMP. 061 protected final byte[] splitkey; 062 063 private final ExtendedCell splitCell; 064 065 private Optional<ExtendedCell> firstKey = Optional.empty(); 066 067 private boolean firstKeySeeked = false; 068 069 private AtomicBoolean closed = new AtomicBoolean(false); 070 071 /** 072 * Creates a half file reader for a hfile referred to by an hfilelink. 073 * @param context Reader context info 074 * @param fileInfo HFile info 075 * @param cacheConf CacheConfig 076 * @param r original reference file (contains top or bottom) 077 * @param conf Configuration 078 */ 079 public HalfStoreFileReader(final ReaderContext context, final HFileInfo fileInfo, 080 final CacheConfig cacheConf, final Reference r, StoreFileInfo storeFileInfo, 081 final Configuration conf) throws IOException { 082 super(context, fileInfo, cacheConf, storeFileInfo, 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 protected boolean isTop() { 095 return this.top; 096 } 097 098 @Override 099 protected HFileScanner getScanner(final boolean cacheBlocks, final boolean pread, 100 final boolean isCompaction) { 101 final HFileScanner s = super.getScanner(cacheBlocks, pread, isCompaction); 102 return new HFileScanner() { 103 final HFileScanner delegate = s; 104 public boolean atEnd = false; 105 106 @Override 107 public ExtendedCell getKey() { 108 if (atEnd) { 109 return null; 110 } 111 return delegate.getKey(); 112 } 113 114 @Override 115 public ByteBuffer getValue() { 116 if (atEnd) { 117 return null; 118 } 119 120 return delegate.getValue(); 121 } 122 123 @Override 124 public ExtendedCell getCell() { 125 if (atEnd) { 126 return null; 127 } 128 129 return delegate.getCell(); 130 } 131 132 @Override 133 public boolean next() throws IOException { 134 if (atEnd) return false; 135 136 boolean b = delegate.next(); 137 if (!b) { 138 return b; 139 } 140 // constrain the bottom. 141 if (!top) { 142 if (getComparator().compare(splitCell, getKey()) <= 0) { 143 atEnd = true; 144 return false; 145 } 146 } 147 return true; 148 } 149 150 @Override 151 public boolean seekTo() throws IOException { 152 if (top) { 153 int r = this.delegate.seekTo(splitCell); 154 if (r == HConstants.INDEX_KEY_MAGIC) { 155 return true; 156 } 157 if (r < 0) { 158 // midkey is < first key in file 159 return this.delegate.seekTo(); 160 } 161 if (r > 0) { 162 return this.delegate.next(); 163 } 164 return true; 165 } 166 167 boolean b = delegate.seekTo(); 168 if (!b) { 169 return b; 170 } 171 // Check key. 172 return (this.delegate.getReader().getComparator().compare(splitCell, getKey())) > 0; 173 } 174 175 @Override 176 public org.apache.hadoop.hbase.io.hfile.HFile.Reader getReader() { 177 return this.delegate.getReader(); 178 } 179 180 @Override 181 public boolean isSeeked() { 182 return this.delegate.isSeeked(); 183 } 184 185 @Override 186 public int seekTo(ExtendedCell key) throws IOException { 187 if (top) { 188 if (PrivateCellUtil.compareKeyIgnoresMvcc(getComparator(), key, splitCell) < 0) { 189 return -1; 190 } 191 } else { 192 if (PrivateCellUtil.compareKeyIgnoresMvcc(getComparator(), key, splitCell) >= 0) { 193 // we would place the scanner in the second half. 194 // it might be an error to return false here ever... 195 boolean res = delegate.seekBefore(splitCell); 196 if (!res) { 197 throw new IOException( 198 "Seeking for a key in bottom of file, but key exists in top of file, " 199 + "failed on seekBefore(midkey)"); 200 } 201 return 1; 202 } 203 } 204 return delegate.seekTo(key); 205 } 206 207 @Override 208 public int reseekTo(ExtendedCell key) throws IOException { 209 // This function is identical to the corresponding seekTo function 210 // except that we call reseekTo (and not seekTo) on the delegate. 211 if (top) { 212 if (PrivateCellUtil.compareKeyIgnoresMvcc(getComparator(), key, splitCell) < 0) { 213 return -1; 214 } 215 } else { 216 if (PrivateCellUtil.compareKeyIgnoresMvcc(getComparator(), key, splitCell) >= 0) { 217 // we would place the scanner in the second half. 218 // it might be an error to return false here ever... 219 boolean res = delegate.seekBefore(splitCell); 220 if (!res) { 221 throw new IOException("Seeking for a key in bottom of file, but" 222 + " key exists in top of file, failed on seekBefore(midkey)"); 223 } 224 return 1; 225 } 226 } 227 if (atEnd) { 228 // skip the 'reseek' and just return 1. 229 return 1; 230 } 231 return delegate.reseekTo(key); 232 } 233 234 @Override 235 public boolean seekBefore(ExtendedCell key) throws IOException { 236 if (top) { 237 Optional<ExtendedCell> fk = getFirstKey(); 238 if ( 239 fk.isPresent() 240 && PrivateCellUtil.compareKeyIgnoresMvcc(getComparator(), key, fk.get()) <= 0 241 ) { 242 return false; 243 } 244 } else { 245 // The equals sign isn't strictly necessary just here to be consistent 246 // with seekTo 247 if (PrivateCellUtil.compareKeyIgnoresMvcc(getComparator(), key, splitCell) >= 0) { 248 boolean ret = this.delegate.seekBefore(splitCell); 249 if (ret) { 250 atEnd = false; 251 } 252 return ret; 253 } 254 } 255 boolean ret = this.delegate.seekBefore(key); 256 if (ret) { 257 atEnd = false; 258 } 259 return ret; 260 } 261 262 @Override 263 public ExtendedCell getNextIndexedKey() { 264 return null; 265 } 266 267 @Override 268 public void close() { 269 this.delegate.close(); 270 } 271 272 @Override 273 public void shipped() throws IOException { 274 this.delegate.shipped(); 275 } 276 277 @Override 278 public void recordBlockSize(IntConsumer blockSizeConsumer) { 279 this.delegate.recordBlockSize(blockSizeConsumer); 280 } 281 }; 282 } 283 284 @Override 285 public boolean passesKeyRangeFilter(Scan scan) { 286 return true; 287 } 288 289 @Override 290 public Optional<ExtendedCell> 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, false); 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<ExtendedCell> midKey() throws IOException { 312 // Returns null to indicate file is not splitable. 313 return Optional.empty(); 314 } 315 316 @Override 317 public Optional<ExtendedCell> 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 348 /** 349 * Overrides close method to handle cache evictions for the referred file. If evictionOnClose is 350 * true, we will seek to the block containing the splitCell and evict all blocks from offset 0 up 351 * to that block offset if this is a bottom half reader, or the from the split block offset up to 352 * the end of the file if this is a top half reader. 353 * @param evictOnClose true if it should evict the file blocks from the cache. 354 */ 355 @Override 356 public void close(boolean evictOnClose) throws IOException { 357 if (closed.compareAndSet(false, true)) { 358 if (evictOnClose) { 359 final HFileReaderImpl.HFileScannerImpl s = 360 (HFileReaderImpl.HFileScannerImpl) super.getScanner(false, true, false); 361 final String reference = this.reader.getHFileInfo().getHFileContext().getHFileName(); 362 final String referred = StoreFileInfo.getReferredToRegionAndFile(reference).getSecond(); 363 s.seekTo(splitCell); 364 if (s.getCurBlock() != null) { 365 long offset = s.getCurBlock().getOffset(); 366 LOG.trace("Seeking to split cell in reader: {} for file: {} top: {}, split offset: {}", 367 this, reference, top, offset); 368 ((HFileReaderImpl) reader).getCacheConf().getBlockCache().ifPresent(cache -> { 369 int numEvictedReferred = top 370 ? cache.evictBlocksRangeByHfileName(referred, offset, Long.MAX_VALUE) 371 : cache.evictBlocksRangeByHfileName(referred, 0, offset); 372 int numEvictedReference = cache.evictBlocksByHfileName(reference); 373 LOG.trace( 374 "Closing reference: {}; referred file: {}; was top? {}; evicted for referred: {};" 375 + "evicted for reference: {}", 376 reference, referred, top, numEvictedReferred, numEvictedReference); 377 }); 378 } 379 s.close(); 380 reader.close(false); 381 } else { 382 reader.close(evictOnClose); 383 } 384 } 385 } 386}