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.regionserver; 019 020import java.io.FileNotFoundException; 021import java.io.IOException; 022import java.util.ArrayList; 023import java.util.Date; 024import java.util.List; 025import java.util.Map; 026import java.util.NavigableSet; 027import java.util.UUID; 028import java.util.concurrent.ConcurrentHashMap; 029import java.util.concurrent.atomic.AtomicLong; 030 031import org.apache.hadoop.conf.Configuration; 032import org.apache.hadoop.fs.FileSystem; 033import org.apache.hadoop.fs.Path; 034import org.apache.hadoop.hbase.ArrayBackedTag; 035import org.apache.hadoop.hbase.Cell; 036import org.apache.hadoop.hbase.CellBuilderType; 037import org.apache.hadoop.hbase.CellComparator; 038import org.apache.hadoop.hbase.DoNotRetryIOException; 039import org.apache.hadoop.hbase.ExtendedCellBuilderFactory; 040import org.apache.hadoop.hbase.HConstants; 041import org.apache.hadoop.hbase.TableName; 042import org.apache.hadoop.hbase.Tag; 043import org.apache.hadoop.hbase.TagType; 044import org.apache.hadoop.hbase.TagUtil; 045import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 046import org.apache.hadoop.hbase.client.Scan; 047import org.apache.hadoop.hbase.filter.Filter; 048import org.apache.hadoop.hbase.filter.FilterList; 049import org.apache.hadoop.hbase.io.compress.Compression; 050import org.apache.hadoop.hbase.io.hfile.CorruptHFileException; 051import org.apache.hadoop.hbase.mob.MobCacheConfig; 052import org.apache.hadoop.hbase.mob.MobConstants; 053import org.apache.hadoop.hbase.mob.MobFile; 054import org.apache.hadoop.hbase.mob.MobFileName; 055import org.apache.hadoop.hbase.mob.MobStoreEngine; 056import org.apache.hadoop.hbase.mob.MobUtils; 057import org.apache.hadoop.hbase.util.Bytes; 058import org.apache.hadoop.hbase.util.HFileArchiveUtil; 059import org.apache.hadoop.hbase.util.IdLock; 060import org.apache.yetus.audience.InterfaceAudience; 061import org.slf4j.Logger; 062import org.slf4j.LoggerFactory; 063 064/** 065 * The store implementation to save MOBs (medium objects), it extends the HStore. 066 * When a descriptor of a column family has the value "IS_MOB", it means this column family 067 * is a mob one. When a HRegion instantiate a store for this column family, the HMobStore is 068 * created. 069 * HMobStore is almost the same with the HStore except using different types of scanners. 070 * In the method of getScanner, the MobStoreScanner and MobReversedStoreScanner are returned. 071 * In these scanners, a additional seeks in the mob files should be performed after the seek 072 * to HBase is done. 073 * The store implements how we save MOBs by extending HStore. When a descriptor 074 * of a column family has the value "IS_MOB", it means this column family is a mob one. When a 075 * HRegion instantiate a store for this column family, the HMobStore is created. HMobStore is 076 * almost the same with the HStore except using different types of scanners. In the method of 077 * getScanner, the MobStoreScanner and MobReversedStoreScanner are returned. In these scanners, a 078 * additional seeks in the mob files should be performed after the seek in HBase is done. 079 */ 080@InterfaceAudience.Private 081public class HMobStore extends HStore { 082 private static final Logger LOG = LoggerFactory.getLogger(HMobStore.class); 083 private MobCacheConfig mobCacheConfig; 084 private Path homePath; 085 private Path mobFamilyPath; 086 private AtomicLong cellsCountCompactedToMob = new AtomicLong(); 087 private AtomicLong cellsCountCompactedFromMob = new AtomicLong(); 088 private AtomicLong cellsSizeCompactedToMob = new AtomicLong(); 089 private AtomicLong cellsSizeCompactedFromMob = new AtomicLong(); 090 private AtomicLong mobFlushCount = new AtomicLong(); 091 private AtomicLong mobFlushedCellsCount = new AtomicLong(); 092 private AtomicLong mobFlushedCellsSize = new AtomicLong(); 093 private AtomicLong mobScanCellsCount = new AtomicLong(); 094 private AtomicLong mobScanCellsSize = new AtomicLong(); 095 private ColumnFamilyDescriptor family; 096 private Map<String, List<Path>> map = new ConcurrentHashMap<>(); 097 private final IdLock keyLock = new IdLock(); 098 // When we add a MOB reference cell to the HFile, we will add 2 tags along with it 099 // 1. A ref tag with type TagType.MOB_REFERENCE_TAG_TYPE. This just denote this this cell is not 100 // original one but a ref to another MOB Cell. 101 // 2. Table name tag. It's very useful in cloning the snapshot. When reading from the cloning 102 // table, we need to find the original mob files by this table name. For details please see 103 // cloning snapshot for mob files. 104 private final byte[] refCellTags; 105 106 public HMobStore(final HRegion region, final ColumnFamilyDescriptor family, 107 final Configuration confParam) throws IOException { 108 super(region, family, confParam); 109 this.family = family; 110 this.mobCacheConfig = (MobCacheConfig) cacheConf; 111 this.homePath = MobUtils.getMobHome(conf); 112 this.mobFamilyPath = MobUtils.getMobFamilyPath(conf, this.getTableName(), 113 family.getNameAsString()); 114 List<Path> locations = new ArrayList<>(2); 115 locations.add(mobFamilyPath); 116 TableName tn = region.getTableDescriptor().getTableName(); 117 locations.add(HFileArchiveUtil.getStoreArchivePath(conf, tn, MobUtils.getMobRegionInfo(tn) 118 .getEncodedName(), family.getNameAsString())); 119 map.put(Bytes.toString(tn.getName()), locations); 120 List<Tag> tags = new ArrayList<>(2); 121 tags.add(MobConstants.MOB_REF_TAG); 122 Tag tableNameTag = new ArrayBackedTag(TagType.MOB_TABLE_NAME_TAG_TYPE, 123 getTableName().getName()); 124 tags.add(tableNameTag); 125 this.refCellTags = TagUtil.fromList(tags); 126 } 127 128 /** 129 * Creates the mob cache config. 130 */ 131 @Override 132 protected void createCacheConf(ColumnFamilyDescriptor family) { 133 cacheConf = new MobCacheConfig(conf, family); 134 } 135 136 /** 137 * Gets current config. 138 */ 139 public Configuration getConfiguration() { 140 return this.conf; 141 } 142 143 /** 144 * Gets the MobStoreScanner or MobReversedStoreScanner. In these scanners, a additional seeks in 145 * the mob files should be performed after the seek in HBase is done. 146 */ 147 @Override 148 protected KeyValueScanner createScanner(Scan scan, ScanInfo scanInfo, 149 NavigableSet<byte[]> targetCols, long readPt) throws IOException { 150 if (MobUtils.isRefOnlyScan(scan)) { 151 Filter refOnlyFilter = new MobReferenceOnlyFilter(); 152 Filter filter = scan.getFilter(); 153 if (filter != null) { 154 scan.setFilter(new FilterList(filter, refOnlyFilter)); 155 } else { 156 scan.setFilter(refOnlyFilter); 157 } 158 } 159 return scan.isReversed() ? new ReversedMobStoreScanner(this, scanInfo, scan, targetCols, readPt) 160 : new MobStoreScanner(this, scanInfo, scan, targetCols, readPt); 161 } 162 163 /** 164 * Creates the mob store engine. 165 */ 166 @Override 167 protected StoreEngine<?, ?, ?, ?> createStoreEngine(HStore store, Configuration conf, 168 CellComparator cellComparator) throws IOException { 169 MobStoreEngine engine = new MobStoreEngine(); 170 engine.createComponents(conf, store, cellComparator); 171 return engine; 172 } 173 174 /** 175 * Gets the temp directory. 176 * @return The temp directory. 177 */ 178 private Path getTempDir() { 179 return new Path(homePath, MobConstants.TEMP_DIR_NAME); 180 } 181 182 /** 183 * Creates the writer for the mob file in temp directory. 184 * @param date The latest date of written cells. 185 * @param maxKeyCount The key count. 186 * @param compression The compression algorithm. 187 * @param startKey The start key. 188 * @param isCompaction If the writer is used in compaction. 189 * @return The writer for the mob file. 190 * @throws IOException 191 */ 192 public StoreFileWriter createWriterInTmp(Date date, long maxKeyCount, 193 Compression.Algorithm compression, byte[] startKey, 194 boolean isCompaction) throws IOException { 195 if (startKey == null) { 196 startKey = HConstants.EMPTY_START_ROW; 197 } 198 Path path = getTempDir(); 199 return createWriterInTmp(MobUtils.formatDate(date), path, maxKeyCount, compression, startKey, 200 isCompaction); 201 } 202 203 /** 204 * Creates the writer for the del file in temp directory. 205 * The del file keeps tracking the delete markers. Its name has a suffix _del, 206 * the format is [0-9a-f]+(_del)?. 207 * @param date The latest date of written cells. 208 * @param maxKeyCount The key count. 209 * @param compression The compression algorithm. 210 * @param startKey The start key. 211 * @return The writer for the del file. 212 * @throws IOException 213 */ 214 public StoreFileWriter createDelFileWriterInTmp(Date date, long maxKeyCount, 215 Compression.Algorithm compression, byte[] startKey) throws IOException { 216 if (startKey == null) { 217 startKey = HConstants.EMPTY_START_ROW; 218 } 219 Path path = getTempDir(); 220 String suffix = UUID 221 .randomUUID().toString().replaceAll("-", "") + "_del"; 222 MobFileName mobFileName = MobFileName.create(startKey, MobUtils.formatDate(date), suffix); 223 return createWriterInTmp(mobFileName, path, maxKeyCount, compression, true); 224 } 225 226 /** 227 * Creates the writer for the mob file in temp directory. 228 * @param date The date string, its format is yyyymmmdd. 229 * @param basePath The basic path for a temp directory. 230 * @param maxKeyCount The key count. 231 * @param compression The compression algorithm. 232 * @param startKey The start key. 233 * @param isCompaction If the writer is used in compaction. 234 * @return The writer for the mob file. 235 * @throws IOException 236 */ 237 public StoreFileWriter createWriterInTmp(String date, Path basePath, long maxKeyCount, 238 Compression.Algorithm compression, byte[] startKey, 239 boolean isCompaction) throws IOException { 240 MobFileName mobFileName = MobFileName.create(startKey, date, UUID.randomUUID() 241 .toString().replaceAll("-", "")); 242 return createWriterInTmp(mobFileName, basePath, maxKeyCount, compression, isCompaction); 243 } 244 245 /** 246 * Creates the writer for the mob file in temp directory. 247 * @param mobFileName The mob file name. 248 * @param basePath The basic path for a temp directory. 249 * @param maxKeyCount The key count. 250 * @param compression The compression algorithm. 251 * @param isCompaction If the writer is used in compaction. 252 * @return The writer for the mob file. 253 * @throws IOException 254 */ 255 public StoreFileWriter createWriterInTmp(MobFileName mobFileName, Path basePath, 256 long maxKeyCount, Compression.Algorithm compression, 257 boolean isCompaction) throws IOException { 258 return MobUtils.createWriter(conf, region.getFilesystem(), family, 259 new Path(basePath, mobFileName.getFileName()), maxKeyCount, compression, mobCacheConfig, 260 cryptoContext, checksumType, bytesPerChecksum, blocksize, BloomType.NONE, isCompaction); 261 } 262 263 /** 264 * Commits the mob file. 265 * @param sourceFile The source file. 266 * @param targetPath The directory path where the source file is renamed to. 267 * @throws IOException 268 */ 269 public void commitFile(final Path sourceFile, Path targetPath) throws IOException { 270 if (sourceFile == null) { 271 return; 272 } 273 Path dstPath = new Path(targetPath, sourceFile.getName()); 274 validateMobFile(sourceFile); 275 String msg = "Renaming flushed file from " + sourceFile + " to " + dstPath; 276 LOG.info(msg); 277 Path parent = dstPath.getParent(); 278 if (!region.getFilesystem().exists(parent)) { 279 region.getFilesystem().mkdirs(parent); 280 } 281 if (!region.getFilesystem().rename(sourceFile, dstPath)) { 282 throw new IOException("Failed rename of " + sourceFile + " to " + dstPath); 283 } 284 } 285 286 /** 287 * Validates a mob file by opening and closing it. 288 * 289 * @param path the path to the mob file 290 */ 291 private void validateMobFile(Path path) throws IOException { 292 HStoreFile storeFile = null; 293 try { 294 storeFile = new HStoreFile(region.getFilesystem(), path, conf, this.mobCacheConfig, 295 BloomType.NONE, isPrimaryReplicaStore()); 296 storeFile.initReader(); 297 } catch (IOException e) { 298 LOG.error("Fail to open mob file[" + path + "], keep it in temp directory.", e); 299 throw e; 300 } finally { 301 if (storeFile != null) { 302 storeFile.closeStoreFile(false); 303 } 304 } 305 } 306 307 /** 308 * Reads the cell from the mob file, and the read point does not count. 309 * This is used for DefaultMobStoreCompactor where we can read empty value for the missing cell. 310 * @param reference The cell found in the HBase, its value is a path to a mob file. 311 * @param cacheBlocks Whether the scanner should cache blocks. 312 * @return The cell found in the mob file. 313 * @throws IOException 314 */ 315 public Cell resolve(Cell reference, boolean cacheBlocks) throws IOException { 316 return resolve(reference, cacheBlocks, -1, true); 317 } 318 319 /** 320 * Reads the cell from the mob file. 321 * @param reference The cell found in the HBase, its value is a path to a mob file. 322 * @param cacheBlocks Whether the scanner should cache blocks. 323 * @param readPt the read point. 324 * @param readEmptyValueOnMobCellMiss Whether return null value when the mob file is 325 * missing or corrupt. 326 * @return The cell found in the mob file. 327 * @throws IOException 328 */ 329 public Cell resolve(Cell reference, boolean cacheBlocks, long readPt, 330 boolean readEmptyValueOnMobCellMiss) throws IOException { 331 Cell result = null; 332 if (MobUtils.hasValidMobRefCellValue(reference)) { 333 String fileName = MobUtils.getMobFileName(reference); 334 Tag tableNameTag = MobUtils.getTableNameTag(reference); 335 if (tableNameTag != null) { 336 String tableNameString = Tag.getValueAsString(tableNameTag); 337 List<Path> locations = map.get(tableNameString); 338 if (locations == null) { 339 IdLock.Entry lockEntry = keyLock.getLockEntry(tableNameString.hashCode()); 340 try { 341 locations = map.get(tableNameString); 342 if (locations == null) { 343 locations = new ArrayList<>(2); 344 TableName tn = TableName.valueOf(tableNameString); 345 locations.add(MobUtils.getMobFamilyPath(conf, tn, family.getNameAsString())); 346 locations.add(HFileArchiveUtil.getStoreArchivePath(conf, tn, MobUtils 347 .getMobRegionInfo(tn).getEncodedName(), family.getNameAsString())); 348 map.put(tableNameString, locations); 349 } 350 } finally { 351 keyLock.releaseLockEntry(lockEntry); 352 } 353 } 354 result = readCell(locations, fileName, reference, cacheBlocks, readPt, 355 readEmptyValueOnMobCellMiss); 356 } 357 } 358 if (result == null) { 359 LOG.warn("The Cell result is null, assemble a new Cell with the same row,family," 360 + "qualifier,timestamp,type and tags but with an empty value to return."); 361 result = ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY) 362 .setRow(reference.getRowArray(), reference.getRowOffset(), reference.getRowLength()) 363 .setFamily(reference.getFamilyArray(), reference.getFamilyOffset(), 364 reference.getFamilyLength()) 365 .setQualifier(reference.getQualifierArray(), 366 reference.getQualifierOffset(), reference.getQualifierLength()) 367 .setTimestamp(reference.getTimestamp()) 368 .setType(reference.getTypeByte()) 369 .setValue(HConstants.EMPTY_BYTE_ARRAY) 370 .setTags(reference.getTagsArray(), reference.getTagsOffset(), 371 reference.getTagsLength()) 372 .build(); 373 } 374 return result; 375 } 376 377 /** 378 * Reads the cell from a mob file. 379 * The mob file might be located in different directories. 380 * 1. The working directory. 381 * 2. The archive directory. 382 * Reads the cell from the files located in both of the above directories. 383 * @param locations The possible locations where the mob files are saved. 384 * @param fileName The file to be read. 385 * @param search The cell to be searched. 386 * @param cacheMobBlocks Whether the scanner should cache blocks. 387 * @param readPt the read point. 388 * @param readEmptyValueOnMobCellMiss Whether return null value when the mob file is 389 * missing or corrupt. 390 * @return The found cell. Null if there's no such a cell. 391 * @throws IOException 392 */ 393 private Cell readCell(List<Path> locations, String fileName, Cell search, boolean cacheMobBlocks, 394 long readPt, boolean readEmptyValueOnMobCellMiss) throws IOException { 395 FileSystem fs = getFileSystem(); 396 Throwable throwable = null; 397 for (Path location : locations) { 398 MobFile file = null; 399 Path path = new Path(location, fileName); 400 try { 401 file = mobCacheConfig.getMobFileCache().openFile(fs, path, mobCacheConfig); 402 return readPt != -1 ? file.readCell(search, cacheMobBlocks, readPt) : file.readCell(search, 403 cacheMobBlocks); 404 } catch (IOException e) { 405 mobCacheConfig.getMobFileCache().evictFile(fileName); 406 throwable = e; 407 if ((e instanceof FileNotFoundException) || 408 (e.getCause() instanceof FileNotFoundException)) { 409 LOG.debug("Fail to read the cell, the mob file " + path + " doesn't exist", e); 410 } else if (e instanceof CorruptHFileException) { 411 LOG.error("The mob file " + path + " is corrupt", e); 412 break; 413 } else { 414 throw e; 415 } 416 } catch (NullPointerException e) { // HDFS 1.x - DFSInputStream.getBlockAt() 417 mobCacheConfig.getMobFileCache().evictFile(fileName); 418 LOG.debug("Fail to read the cell", e); 419 throwable = e; 420 } catch (AssertionError e) { // assert in HDFS 1.x - DFSInputStream.getBlockAt() 421 mobCacheConfig.getMobFileCache().evictFile(fileName); 422 LOG.debug("Fail to read the cell", e); 423 throwable = e; 424 } finally { 425 if (file != null) { 426 mobCacheConfig.getMobFileCache().closeFile(file); 427 } 428 } 429 } 430 LOG.error("The mob file " + fileName + " could not be found in the locations " + locations 431 + " or it is corrupt"); 432 if (readEmptyValueOnMobCellMiss) { 433 return null; 434 } else if ((throwable instanceof FileNotFoundException) 435 || (throwable.getCause() instanceof FileNotFoundException)) { 436 // The region is re-opened when FileNotFoundException is thrown. 437 // This is not necessary when MOB files cannot be found, because the store files 438 // in a region only contain the references to MOB files and a re-open on a region 439 // doesn't help fix the lost MOB files. 440 throw new DoNotRetryIOException(throwable); 441 } else if (throwable instanceof IOException) { 442 throw (IOException) throwable; 443 } else { 444 throw new IOException(throwable); 445 } 446 } 447 448 /** 449 * Gets the mob file path. 450 * @return The mob file path. 451 */ 452 public Path getPath() { 453 return mobFamilyPath; 454 } 455 456 public void updateCellsCountCompactedToMob(long count) { 457 cellsCountCompactedToMob.addAndGet(count); 458 } 459 460 public long getCellsCountCompactedToMob() { 461 return cellsCountCompactedToMob.get(); 462 } 463 464 public void updateCellsCountCompactedFromMob(long count) { 465 cellsCountCompactedFromMob.addAndGet(count); 466 } 467 468 public long getCellsCountCompactedFromMob() { 469 return cellsCountCompactedFromMob.get(); 470 } 471 472 public void updateCellsSizeCompactedToMob(long size) { 473 cellsSizeCompactedToMob.addAndGet(size); 474 } 475 476 public long getCellsSizeCompactedToMob() { 477 return cellsSizeCompactedToMob.get(); 478 } 479 480 public void updateCellsSizeCompactedFromMob(long size) { 481 cellsSizeCompactedFromMob.addAndGet(size); 482 } 483 484 public long getCellsSizeCompactedFromMob() { 485 return cellsSizeCompactedFromMob.get(); 486 } 487 488 public void updateMobFlushCount() { 489 mobFlushCount.incrementAndGet(); 490 } 491 492 public long getMobFlushCount() { 493 return mobFlushCount.get(); 494 } 495 496 public void updateMobFlushedCellsCount(long count) { 497 mobFlushedCellsCount.addAndGet(count); 498 } 499 500 public long getMobFlushedCellsCount() { 501 return mobFlushedCellsCount.get(); 502 } 503 504 public void updateMobFlushedCellsSize(long size) { 505 mobFlushedCellsSize.addAndGet(size); 506 } 507 508 public long getMobFlushedCellsSize() { 509 return mobFlushedCellsSize.get(); 510 } 511 512 public void updateMobScanCellsCount(long count) { 513 mobScanCellsCount.addAndGet(count); 514 } 515 516 public long getMobScanCellsCount() { 517 return mobScanCellsCount.get(); 518 } 519 520 public void updateMobScanCellsSize(long size) { 521 mobScanCellsSize.addAndGet(size); 522 } 523 524 public long getMobScanCellsSize() { 525 return mobScanCellsSize.get(); 526 } 527 528 public byte[] getRefCellTags() { 529 return this.refCellTags; 530 } 531}