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.hfile; 019 020import java.io.ByteArrayInputStream; 021import java.io.DataInputStream; 022import java.io.DataOutputStream; 023import java.io.IOException; 024import java.io.SequenceInputStream; 025import java.util.ArrayList; 026import java.util.Collection; 027import java.util.Comparator; 028import java.util.List; 029import java.util.Map; 030import java.util.Objects; 031import java.util.Set; 032import java.util.SortedMap; 033import java.util.TreeMap; 034import org.apache.commons.io.IOUtils; 035import org.apache.hadoop.conf.Configuration; 036import org.apache.hadoop.fs.Path; 037import org.apache.hadoop.hbase.Cell; 038import org.apache.hadoop.hbase.CellUtil; 039import org.apache.hadoop.hbase.ExtendedCell; 040import org.apache.hadoop.hbase.KeyValue; 041import org.apache.hadoop.hbase.protobuf.ProtobufMagic; 042import org.apache.hadoop.hbase.security.SecurityUtil; 043import org.apache.hadoop.hbase.util.Bytes; 044import org.apache.yetus.audience.InterfaceAudience; 045import org.slf4j.Logger; 046import org.slf4j.LoggerFactory; 047 048import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations; 049 050import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 051import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; 052import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.BytesBytesPair; 053import org.apache.hadoop.hbase.shaded.protobuf.generated.HFileProtos; 054 055/** 056 * Metadata Map of attributes for HFile written out as HFile Trailer. Created by the Writer and 057 * added to the tail of the file just before close. Metadata includes core attributes such as last 058 * key seen, comparator used writing the file, etc. Clients can add their own attributes via 059 * {@link #append(byte[], byte[], boolean)} and they'll be persisted and available at read time. 060 * Reader creates the HFileInfo on open by reading the tail of the HFile. The parse of the HFile 061 * trailer also creates a {@link HFileContext}, a read-only data structure that includes bulk of the 062 * HFileInfo and extras that is safe to pass around when working on HFiles. 063 * @see HFileContext 064 */ 065@InterfaceAudience.Private 066public class HFileInfo implements SortedMap<byte[], byte[]> { 067 068 private static final Logger LOG = LoggerFactory.getLogger(HFileInfo.class); 069 070 static final String RESERVED_PREFIX = "hfile."; 071 static final byte[] RESERVED_PREFIX_BYTES = Bytes.toBytes(RESERVED_PREFIX); 072 static final byte[] LASTKEY = Bytes.toBytes(RESERVED_PREFIX + "LASTKEY"); 073 static final byte[] AVG_KEY_LEN = Bytes.toBytes(RESERVED_PREFIX + "AVG_KEY_LEN"); 074 static final byte[] AVG_VALUE_LEN = Bytes.toBytes(RESERVED_PREFIX + "AVG_VALUE_LEN"); 075 static final byte[] CREATE_TIME_TS = Bytes.toBytes(RESERVED_PREFIX + "CREATE_TIME_TS"); 076 static final byte[] TAGS_COMPRESSED = Bytes.toBytes(RESERVED_PREFIX + "TAGS_COMPRESSED"); 077 static final byte[] KEY_OF_BIGGEST_CELL = Bytes.toBytes(RESERVED_PREFIX + "KEY_OF_BIGGEST_CELL"); 078 static final byte[] LEN_OF_BIGGEST_CELL = Bytes.toBytes(RESERVED_PREFIX + "LEN_OF_BIGGEST_CELL"); 079 public static final byte[] MAX_TAGS_LEN = Bytes.toBytes(RESERVED_PREFIX + "MAX_TAGS_LEN"); 080 private final SortedMap<byte[], byte[]> map = new TreeMap<>(Bytes.BYTES_COMPARATOR); 081 082 /** 083 * We can read files whose major version is v2 IFF their minor version is at least 3. 084 */ 085 private static final int MIN_V2_MINOR_VERSION_WITH_PB = 3; 086 087 /** Maximum minor version supported by this HFile format */ 088 // We went to version 2 when we moved to pb'ing fileinfo and the trailer on 089 // the file. This version can read Writables version 1. 090 static final int MAX_MINOR_VERSION = 3; 091 092 /** Last key in the file. Filled in when we read in the file info */ 093 private ExtendedCell lastKeyCell = null; 094 /** Average key length read from file info */ 095 private int avgKeyLen = -1; 096 /** Average value length read from file info */ 097 private int avgValueLen = -1; 098 /** Biggest Cell in the file, key only. Filled in when we read in the file info */ 099 private Cell biggestCell = null; 100 /** Length of the biggest Cell */ 101 private long lenOfBiggestCell = -1; 102 private boolean includesMemstoreTS = false; 103 private boolean decodeMemstoreTS = false; 104 105 /** 106 * Blocks read from the load-on-open section, excluding data root index, meta index, and file 107 * info. 108 */ 109 private List<HFileBlock> loadOnOpenBlocks = new ArrayList<>(); 110 111 /** 112 * The iterator will track all blocks in load-on-open section, since we use the 113 * {@link org.apache.hadoop.hbase.io.ByteBuffAllocator} to manage the ByteBuffers in block now, so 114 * we must ensure that deallocate all ByteBuffers in the end. 115 */ 116 private HFileBlock.BlockIterator blockIter; 117 118 private HFileBlockIndex.CellBasedKeyBlockIndexReader dataIndexReader; 119 private HFileBlockIndex.ByteArrayKeyBlockIndexReader metaIndexReader; 120 121 private FixedFileTrailer trailer; 122 private HFileContext hfileContext; 123 private boolean initialized = false; 124 125 public HFileInfo() { 126 super(); 127 } 128 129 public HFileInfo(ReaderContext context, Configuration conf) throws IOException { 130 this.initTrailerAndContext(context, conf); 131 } 132 133 /** 134 * Append the given key/value pair to the file info, optionally checking the key prefix. 135 * @param k key to add 136 * @param v value to add 137 * @param checkPrefix whether to check that the provided key does not start with the reserved 138 * prefix 139 * @return this file info object 140 * @throws IOException if the key or value is invalid 141 * @throws NullPointerException if {@code key} or {@code value} is {@code null} 142 */ 143 public HFileInfo append(final byte[] k, final byte[] v, final boolean checkPrefix) 144 throws IOException { 145 Objects.requireNonNull(k, "key cannot be null"); 146 Objects.requireNonNull(v, "value cannot be null"); 147 148 if (checkPrefix && isReservedFileInfoKey(k)) { 149 throw new IOException("Keys with a " + HFileInfo.RESERVED_PREFIX + " are reserved"); 150 } 151 put(k, v); 152 return this; 153 } 154 155 /** Return true if the given file info key is reserved for internal use. */ 156 public static boolean isReservedFileInfoKey(byte[] key) { 157 return Bytes.startsWith(key, HFileInfo.RESERVED_PREFIX_BYTES); 158 } 159 160 @Override 161 public void clear() { 162 this.map.clear(); 163 } 164 165 @Override 166 public Comparator<? super byte[]> comparator() { 167 return map.comparator(); 168 } 169 170 @Override 171 public boolean containsKey(Object key) { 172 return map.containsKey(key); 173 } 174 175 @Override 176 public boolean containsValue(Object value) { 177 return map.containsValue(value); 178 } 179 180 @Override 181 public Set<java.util.Map.Entry<byte[], byte[]>> entrySet() { 182 return map.entrySet(); 183 } 184 185 @Override 186 public boolean equals(Object o) { 187 return map.equals(o); 188 } 189 190 @Override 191 public byte[] firstKey() { 192 return map.firstKey(); 193 } 194 195 @Override 196 public byte[] get(Object key) { 197 return map.get(key); 198 } 199 200 @Override 201 public int hashCode() { 202 return map.hashCode(); 203 } 204 205 @Override 206 public SortedMap<byte[], byte[]> headMap(byte[] toKey) { 207 return this.map.headMap(toKey); 208 } 209 210 @Override 211 public boolean isEmpty() { 212 return map.isEmpty(); 213 } 214 215 @Override 216 public Set<byte[]> keySet() { 217 return map.keySet(); 218 } 219 220 @Override 221 public byte[] lastKey() { 222 return map.lastKey(); 223 } 224 225 @Override 226 public byte[] put(byte[] key, byte[] value) { 227 return this.map.put(key, value); 228 } 229 230 @Override 231 public void putAll(Map<? extends byte[], ? extends byte[]> m) { 232 this.map.putAll(m); 233 } 234 235 @Override 236 public byte[] remove(Object key) { 237 return this.map.remove(key); 238 } 239 240 @Override 241 public int size() { 242 return map.size(); 243 } 244 245 @Override 246 public SortedMap<byte[], byte[]> subMap(byte[] fromKey, byte[] toKey) { 247 return this.map.subMap(fromKey, toKey); 248 } 249 250 @Override 251 public SortedMap<byte[], byte[]> tailMap(byte[] fromKey) { 252 return this.map.tailMap(fromKey); 253 } 254 255 @Override 256 public Collection<byte[]> values() { 257 return map.values(); 258 } 259 260 /** 261 * Write out this instance on the passed in <code>out</code> stream. We write it as a protobuf. 262 * @see #read(DataInputStream) 263 */ 264 void write(final DataOutputStream out) throws IOException { 265 HFileProtos.FileInfoProto.Builder builder = HFileProtos.FileInfoProto.newBuilder(); 266 for (Map.Entry<byte[], byte[]> e : this.map.entrySet()) { 267 HBaseProtos.BytesBytesPair.Builder bbpBuilder = HBaseProtos.BytesBytesPair.newBuilder(); 268 bbpBuilder.setFirst(UnsafeByteOperations.unsafeWrap(e.getKey())); 269 bbpBuilder.setSecond(UnsafeByteOperations.unsafeWrap(e.getValue())); 270 builder.addMapEntry(bbpBuilder.build()); 271 } 272 out.write(ProtobufMagic.PB_MAGIC); 273 builder.build().writeDelimitedTo(out); 274 } 275 276 /** 277 * Populate this instance with what we find on the passed in <code>in</code> stream. Can 278 * deserialize protobuf of old Writables format. 279 * @see #write(DataOutputStream) 280 */ 281 void read(final DataInputStream in) throws IOException { 282 // This code is tested over in TestHFileReaderV1 where we read an old hfile w/ this new code. 283 int pblen = ProtobufUtil.lengthOfPBMagic(); 284 byte[] pbuf = new byte[pblen]; 285 if (in.markSupported()) { 286 in.mark(pblen); 287 } 288 int read = in.read(pbuf); 289 if (read != pblen) { 290 throw new IOException("read=" + read + ", wanted=" + pblen); 291 } 292 if (ProtobufUtil.isPBMagicPrefix(pbuf)) { 293 parsePB(HFileProtos.FileInfoProto.parseDelimitedFrom(in)); 294 } else { 295 if (in.markSupported()) { 296 in.reset(); 297 parseWritable(in); 298 } else { 299 // We cannot use BufferedInputStream, it consumes more than we read from the underlying IS 300 ByteArrayInputStream bais = new ByteArrayInputStream(pbuf); 301 SequenceInputStream sis = new SequenceInputStream(bais, in); // Concatenate input streams 302 // TODO: Am I leaking anything here wrapping the passed in stream? We are not calling 303 // close on the wrapped streams but they should be let go after we leave this context? 304 // I see that we keep a reference to the passed in inputstream but since we no longer 305 // have a reference to this after we leave, we should be ok. 306 parseWritable(new DataInputStream(sis)); 307 } 308 } 309 } 310 311 /** 312 * Now parse the old Writable format. It was a list of Map entries. Each map entry was a key and a 313 * value of a byte []. The old map format had a byte before each entry that held a code which was 314 * short for the key or value type. We know it was a byte [] so in below we just read and dump it. 315 */ 316 void parseWritable(final DataInputStream in) throws IOException { 317 // First clear the map. 318 // Otherwise we will just accumulate entries every time this method is called. 319 this.map.clear(); 320 // Read the number of entries in the map 321 int entries = in.readInt(); 322 // Then read each key/value pair 323 for (int i = 0; i < entries; i++) { 324 byte[] key = Bytes.readByteArray(in); 325 // We used to read a byte that encoded the class type. 326 // Read and ignore it because it is always byte [] in hfile 327 in.readByte(); 328 byte[] value = Bytes.readByteArray(in); 329 this.map.put(key, value); 330 } 331 } 332 333 /** 334 * Fill our map with content of the pb we read off disk 335 * @param fip protobuf message to read 336 */ 337 void parsePB(final HFileProtos.FileInfoProto fip) { 338 this.map.clear(); 339 for (BytesBytesPair pair : fip.getMapEntryList()) { 340 this.map.put(pair.getFirst().toByteArray(), pair.getSecond().toByteArray()); 341 } 342 } 343 344 public void initTrailerAndContext(ReaderContext context, Configuration conf) throws IOException { 345 try { 346 boolean isHBaseChecksum = context.getInputStreamWrapper().shouldUseHBaseChecksum(); 347 trailer = FixedFileTrailer.readFromStream( 348 context.getInputStreamWrapper().getStream(isHBaseChecksum), context.getFileSize()); 349 Path path = context.getFilePath(); 350 checkFileVersion(path); 351 this.hfileContext = createHFileContext(context, path, trailer, conf); 352 context.getInputStreamWrapper().unbuffer(); 353 } catch (Throwable t) { 354 IOUtils.closeQuietly(context.getInputStreamWrapper(), 355 e -> LOG.warn("failed to close input stream wrapper", e)); 356 throw new CorruptHFileException( 357 "Problem reading HFile Trailer from file " + context.getFilePath(), t); 358 } 359 } 360 361 /** 362 * should be called after initTrailerAndContext 363 */ 364 public void initMetaAndIndex(HFile.Reader reader) throws IOException { 365 if (initialized) { 366 return; 367 } 368 369 ReaderContext context = reader.getContext(); 370 try { 371 HFileBlock.FSReader blockReader = reader.getUncachedBlockReader(); 372 // Initialize an block iterator, and parse load-on-open blocks in the following. 373 blockIter = blockReader.blockRange(trailer.getLoadOnOpenDataOffset(), 374 context.getFileSize() - trailer.getTrailerSize()); 375 // Data index. We also read statistics about the block index written after 376 // the root level. 377 HFileBlock dataBlockRootIndex = blockIter.nextBlockWithBlockType(BlockType.ROOT_INDEX); 378 HFileBlock metaBlockIndex = blockIter.nextBlockWithBlockType(BlockType.ROOT_INDEX); 379 loadMetaInfo(blockIter, hfileContext); 380 381 HFileIndexBlockEncoder indexBlockEncoder = 382 HFileIndexBlockEncoderImpl.createFromFileInfo(this); 383 this.dataIndexReader = new HFileBlockIndex.CellBasedKeyBlockIndexReaderV2( 384 trailer.createComparator(), trailer.getNumDataIndexLevels(), indexBlockEncoder); 385 dataIndexReader.readMultiLevelIndexRoot(dataBlockRootIndex, trailer.getDataIndexCount()); 386 reader.setDataBlockIndexReader(dataIndexReader); 387 // Meta index. 388 this.metaIndexReader = new HFileBlockIndex.ByteArrayKeyBlockIndexReader(1); 389 metaIndexReader.readRootIndex(metaBlockIndex, trailer.getMetaIndexCount()); 390 reader.setMetaBlockIndexReader(metaIndexReader); 391 392 reader.setDataBlockEncoder(HFileDataBlockEncoderImpl.createFromFileInfo(this)); 393 // Load-On-Open info 394 HFileBlock b; 395 while ((b = blockIter.nextBlock()) != null) { 396 loadOnOpenBlocks.add(b); 397 } 398 // close the block reader 399 context.getInputStreamWrapper().unbuffer(); 400 } catch (Throwable t) { 401 IOUtils.closeQuietly(context.getInputStreamWrapper(), 402 e -> LOG.warn("failed to close input stream wrapper", e)); 403 throw new CorruptHFileException( 404 "Problem reading data index and meta index from file " + context.getFilePath(), t); 405 } 406 initialized = true; 407 } 408 409 private HFileContext createHFileContext(ReaderContext readerContext, Path path, 410 FixedFileTrailer trailer, Configuration conf) throws IOException { 411 return new HFileContextBuilder().withHBaseCheckSum(true).withHFileName(path.getName()) 412 .withCompression(trailer.getCompressionCodec()) 413 .withDecompressionContext( 414 trailer.getCompressionCodec().getHFileDecompressionContextForConfiguration(conf)) 415 .withCellComparator(FixedFileTrailer.createComparator(trailer.getComparatorClassName())) 416 .withEncryptionContext(SecurityUtil.createEncryptionContext(conf, path, trailer, 417 readerContext.getManagedKeyDataCache(), readerContext.getSystemKeyCache())) 418 .build(); 419 } 420 421 private void loadMetaInfo(HFileBlock.BlockIterator blockIter, HFileContext hfileContext) 422 throws IOException { 423 read(blockIter.nextBlockWithBlockType(BlockType.FILE_INFO).getByteStream()); 424 byte[] creationTimeBytes = get(HFileInfo.CREATE_TIME_TS); 425 hfileContext.setFileCreateTime(creationTimeBytes == null ? 0 : Bytes.toLong(creationTimeBytes)); 426 byte[] tmp = get(HFileInfo.MAX_TAGS_LEN); 427 // max tag length is not present in the HFile means tags were not at all written to file. 428 if (tmp != null) { 429 hfileContext.setIncludesTags(true); 430 tmp = get(HFileInfo.TAGS_COMPRESSED); 431 if (tmp != null && Bytes.toBoolean(tmp)) { 432 hfileContext.setCompressTags(true); 433 } 434 } 435 // parse meta info 436 if (get(HFileInfo.LASTKEY) != null) { 437 lastKeyCell = new KeyValue.KeyOnlyKeyValue(get(HFileInfo.LASTKEY)); 438 } 439 if (get(HFileInfo.KEY_OF_BIGGEST_CELL) != null) { 440 biggestCell = new KeyValue.KeyOnlyKeyValue(get(HFileInfo.KEY_OF_BIGGEST_CELL)); 441 lenOfBiggestCell = Bytes.toLong(get(HFileInfo.LEN_OF_BIGGEST_CELL)); 442 } 443 avgKeyLen = Bytes.toInt(get(HFileInfo.AVG_KEY_LEN)); 444 avgValueLen = Bytes.toInt(get(HFileInfo.AVG_VALUE_LEN)); 445 byte[] keyValueFormatVersion = get(HFileWriterImpl.KEY_VALUE_VERSION); 446 includesMemstoreTS = keyValueFormatVersion != null 447 && Bytes.toInt(keyValueFormatVersion) == HFileWriterImpl.KEY_VALUE_VER_WITH_MEMSTORE; 448 hfileContext.setIncludesMvcc(includesMemstoreTS); 449 if (includesMemstoreTS) { 450 decodeMemstoreTS = Bytes.toLong(get(HFileWriterImpl.MAX_MEMSTORE_TS_KEY)) > 0; 451 } 452 } 453 454 /** 455 * File version check is a little sloppy. We read v3 files but can also read v2 files if their 456 * content has been pb'd; files written with 0.98. 457 */ 458 private void checkFileVersion(Path path) { 459 int majorVersion = trailer.getMajorVersion(); 460 if (majorVersion == getMajorVersion()) { 461 return; 462 } 463 int minorVersion = trailer.getMinorVersion(); 464 if (majorVersion == 2 && minorVersion >= MIN_V2_MINOR_VERSION_WITH_PB) { 465 return; 466 } 467 // We can read v3 or v2 versions of hfile. 468 throw new IllegalArgumentException("Invalid HFile version: major=" + trailer.getMajorVersion() 469 + ", minor=" + trailer.getMinorVersion() + ": expected at least " + "major=2 and minor=" 470 + MAX_MINOR_VERSION + ", path=" + path); 471 } 472 473 public void close() { 474 if (blockIter != null) { 475 blockIter.freeBlocks(); 476 } 477 } 478 479 public int getMajorVersion() { 480 return 3; 481 } 482 483 public void setTrailer(FixedFileTrailer trailer) { 484 this.trailer = trailer; 485 } 486 487 public FixedFileTrailer getTrailer() { 488 return this.trailer; 489 } 490 491 public HFileBlockIndex.CellBasedKeyBlockIndexReader getDataBlockIndexReader() { 492 return this.dataIndexReader; 493 } 494 495 public HFileBlockIndex.ByteArrayKeyBlockIndexReader getMetaBlockIndexReader() { 496 return this.metaIndexReader; 497 } 498 499 public HFileContext getHFileContext() { 500 return this.hfileContext; 501 } 502 503 public List<HFileBlock> getLoadOnOpenBlocks() { 504 return loadOnOpenBlocks; 505 } 506 507 public ExtendedCell getLastKeyCell() { 508 return lastKeyCell; 509 } 510 511 public int getAvgKeyLen() { 512 return avgKeyLen; 513 } 514 515 public int getAvgValueLen() { 516 return avgValueLen; 517 } 518 519 public String getKeyOfBiggestCell() { 520 return CellUtil.toString(biggestCell, false); 521 } 522 523 public long getLenOfBiggestCell() { 524 return lenOfBiggestCell; 525 } 526 527 public boolean shouldIncludeMemStoreTS() { 528 return includesMemstoreTS; 529 } 530 531 public boolean isDecodeMemstoreTS() { 532 return decodeMemstoreTS; 533 } 534}