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}