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.mob; 020 021import java.io.FileNotFoundException; 022import java.io.IOException; 023import java.text.ParseException; 024import java.text.SimpleDateFormat; 025import java.util.ArrayList; 026import java.util.Calendar; 027import java.util.Collection; 028import java.util.Date; 029import java.util.List; 030import java.util.Optional; 031import java.util.UUID; 032import java.util.concurrent.ExecutorService; 033import java.util.concurrent.RejectedExecutionException; 034import java.util.concurrent.RejectedExecutionHandler; 035import java.util.concurrent.SynchronousQueue; 036import java.util.concurrent.ThreadPoolExecutor; 037import java.util.concurrent.TimeUnit; 038 039import org.apache.hadoop.conf.Configuration; 040import org.apache.hadoop.fs.FileStatus; 041import org.apache.hadoop.fs.FileSystem; 042import org.apache.hadoop.fs.Path; 043import org.apache.hadoop.hbase.Cell; 044import org.apache.hadoop.hbase.CellComparator; 045import org.apache.hadoop.hbase.HConstants; 046import org.apache.hadoop.hbase.PrivateCellUtil; 047import org.apache.hadoop.hbase.TableName; 048import org.apache.hadoop.hbase.Tag; 049import org.apache.hadoop.hbase.TagType; 050import org.apache.hadoop.hbase.TagUtil; 051import org.apache.hadoop.hbase.backup.HFileArchiver; 052import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 053import org.apache.hadoop.hbase.client.MobCompactPartitionPolicy; 054import org.apache.hadoop.hbase.client.RegionInfo; 055import org.apache.hadoop.hbase.client.RegionInfoBuilder; 056import org.apache.hadoop.hbase.client.Scan; 057import org.apache.hadoop.hbase.client.TableDescriptor; 058import org.apache.hadoop.hbase.io.HFileLink; 059import org.apache.hadoop.hbase.io.compress.Compression; 060import org.apache.hadoop.hbase.io.crypto.Encryption; 061import org.apache.hadoop.hbase.io.hfile.CacheConfig; 062import org.apache.hadoop.hbase.io.hfile.HFile; 063import org.apache.hadoop.hbase.io.hfile.HFileContext; 064import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder; 065import org.apache.hadoop.hbase.master.locking.LockManager; 066import org.apache.hadoop.hbase.mob.compactions.MobCompactor; 067import org.apache.hadoop.hbase.mob.compactions.PartitionedMobCompactionRequest.CompactionPartitionId; 068import org.apache.hadoop.hbase.mob.compactions.PartitionedMobCompactor; 069import org.apache.hadoop.hbase.regionserver.BloomType; 070import org.apache.hadoop.hbase.regionserver.HStore; 071import org.apache.hadoop.hbase.regionserver.HStoreFile; 072import org.apache.hadoop.hbase.regionserver.StoreFileWriter; 073import org.apache.hadoop.hbase.util.Bytes; 074import org.apache.hadoop.hbase.util.ChecksumType; 075import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 076import org.apache.hadoop.hbase.util.FSUtils; 077import org.apache.hadoop.hbase.util.ReflectionUtils; 078import org.apache.hadoop.hbase.util.Threads; 079import org.apache.yetus.audience.InterfaceAudience; 080import org.slf4j.Logger; 081import org.slf4j.LoggerFactory; 082 083/** 084 * The mob utilities 085 */ 086@InterfaceAudience.Private 087public final class MobUtils { 088 089 private static final Logger LOG = LoggerFactory.getLogger(MobUtils.class); 090 private final static long WEEKLY_THRESHOLD_MULTIPLIER = 7; 091 private final static long MONTHLY_THRESHOLD_MULTIPLIER = 4 * WEEKLY_THRESHOLD_MULTIPLIER; 092 093 private static final ThreadLocal<SimpleDateFormat> LOCAL_FORMAT = 094 new ThreadLocal<SimpleDateFormat>() { 095 @Override 096 protected SimpleDateFormat initialValue() { 097 return new SimpleDateFormat("yyyyMMdd"); 098 } 099 }; 100 101 private static final byte[] REF_DELETE_MARKER_TAG_BYTES; 102 static { 103 List<Tag> tags = new ArrayList<>(); 104 tags.add(MobConstants.MOB_REF_TAG); 105 REF_DELETE_MARKER_TAG_BYTES = TagUtil.fromList(tags); 106 } 107 108 /** 109 * Private constructor to keep this class from being instantiated. 110 */ 111 private MobUtils() { 112 } 113 114 /** 115 * Formats a date to a string. 116 * @param date The date. 117 * @return The string format of the date, it's yyyymmdd. 118 */ 119 public static String formatDate(Date date) { 120 return LOCAL_FORMAT.get().format(date); 121 } 122 123 /** 124 * Parses the string to a date. 125 * @param dateString The string format of a date, it's yyyymmdd. 126 * @return A date. 127 * @throws ParseException 128 */ 129 public static Date parseDate(String dateString) throws ParseException { 130 return LOCAL_FORMAT.get().parse(dateString); 131 } 132 133 /** 134 * Get the first day of the input date's month 135 * @param calendar Calendar object 136 * @param date The date to find out its first day of that month 137 * @return The first day in the month 138 */ 139 public static Date getFirstDayOfMonth(final Calendar calendar, final Date date) { 140 141 calendar.setTime(date); 142 calendar.set(Calendar.HOUR_OF_DAY, 0); 143 calendar.set(Calendar.MINUTE, 0); 144 calendar.set(Calendar.SECOND, 0); 145 calendar.set(Calendar.MILLISECOND, 0); 146 calendar.set(Calendar.DAY_OF_MONTH, 1); 147 148 Date firstDayInMonth = calendar.getTime(); 149 return firstDayInMonth; 150 } 151 152 /** 153 * Get the first day of the input date's week 154 * @param calendar Calendar object 155 * @param date The date to find out its first day of that week 156 * @return The first day in the week 157 */ 158 public static Date getFirstDayOfWeek(final Calendar calendar, final Date date) { 159 160 calendar.setTime(date); 161 calendar.set(Calendar.HOUR_OF_DAY, 0); 162 calendar.set(Calendar.MINUTE, 0); 163 calendar.set(Calendar.SECOND, 0); 164 calendar.set(Calendar.MILLISECOND, 0); 165 calendar.setFirstDayOfWeek(Calendar.MONDAY); 166 calendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY); 167 168 Date firstDayInWeek = calendar.getTime(); 169 return firstDayInWeek; 170 } 171 172 /** 173 * Whether the current cell is a mob reference cell. 174 * @param cell The current cell. 175 * @return True if the cell has a mob reference tag, false if it doesn't. 176 */ 177 public static boolean isMobReferenceCell(Cell cell) { 178 if (cell.getTagsLength() > 0) { 179 Optional<Tag> tag = PrivateCellUtil.getTag(cell, TagType.MOB_REFERENCE_TAG_TYPE); 180 if (tag.isPresent()) { 181 return true; 182 } 183 } 184 return false; 185 } 186 187 /** 188 * Gets the table name tag. 189 * @param cell The current cell. 190 * @return The table name tag. 191 */ 192 public static Tag getTableNameTag(Cell cell) { 193 if (cell.getTagsLength() > 0) { 194 Optional<Tag> tag = PrivateCellUtil.getTag(cell, TagType.MOB_TABLE_NAME_TAG_TYPE); 195 if (tag.isPresent()) { 196 return tag.get(); 197 } 198 } 199 return null; 200 } 201 202 /** 203 * Whether the tag list has a mob reference tag. 204 * @param tags The tag list. 205 * @return True if the list has a mob reference tag, false if it doesn't. 206 */ 207 public static boolean hasMobReferenceTag(List<Tag> tags) { 208 if (!tags.isEmpty()) { 209 for (Tag tag : tags) { 210 if (tag.getType() == TagType.MOB_REFERENCE_TAG_TYPE) { 211 return true; 212 } 213 } 214 } 215 return false; 216 } 217 218 /** 219 * Indicates whether it's a raw scan. 220 * The information is set in the attribute "hbase.mob.scan.raw" of scan. 221 * For a mob cell, in a normal scan the scanners retrieves the mob cell from the mob file. 222 * In a raw scan, the scanner directly returns cell in HBase without retrieve the one in 223 * the mob file. 224 * @param scan The current scan. 225 * @return True if it's a raw scan. 226 */ 227 public static boolean isRawMobScan(Scan scan) { 228 byte[] raw = scan.getAttribute(MobConstants.MOB_SCAN_RAW); 229 try { 230 return raw != null && Bytes.toBoolean(raw); 231 } catch (IllegalArgumentException e) { 232 return false; 233 } 234 } 235 236 /** 237 * Indicates whether it's a reference only scan. 238 * The information is set in the attribute "hbase.mob.scan.ref.only" of scan. 239 * If it's a ref only scan, only the cells with ref tag are returned. 240 * @param scan The current scan. 241 * @return True if it's a ref only scan. 242 */ 243 public static boolean isRefOnlyScan(Scan scan) { 244 byte[] refOnly = scan.getAttribute(MobConstants.MOB_SCAN_REF_ONLY); 245 try { 246 return refOnly != null && Bytes.toBoolean(refOnly); 247 } catch (IllegalArgumentException e) { 248 return false; 249 } 250 } 251 252 /** 253 * Indicates whether the scan contains the information of caching blocks. 254 * The information is set in the attribute "hbase.mob.cache.blocks" of scan. 255 * @param scan The current scan. 256 * @return True when the Scan attribute specifies to cache the MOB blocks. 257 */ 258 public static boolean isCacheMobBlocks(Scan scan) { 259 byte[] cache = scan.getAttribute(MobConstants.MOB_CACHE_BLOCKS); 260 try { 261 return cache != null && Bytes.toBoolean(cache); 262 } catch (IllegalArgumentException e) { 263 return false; 264 } 265 } 266 267 /** 268 * Sets the attribute of caching blocks in the scan. 269 * 270 * @param scan 271 * The current scan. 272 * @param cacheBlocks 273 * True, set the attribute of caching blocks into the scan, the scanner with this scan 274 * caches blocks. 275 * False, the scanner doesn't cache blocks for this scan. 276 */ 277 public static void setCacheMobBlocks(Scan scan, boolean cacheBlocks) { 278 scan.setAttribute(MobConstants.MOB_CACHE_BLOCKS, Bytes.toBytes(cacheBlocks)); 279 } 280 281 /** 282 * Cleans the expired mob files. 283 * Cleans the files whose creation date is older than (current - columnFamily.ttl), and 284 * the minVersions of that column family is 0. 285 * @param fs The current file system. 286 * @param conf The current configuration. 287 * @param tableName The current table name. 288 * @param columnDescriptor The descriptor of the current column family. 289 * @param cacheConfig The cacheConfig that disables the block cache. 290 * @param current The current time. 291 * @throws IOException 292 */ 293 public static void cleanExpiredMobFiles(FileSystem fs, Configuration conf, TableName tableName, 294 ColumnFamilyDescriptor columnDescriptor, CacheConfig cacheConfig, long current) 295 throws IOException { 296 long timeToLive = columnDescriptor.getTimeToLive(); 297 if (Integer.MAX_VALUE == timeToLive) { 298 // no need to clean, because the TTL is not set. 299 return; 300 } 301 302 Calendar calendar = Calendar.getInstance(); 303 calendar.setTimeInMillis(current - timeToLive * 1000); 304 calendar.set(Calendar.HOUR_OF_DAY, 0); 305 calendar.set(Calendar.MINUTE, 0); 306 calendar.set(Calendar.SECOND, 0); 307 308 Date expireDate = calendar.getTime(); 309 310 LOG.info("MOB HFiles older than " + expireDate.toGMTString() + " will be deleted!"); 311 312 FileStatus[] stats = null; 313 Path mobTableDir = FSUtils.getTableDir(getMobHome(conf), tableName); 314 Path path = getMobFamilyPath(conf, tableName, columnDescriptor.getNameAsString()); 315 try { 316 stats = fs.listStatus(path); 317 } catch (FileNotFoundException e) { 318 LOG.warn("Failed to find the mob file " + path, e); 319 } 320 if (null == stats) { 321 // no file found 322 return; 323 } 324 List<HStoreFile> filesToClean = new ArrayList<>(); 325 int deletedFileCount = 0; 326 for (FileStatus file : stats) { 327 String fileName = file.getPath().getName(); 328 try { 329 if (HFileLink.isHFileLink(file.getPath())) { 330 HFileLink hfileLink = HFileLink.buildFromHFileLinkPattern(conf, file.getPath()); 331 fileName = hfileLink.getOriginPath().getName(); 332 } 333 334 Date fileDate = parseDate(MobFileName.getDateFromName(fileName)); 335 336 if (LOG.isDebugEnabled()) { 337 LOG.debug("Checking file " + fileName); 338 } 339 if (fileDate.getTime() < expireDate.getTime()) { 340 if (LOG.isDebugEnabled()) { 341 LOG.debug(fileName + " is an expired file"); 342 } 343 filesToClean 344 .add(new HStoreFile(fs, file.getPath(), conf, cacheConfig, BloomType.NONE, true)); 345 } 346 } catch (Exception e) { 347 LOG.error("Cannot parse the fileName " + fileName, e); 348 } 349 } 350 if (!filesToClean.isEmpty()) { 351 try { 352 removeMobFiles(conf, fs, tableName, mobTableDir, columnDescriptor.getName(), 353 filesToClean); 354 deletedFileCount = filesToClean.size(); 355 } catch (IOException e) { 356 LOG.error("Failed to delete the mob files " + filesToClean, e); 357 } 358 } 359 LOG.info(deletedFileCount + " expired mob files are deleted"); 360 } 361 362 /** 363 * Gets the root dir of the mob files. 364 * It's {HBASE_DIR}/mobdir. 365 * @param conf The current configuration. 366 * @return the root dir of the mob file. 367 */ 368 public static Path getMobHome(Configuration conf) { 369 Path hbaseDir = new Path(conf.get(HConstants.HBASE_DIR)); 370 return new Path(hbaseDir, MobConstants.MOB_DIR_NAME); 371 } 372 373 /** 374 * Gets the qualified root dir of the mob files. 375 * @param conf The current configuration. 376 * @return The qualified root dir. 377 * @throws IOException 378 */ 379 public static Path getQualifiedMobRootDir(Configuration conf) throws IOException { 380 Path hbaseDir = new Path(conf.get(HConstants.HBASE_DIR)); 381 Path mobRootDir = new Path(hbaseDir, MobConstants.MOB_DIR_NAME); 382 FileSystem fs = mobRootDir.getFileSystem(conf); 383 return mobRootDir.makeQualified(fs.getUri(), fs.getWorkingDirectory()); 384 } 385 386 /** 387 * Gets the region dir of the mob files. 388 * It's {HBASE_DIR}/mobdir/{namespace}/{tableName}/{regionEncodedName}. 389 * @param conf The current configuration. 390 * @param tableName The current table name. 391 * @return The region dir of the mob files. 392 */ 393 public static Path getMobRegionPath(Configuration conf, TableName tableName) { 394 Path tablePath = FSUtils.getTableDir(getMobHome(conf), tableName); 395 RegionInfo regionInfo = getMobRegionInfo(tableName); 396 return new Path(tablePath, regionInfo.getEncodedName()); 397 } 398 399 /** 400 * Gets the family dir of the mob files. 401 * It's {HBASE_DIR}/mobdir/{namespace}/{tableName}/{regionEncodedName}/{columnFamilyName}. 402 * @param conf The current configuration. 403 * @param tableName The current table name. 404 * @param familyName The current family name. 405 * @return The family dir of the mob files. 406 */ 407 public static Path getMobFamilyPath(Configuration conf, TableName tableName, String familyName) { 408 return new Path(getMobRegionPath(conf, tableName), familyName); 409 } 410 411 /** 412 * Gets the family dir of the mob files. 413 * It's {HBASE_DIR}/mobdir/{namespace}/{tableName}/{regionEncodedName}/{columnFamilyName}. 414 * @param regionPath The path of mob region which is a dummy one. 415 * @param familyName The current family name. 416 * @return The family dir of the mob files. 417 */ 418 public static Path getMobFamilyPath(Path regionPath, String familyName) { 419 return new Path(regionPath, familyName); 420 } 421 422 /** 423 * Gets the RegionInfo of the mob files. 424 * This is a dummy region. The mob files are not saved in a region in HBase. 425 * This is only used in mob snapshot. It's internally used only. 426 * @param tableName 427 * @return A dummy mob region info. 428 */ 429 public static RegionInfo getMobRegionInfo(TableName tableName) { 430 return RegionInfoBuilder.newBuilder(tableName) 431 .setStartKey(MobConstants.MOB_REGION_NAME_BYTES) 432 .setEndKey(HConstants.EMPTY_END_ROW) 433 .setSplit(false) 434 .setRegionId(0) 435 .build(); 436 } 437 438 /** 439 * Gets whether the current RegionInfo is a mob one. 440 * @param regionInfo The current RegionInfo. 441 * @return If true, the current RegionInfo is a mob one. 442 */ 443 public static boolean isMobRegionInfo(RegionInfo regionInfo) { 444 return regionInfo == null ? false : getMobRegionInfo(regionInfo.getTable()).getEncodedName() 445 .equals(regionInfo.getEncodedName()); 446 } 447 448 /** 449 * Gets whether the current region name follows the pattern of a mob region name. 450 * @param tableName The current table name. 451 * @param regionName The current region name. 452 * @return True if the current region name follows the pattern of a mob region name. 453 */ 454 public static boolean isMobRegionName(TableName tableName, byte[] regionName) { 455 return Bytes.equals(regionName, getMobRegionInfo(tableName).getRegionName()); 456 } 457 458 /** 459 * Gets the working directory of the mob compaction. 460 * @param root The root directory of the mob compaction. 461 * @param jobName The current job name. 462 * @return The directory of the mob compaction for the current job. 463 */ 464 public static Path getCompactionWorkingPath(Path root, String jobName) { 465 return new Path(root, jobName); 466 } 467 468 /** 469 * Archives the mob files. 470 * @param conf The current configuration. 471 * @param fs The current file system. 472 * @param tableName The table name. 473 * @param tableDir The table directory. 474 * @param family The name of the column family. 475 * @param storeFiles The files to be deleted. 476 * @throws IOException 477 */ 478 public static void removeMobFiles(Configuration conf, FileSystem fs, TableName tableName, 479 Path tableDir, byte[] family, Collection<HStoreFile> storeFiles) throws IOException { 480 HFileArchiver.archiveStoreFiles(conf, fs, getMobRegionInfo(tableName), tableDir, family, 481 storeFiles); 482 } 483 484 /** 485 * Creates a mob reference KeyValue. 486 * The value of the mob reference KeyValue is mobCellValueSize + mobFileName. 487 * @param cell The original Cell. 488 * @param fileName The mob file name where the mob reference KeyValue is written. 489 * @param tableNameTag The tag of the current table name. It's very important in 490 * cloning the snapshot. 491 * @return The mob reference KeyValue. 492 */ 493 public static Cell createMobRefCell(Cell cell, byte[] fileName, Tag tableNameTag) { 494 // Append the tags to the KeyValue. 495 // The key is same, the value is the filename of the mob file 496 List<Tag> tags = new ArrayList<>(); 497 // Add the ref tag as the 1st one. 498 tags.add(MobConstants.MOB_REF_TAG); 499 // Add the tag of the source table name, this table is where this mob file is flushed 500 // from. 501 // It's very useful in cloning the snapshot. When reading from the cloning table, we need to 502 // find the original mob files by this table name. For details please see cloning 503 // snapshot for mob files. 504 tags.add(tableNameTag); 505 return createMobRefCell(cell, fileName, TagUtil.fromList(tags)); 506 } 507 508 public static Cell createMobRefCell(Cell cell, byte[] fileName, byte[] refCellTags) { 509 byte[] refValue = Bytes.add(Bytes.toBytes(cell.getValueLength()), fileName); 510 return PrivateCellUtil.createCell(cell, refValue, TagUtil.concatTags(refCellTags, cell)); 511 } 512 513 /** 514 * Creates a writer for the mob file in temp directory. 515 * @param conf The current configuration. 516 * @param fs The current file system. 517 * @param family The descriptor of the current column family. 518 * @param date The date string, its format is yyyymmmdd. 519 * @param basePath The basic path for a temp directory. 520 * @param maxKeyCount The key count. 521 * @param compression The compression algorithm. 522 * @param startKey The hex string of the start key. 523 * @param cacheConfig The current cache config. 524 * @param cryptoContext The encryption context. 525 * @param isCompaction If the writer is used in compaction. 526 * @return The writer for the mob file. 527 * @throws IOException 528 */ 529 public static StoreFileWriter createWriter(Configuration conf, FileSystem fs, 530 ColumnFamilyDescriptor family, String date, Path basePath, long maxKeyCount, 531 Compression.Algorithm compression, String startKey, CacheConfig cacheConfig, 532 Encryption.Context cryptoContext, boolean isCompaction) 533 throws IOException { 534 MobFileName mobFileName = MobFileName.create(startKey, date, 535 UUID.randomUUID().toString().replaceAll("-", "")); 536 return createWriter(conf, fs, family, mobFileName, basePath, maxKeyCount, compression, 537 cacheConfig, cryptoContext, isCompaction); 538 } 539 540 /** 541 * Creates a writer for the ref file in temp directory. 542 * @param conf The current configuration. 543 * @param fs The current file system. 544 * @param family The descriptor of the current column family. 545 * @param basePath The basic path for a temp directory. 546 * @param maxKeyCount The key count. 547 * @param cacheConfig The current cache config. 548 * @param cryptoContext The encryption context. 549 * @param isCompaction If the writer is used in compaction. 550 * @return The writer for the mob file. 551 * @throws IOException 552 */ 553 public static StoreFileWriter createRefFileWriter(Configuration conf, FileSystem fs, 554 ColumnFamilyDescriptor family, Path basePath, long maxKeyCount, CacheConfig cacheConfig, 555 Encryption.Context cryptoContext, boolean isCompaction) 556 throws IOException { 557 return createWriter(conf, fs, family, 558 new Path(basePath, UUID.randomUUID().toString().replaceAll("-", "")), maxKeyCount, 559 family.getCompactionCompressionType(), cacheConfig, cryptoContext, 560 HStore.getChecksumType(conf), HStore.getBytesPerChecksum(conf), family.getBlocksize(), 561 family.getBloomFilterType(), isCompaction); 562 } 563 564 /** 565 * Creates a writer for the mob file in temp directory. 566 * @param conf The current configuration. 567 * @param fs The current file system. 568 * @param family The descriptor of the current column family. 569 * @param date The date string, its format is yyyymmmdd. 570 * @param basePath The basic path for a temp directory. 571 * @param maxKeyCount The key count. 572 * @param compression The compression algorithm. 573 * @param startKey The start key. 574 * @param cacheConfig The current cache config. 575 * @param cryptoContext The encryption context. 576 * @param isCompaction If the writer is used in compaction. 577 * @return The writer for the mob file. 578 * @throws IOException 579 */ 580 public static StoreFileWriter createWriter(Configuration conf, FileSystem fs, 581 ColumnFamilyDescriptor family, String date, Path basePath, long maxKeyCount, 582 Compression.Algorithm compression, byte[] startKey, CacheConfig cacheConfig, 583 Encryption.Context cryptoContext, boolean isCompaction) 584 throws IOException { 585 MobFileName mobFileName = MobFileName.create(startKey, date, 586 UUID.randomUUID().toString().replaceAll("-", "")); 587 return createWriter(conf, fs, family, mobFileName, basePath, maxKeyCount, compression, 588 cacheConfig, cryptoContext, isCompaction); 589 } 590 591 /** 592 * Creates a writer for the del file in temp directory. 593 * @param conf The current configuration. 594 * @param fs The current file system. 595 * @param family The descriptor of the current column family. 596 * @param date The date string, its format is yyyymmmdd. 597 * @param basePath The basic path for a temp directory. 598 * @param maxKeyCount The key count. 599 * @param compression The compression algorithm. 600 * @param startKey The start key. 601 * @param cacheConfig The current cache config. 602 * @param cryptoContext The encryption context. 603 * @return The writer for the del file. 604 * @throws IOException 605 */ 606 public static StoreFileWriter createDelFileWriter(Configuration conf, FileSystem fs, 607 ColumnFamilyDescriptor family, String date, Path basePath, long maxKeyCount, 608 Compression.Algorithm compression, byte[] startKey, CacheConfig cacheConfig, 609 Encryption.Context cryptoContext) 610 throws IOException { 611 String suffix = UUID 612 .randomUUID().toString().replaceAll("-", "") + "_del"; 613 MobFileName mobFileName = MobFileName.create(startKey, date, suffix); 614 return createWriter(conf, fs, family, mobFileName, basePath, maxKeyCount, compression, 615 cacheConfig, cryptoContext, true); 616 } 617 618 /** 619 * Creates a writer for the mob file in temp directory. 620 * @param conf The current configuration. 621 * @param fs The current file system. 622 * @param family The descriptor of the current column family. 623 * @param mobFileName The mob file name. 624 * @param basePath The basic path for a temp directory. 625 * @param maxKeyCount The key count. 626 * @param compression The compression algorithm. 627 * @param cacheConfig The current cache config. 628 * @param cryptoContext The encryption context. 629 * @param isCompaction If the writer is used in compaction. 630 * @return The writer for the mob file. 631 * @throws IOException 632 */ 633 public static StoreFileWriter createWriter(Configuration conf, FileSystem fs, 634 ColumnFamilyDescriptor family, MobFileName mobFileName, Path basePath, long maxKeyCount, 635 Compression.Algorithm compression, CacheConfig cacheConfig, Encryption.Context cryptoContext, 636 boolean isCompaction) 637 throws IOException { 638 return createWriter(conf, fs, family, 639 new Path(basePath, mobFileName.getFileName()), maxKeyCount, compression, cacheConfig, 640 cryptoContext, HStore.getChecksumType(conf), HStore.getBytesPerChecksum(conf), 641 family.getBlocksize(), BloomType.NONE, isCompaction); 642 } 643 644 /** 645 * Creates a writer for the mob file in temp directory. 646 * @param conf The current configuration. 647 * @param fs The current file system. 648 * @param family The descriptor of the current column family. 649 * @param path The path for a temp directory. 650 * @param maxKeyCount The key count. 651 * @param compression The compression algorithm. 652 * @param cacheConfig The current cache config. 653 * @param cryptoContext The encryption context. 654 * @param checksumType The checksum type. 655 * @param bytesPerChecksum The bytes per checksum. 656 * @param blocksize The HFile block size. 657 * @param bloomType The bloom filter type. 658 * @param isCompaction If the writer is used in compaction. 659 * @return The writer for the mob file. 660 * @throws IOException 661 */ 662 public static StoreFileWriter createWriter(Configuration conf, FileSystem fs, 663 ColumnFamilyDescriptor family, Path path, long maxKeyCount, 664 Compression.Algorithm compression, CacheConfig cacheConfig, Encryption.Context cryptoContext, 665 ChecksumType checksumType, int bytesPerChecksum, int blocksize, BloomType bloomType, 666 boolean isCompaction) 667 throws IOException { 668 if (compression == null) { 669 compression = HFile.DEFAULT_COMPRESSION_ALGORITHM; 670 } 671 final CacheConfig writerCacheConf; 672 if (isCompaction) { 673 writerCacheConf = new CacheConfig(cacheConfig); 674 writerCacheConf.setCacheDataOnWrite(false); 675 } else { 676 writerCacheConf = cacheConfig; 677 } 678 HFileContext hFileContext = new HFileContextBuilder().withCompression(compression) 679 .withIncludesMvcc(true).withIncludesTags(true) 680 .withCompressTags(family.isCompressTags()) 681 .withChecksumType(checksumType) 682 .withBytesPerCheckSum(bytesPerChecksum) 683 .withBlockSize(blocksize) 684 .withHBaseCheckSum(true).withDataBlockEncoding(family.getDataBlockEncoding()) 685 .withEncryptionContext(cryptoContext) 686 .withCreateTime(EnvironmentEdgeManager.currentTime()).build(); 687 688 StoreFileWriter w = new StoreFileWriter.Builder(conf, writerCacheConf, fs) 689 .withFilePath(path) 690 .withComparator(CellComparator.getInstance()).withBloomType(bloomType) 691 .withMaxKeyCount(maxKeyCount).withFileContext(hFileContext).build(); 692 return w; 693 } 694 695 /** 696 * Commits the mob file. 697 * @param conf The current configuration. 698 * @param fs The current file system. 699 * @param sourceFile The path where the mob file is saved. 700 * @param targetPath The directory path where the source file is renamed to. 701 * @param cacheConfig The current cache config. 702 * @return The target file path the source file is renamed to. 703 * @throws IOException 704 */ 705 public static Path commitFile(Configuration conf, FileSystem fs, final Path sourceFile, 706 Path targetPath, CacheConfig cacheConfig) throws IOException { 707 if (sourceFile == null) { 708 return null; 709 } 710 Path dstPath = new Path(targetPath, sourceFile.getName()); 711 validateMobFile(conf, fs, sourceFile, cacheConfig, true); 712 String msg = "Renaming flushed file from " + sourceFile + " to " + dstPath; 713 LOG.info(msg); 714 Path parent = dstPath.getParent(); 715 if (!fs.exists(parent)) { 716 fs.mkdirs(parent); 717 } 718 if (!fs.rename(sourceFile, dstPath)) { 719 throw new IOException("Failed rename of " + sourceFile + " to " + dstPath); 720 } 721 return dstPath; 722 } 723 724 /** 725 * Validates a mob file by opening and closing it. 726 * @param conf The current configuration. 727 * @param fs The current file system. 728 * @param path The path where the mob file is saved. 729 * @param cacheConfig The current cache config. 730 */ 731 private static void validateMobFile(Configuration conf, FileSystem fs, Path path, 732 CacheConfig cacheConfig, boolean primaryReplica) throws IOException { 733 HStoreFile storeFile = null; 734 try { 735 storeFile = new HStoreFile(fs, path, conf, cacheConfig, BloomType.NONE, primaryReplica); 736 storeFile.initReader(); 737 } catch (IOException e) { 738 LOG.error("Failed to open mob file[" + path + "], keep it in temp directory.", e); 739 throw e; 740 } finally { 741 if (storeFile != null) { 742 storeFile.closeStoreFile(false); 743 } 744 } 745 } 746 747 /** 748 * Indicates whether the current mob ref cell has a valid value. 749 * A mob ref cell has a mob reference tag. 750 * The value of a mob ref cell consists of two parts, real mob value length and mob file name. 751 * The real mob value length takes 4 bytes. 752 * The remaining part is the mob file name. 753 * @param cell The mob ref cell. 754 * @return True if the cell has a valid value. 755 */ 756 public static boolean hasValidMobRefCellValue(Cell cell) { 757 return cell.getValueLength() > Bytes.SIZEOF_INT; 758 } 759 760 /** 761 * Gets the mob value length from the mob ref cell. 762 * A mob ref cell has a mob reference tag. 763 * The value of a mob ref cell consists of two parts, real mob value length and mob file name. 764 * The real mob value length takes 4 bytes. 765 * The remaining part is the mob file name. 766 * @param cell The mob ref cell. 767 * @return The real mob value length. 768 */ 769 public static int getMobValueLength(Cell cell) { 770 return PrivateCellUtil.getValueAsInt(cell); 771 } 772 773 /** 774 * Gets the mob file name from the mob ref cell. 775 * A mob ref cell has a mob reference tag. 776 * The value of a mob ref cell consists of two parts, real mob value length and mob file name. 777 * The real mob value length takes 4 bytes. 778 * The remaining part is the mob file name. 779 * @param cell The mob ref cell. 780 * @return The mob file name. 781 */ 782 public static String getMobFileName(Cell cell) { 783 return Bytes.toString(cell.getValueArray(), cell.getValueOffset() + Bytes.SIZEOF_INT, 784 cell.getValueLength() - Bytes.SIZEOF_INT); 785 } 786 787 /** 788 * Gets the table name used in the table lock. 789 * The table lock name is a dummy one, it's not a table name. It's tableName + ".mobLock". 790 * @param tn The table name. 791 * @return The table name used in table lock. 792 */ 793 public static TableName getTableLockName(TableName tn) { 794 byte[] tableName = tn.getName(); 795 return TableName.valueOf(Bytes.add(tableName, MobConstants.MOB_TABLE_LOCK_SUFFIX)); 796 } 797 798 /** 799 * Performs the mob compaction. 800 * @param conf the Configuration 801 * @param fs the file system 802 * @param tableName the table the compact 803 * @param hcd the column descriptor 804 * @param pool the thread pool 805 * @param allFiles Whether add all mob files into the compaction. 806 */ 807 public static void doMobCompaction(Configuration conf, FileSystem fs, TableName tableName, 808 ColumnFamilyDescriptor hcd, ExecutorService pool, boolean allFiles, LockManager.MasterLock lock) 809 throws IOException { 810 String className = conf.get(MobConstants.MOB_COMPACTOR_CLASS_KEY, 811 PartitionedMobCompactor.class.getName()); 812 // instantiate the mob compactor. 813 MobCompactor compactor = null; 814 try { 815 compactor = ReflectionUtils.instantiateWithCustomCtor(className, new Class[] { 816 Configuration.class, FileSystem.class, TableName.class, ColumnFamilyDescriptor.class, 817 ExecutorService.class }, new Object[] { conf, fs, tableName, hcd, pool }); 818 } catch (Exception e) { 819 throw new IOException("Unable to load configured mob file compactor '" + className + "'", e); 820 } 821 // compact only for mob-enabled column. 822 // obtain a write table lock before performing compaction to avoid race condition 823 // with major compaction in mob-enabled column. 824 try { 825 lock.acquire(); 826 compactor.compact(allFiles); 827 } catch (Exception e) { 828 LOG.error("Failed to compact the mob files for the column " + hcd.getNameAsString() 829 + " in the table " + tableName.getNameAsString(), e); 830 } finally { 831 lock.release(); 832 } 833 } 834 835 /** 836 * Creates a thread pool. 837 * @param conf the Configuration 838 * @return A thread pool. 839 */ 840 public static ExecutorService createMobCompactorThreadPool(Configuration conf) { 841 int maxThreads = conf.getInt(MobConstants.MOB_COMPACTION_THREADS_MAX, 842 MobConstants.DEFAULT_MOB_COMPACTION_THREADS_MAX); 843 if (maxThreads == 0) { 844 maxThreads = 1; 845 } 846 final SynchronousQueue<Runnable> queue = new SynchronousQueue<>(); 847 ThreadPoolExecutor pool = new ThreadPoolExecutor(1, maxThreads, 60, TimeUnit.SECONDS, queue, 848 Threads.newDaemonThreadFactory("MobCompactor"), new RejectedExecutionHandler() { 849 @Override 850 public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { 851 try { 852 // waiting for a thread to pick up instead of throwing exceptions. 853 queue.put(r); 854 } catch (InterruptedException e) { 855 throw new RejectedExecutionException(e); 856 } 857 } 858 }); 859 ((ThreadPoolExecutor) pool).allowCoreThreadTimeOut(true); 860 return pool; 861 } 862 863 /** 864 * Checks whether this table has mob-enabled columns. 865 * @param htd The current table descriptor. 866 * @return Whether this table has mob-enabled columns. 867 */ 868 public static boolean hasMobColumns(TableDescriptor htd) { 869 ColumnFamilyDescriptor[] hcds = htd.getColumnFamilies(); 870 for (ColumnFamilyDescriptor hcd : hcds) { 871 if (hcd.isMobEnabled()) { 872 return true; 873 } 874 } 875 return false; 876 } 877 878 /** 879 * Indicates whether return null value when the mob file is missing or corrupt. 880 * The information is set in the attribute "empty.value.on.mobcell.miss" of scan. 881 * @param scan The current scan. 882 * @return True if the readEmptyValueOnMobCellMiss is enabled. 883 */ 884 public static boolean isReadEmptyValueOnMobCellMiss(Scan scan) { 885 byte[] readEmptyValueOnMobCellMiss = 886 scan.getAttribute(MobConstants.EMPTY_VALUE_ON_MOBCELL_MISS); 887 try { 888 return readEmptyValueOnMobCellMiss != null && Bytes.toBoolean(readEmptyValueOnMobCellMiss); 889 } catch (IllegalArgumentException e) { 890 return false; 891 } 892 } 893 894 /** 895 * Creates a mob ref delete marker. 896 * @param cell The current delete marker. 897 * @return A delete marker with the ref tag. 898 */ 899 public static Cell createMobRefDeleteMarker(Cell cell) { 900 return PrivateCellUtil.createCell(cell, TagUtil.concatTags(REF_DELETE_MARKER_TAG_BYTES, cell)); 901 } 902 903 /** 904 * Checks if the mob file is expired. 905 * @param column The descriptor of the current column family. 906 * @param current The current time. 907 * @param fileDate The date string parsed from the mob file name. 908 * @return True if the mob file is expired. 909 */ 910 public static boolean isMobFileExpired(ColumnFamilyDescriptor column, long current, String fileDate) { 911 if (column.getMinVersions() > 0) { 912 return false; 913 } 914 long timeToLive = column.getTimeToLive(); 915 if (Integer.MAX_VALUE == timeToLive) { 916 return false; 917 } 918 919 Date expireDate = new Date(current - timeToLive * 1000); 920 expireDate = new Date(expireDate.getYear(), expireDate.getMonth(), expireDate.getDate()); 921 try { 922 Date date = parseDate(fileDate); 923 if (date.getTime() < expireDate.getTime()) { 924 return true; 925 } 926 } catch (ParseException e) { 927 LOG.warn("Failed to parse the date " + fileDate, e); 928 return false; 929 } 930 return false; 931 } 932 933 /** 934 * fill out partition id based on compaction policy and date, threshold... 935 * @param id Partition id to be filled out 936 * @param firstDayOfCurrentMonth The first day in the current month 937 * @param firstDayOfCurrentWeek The first day in the current week 938 * @param dateStr Date string from the mob file 939 * @param policy Mob compaction policy 940 * @param calendar Calendar object 941 * @param threshold Mob compaciton threshold configured 942 * @return true if the file needs to be excluded from compaction 943 */ 944 public static boolean fillPartitionId(final CompactionPartitionId id, 945 final Date firstDayOfCurrentMonth, final Date firstDayOfCurrentWeek, final String dateStr, 946 final MobCompactPartitionPolicy policy, final Calendar calendar, final long threshold) { 947 948 boolean skipCompcation = false; 949 id.setThreshold(threshold); 950 if (threshold <= 0) { 951 id.setDate(dateStr); 952 return skipCompcation; 953 } 954 955 long finalThreshold; 956 Date date; 957 try { 958 date = MobUtils.parseDate(dateStr); 959 } catch (ParseException e) { 960 LOG.warn("Failed to parse date " + dateStr, e); 961 id.setDate(dateStr); 962 return true; 963 } 964 965 /* The algorithm works as follows: 966 * For monthly policy: 967 * 1). If the file's date is in past months, apply 4 * 7 * threshold 968 * 2). If the file's date is in past weeks, apply 7 * threshold 969 * 3). If the file's date is in current week, exclude it from the compaction 970 * For weekly policy: 971 * 1). If the file's date is in past weeks, apply 7 * threshold 972 * 2). If the file's date in currently, apply threshold 973 * For daily policy: 974 * 1). apply threshold 975 */ 976 if (policy == MobCompactPartitionPolicy.MONTHLY) { 977 if (date.before(firstDayOfCurrentMonth)) { 978 // Check overflow 979 if (threshold < (Long.MAX_VALUE / MONTHLY_THRESHOLD_MULTIPLIER)) { 980 finalThreshold = MONTHLY_THRESHOLD_MULTIPLIER * threshold; 981 } else { 982 finalThreshold = Long.MAX_VALUE; 983 } 984 id.setThreshold(finalThreshold); 985 986 // set to the date for the first day of that month 987 id.setDate(MobUtils.formatDate(MobUtils.getFirstDayOfMonth(calendar, date))); 988 return skipCompcation; 989 } 990 } 991 992 if ((policy == MobCompactPartitionPolicy.MONTHLY) || 993 (policy == MobCompactPartitionPolicy.WEEKLY)) { 994 // Check if it needs to apply weekly multiplier 995 if (date.before(firstDayOfCurrentWeek)) { 996 // Check overflow 997 if (threshold < (Long.MAX_VALUE / WEEKLY_THRESHOLD_MULTIPLIER)) { 998 finalThreshold = WEEKLY_THRESHOLD_MULTIPLIER * threshold; 999 } else { 1000 finalThreshold = Long.MAX_VALUE; 1001 } 1002 id.setThreshold(finalThreshold); 1003 1004 id.setDate(MobUtils.formatDate(MobUtils.getFirstDayOfWeek(calendar, date))); 1005 return skipCompcation; 1006 } else if (policy == MobCompactPartitionPolicy.MONTHLY) { 1007 skipCompcation = true; 1008 } 1009 } 1010 1011 // Rest is daily 1012 id.setDate(dateStr); 1013 return skipCompcation; 1014 } 1015}