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 */ 019 020package org.apache.hadoop.hbase.regionserver; 021 022import java.io.FileNotFoundException; 023import java.io.IOException; 024import java.io.InterruptedIOException; 025import java.util.ArrayList; 026import java.util.Collection; 027import java.util.List; 028import java.util.Optional; 029import java.util.UUID; 030import org.apache.hadoop.conf.Configuration; 031import org.apache.hadoop.fs.FSDataInputStream; 032import org.apache.hadoop.fs.FSDataOutputStream; 033import org.apache.hadoop.fs.FileStatus; 034import org.apache.hadoop.fs.FileSystem; 035import org.apache.hadoop.fs.FileUtil; 036import org.apache.hadoop.fs.LocatedFileStatus; 037import org.apache.hadoop.fs.Path; 038import org.apache.hadoop.fs.permission.FsPermission; 039import org.apache.hadoop.hbase.Cell; 040import org.apache.hadoop.hbase.HConstants; 041import org.apache.hadoop.hbase.PrivateCellUtil; 042import org.apache.hadoop.hbase.backup.HFileArchiver; 043import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 044import org.apache.hadoop.hbase.client.RegionInfo; 045import org.apache.hadoop.hbase.client.TableDescriptor; 046import org.apache.hadoop.hbase.fs.HFileSystem; 047import org.apache.hadoop.hbase.io.Reference; 048import org.apache.hadoop.hbase.util.Bytes; 049import org.apache.hadoop.hbase.util.FSHDFSUtils; 050import org.apache.hadoop.hbase.util.FSUtils; 051import org.apache.hadoop.hbase.util.Pair; 052import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil; 053import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; 054import org.apache.yetus.audience.InterfaceAudience; 055import org.slf4j.Logger; 056import org.slf4j.LoggerFactory; 057import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 058 059import edu.umd.cs.findbugs.annotations.Nullable; 060 061/** 062 * View to an on-disk Region. 063 * Provides the set of methods necessary to interact with the on-disk region data. 064 */ 065@InterfaceAudience.Private 066public class HRegionFileSystem { 067 private static final Logger LOG = LoggerFactory.getLogger(HRegionFileSystem.class); 068 069 /** Name of the region info file that resides just under the region directory. */ 070 public final static String REGION_INFO_FILE = ".regioninfo"; 071 072 /** Temporary subdirectory of the region directory used for merges. */ 073 public static final String REGION_MERGES_DIR = ".merges"; 074 075 /** Temporary subdirectory of the region directory used for splits. */ 076 public static final String REGION_SPLITS_DIR = ".splits"; 077 078 /** Temporary subdirectory of the region directory used for compaction output. */ 079 @VisibleForTesting static final String REGION_TEMP_DIR = ".tmp"; 080 081 private final RegionInfo regionInfo; 082 //regionInfo for interacting with FS (getting encodedName, etc) 083 private final RegionInfo regionInfoForFs; 084 private final Configuration conf; 085 private final Path tableDir; 086 private final FileSystem fs; 087 088 /** 089 * In order to handle NN connectivity hiccups, one need to retry non-idempotent operation at the 090 * client level. 091 */ 092 private final int hdfsClientRetriesNumber; 093 private final int baseSleepBeforeRetries; 094 private static final int DEFAULT_HDFS_CLIENT_RETRIES_NUMBER = 10; 095 private static final int DEFAULT_BASE_SLEEP_BEFORE_RETRIES = 1000; 096 097 /** 098 * Create a view to the on-disk region 099 * @param conf the {@link Configuration} to use 100 * @param fs {@link FileSystem} that contains the region 101 * @param tableDir {@link Path} to where the table is being stored 102 * @param regionInfo {@link RegionInfo} for region 103 */ 104 HRegionFileSystem(final Configuration conf, final FileSystem fs, final Path tableDir, 105 final RegionInfo regionInfo) { 106 this.fs = fs; 107 this.conf = conf; 108 this.tableDir = tableDir; 109 this.regionInfo = regionInfo; 110 this.regionInfoForFs = ServerRegionReplicaUtil.getRegionInfoForFs(regionInfo); 111 this.hdfsClientRetriesNumber = conf.getInt("hdfs.client.retries.number", 112 DEFAULT_HDFS_CLIENT_RETRIES_NUMBER); 113 this.baseSleepBeforeRetries = conf.getInt("hdfs.client.sleep.before.retries", 114 DEFAULT_BASE_SLEEP_BEFORE_RETRIES); 115 } 116 117 /** @return the underlying {@link FileSystem} */ 118 public FileSystem getFileSystem() { 119 return this.fs; 120 } 121 122 /** @return the {@link RegionInfo} that describe this on-disk region view */ 123 public RegionInfo getRegionInfo() { 124 return this.regionInfo; 125 } 126 127 public RegionInfo getRegionInfoForFS() { 128 return this.regionInfoForFs; 129 } 130 131 /** @return {@link Path} to the region's root directory. */ 132 public Path getTableDir() { 133 return this.tableDir; 134 } 135 136 /** @return {@link Path} to the region directory. */ 137 public Path getRegionDir() { 138 return new Path(this.tableDir, this.regionInfoForFs.getEncodedName()); 139 } 140 141 // =========================================================================== 142 // Temp Helpers 143 // =========================================================================== 144 /** @return {@link Path} to the region's temp directory, used for file creations */ 145 Path getTempDir() { 146 return new Path(getRegionDir(), REGION_TEMP_DIR); 147 } 148 149 /** 150 * Clean up any temp detritus that may have been left around from previous operation attempts. 151 */ 152 void cleanupTempDir() throws IOException { 153 deleteDir(getTempDir()); 154 } 155 156 // =========================================================================== 157 // Store/StoreFile Helpers 158 // =========================================================================== 159 /** 160 * Returns the directory path of the specified family 161 * @param familyName Column Family Name 162 * @return {@link Path} to the directory of the specified family 163 */ 164 public Path getStoreDir(final String familyName) { 165 return new Path(this.getRegionDir(), familyName); 166 } 167 168 /** 169 * Create the store directory for the specified family name 170 * @param familyName Column Family Name 171 * @return {@link Path} to the directory of the specified family 172 * @throws IOException if the directory creation fails. 173 */ 174 Path createStoreDir(final String familyName) throws IOException { 175 Path storeDir = getStoreDir(familyName); 176 if(!fs.exists(storeDir) && !createDir(storeDir)) 177 throw new IOException("Failed creating "+storeDir); 178 return storeDir; 179 } 180 181 /** 182 * Set the directory of CF to the specified storage policy. <br> 183 * <i>"LAZY_PERSIST"</i>, <i>"ALL_SSD"</i>, <i>"ONE_SSD"</i>, <i>"HOT"</i>, <i>"WARM"</i>, 184 * <i>"COLD"</i> <br> 185 * <br> 186 * See {@link org.apache.hadoop.hdfs.protocol.HdfsConstants} for more details. 187 * @param familyName The name of column family. 188 * @param policyName The name of the storage policy: 'HOT', 'COLD', etc. 189 * See see hadoop 2.6+ org.apache.hadoop.hdfs.protocol.HdfsConstants for possible list e.g 190 * 'COLD', 'WARM', 'HOT', 'ONE_SSD', 'ALL_SSD', 'LAZY_PERSIST'. 191 */ 192 public void setStoragePolicy(String familyName, String policyName) { 193 FSUtils.setStoragePolicy(this.fs, getStoreDir(familyName), policyName); 194 } 195 196 /** 197 * Get the storage policy of the directory of CF. 198 * @param familyName The name of column family. 199 * @return Storage policy name, or {@code null} if not using {@link HFileSystem} or exception 200 * thrown when trying to get policy 201 */ 202 @Nullable 203 public String getStoragePolicyName(String familyName) { 204 if (this.fs instanceof HFileSystem) { 205 Path storeDir = getStoreDir(familyName); 206 return ((HFileSystem) this.fs).getStoragePolicyName(storeDir); 207 } 208 209 return null; 210 } 211 212 /** 213 * Returns the store files available for the family. 214 * This methods performs the filtering based on the valid store files. 215 * @param familyName Column Family Name 216 * @return a set of {@link StoreFileInfo} for the specified family. 217 */ 218 public Collection<StoreFileInfo> getStoreFiles(final byte[] familyName) throws IOException { 219 return getStoreFiles(Bytes.toString(familyName)); 220 } 221 222 public Collection<StoreFileInfo> getStoreFiles(final String familyName) throws IOException { 223 return getStoreFiles(familyName, true); 224 } 225 226 /** 227 * Returns the store files available for the family. 228 * This methods performs the filtering based on the valid store files. 229 * @param familyName Column Family Name 230 * @return a set of {@link StoreFileInfo} for the specified family. 231 */ 232 public Collection<StoreFileInfo> getStoreFiles(final String familyName, final boolean validate) 233 throws IOException { 234 Path familyDir = getStoreDir(familyName); 235 FileStatus[] files = FSUtils.listStatus(this.fs, familyDir); 236 if (files == null) { 237 if (LOG.isTraceEnabled()) { 238 LOG.trace("No StoreFiles for: " + familyDir); 239 } 240 return null; 241 } 242 243 ArrayList<StoreFileInfo> storeFiles = new ArrayList<>(files.length); 244 for (FileStatus status: files) { 245 if (validate && !StoreFileInfo.isValid(status)) { 246 LOG.warn("Invalid StoreFile: " + status.getPath()); 247 continue; 248 } 249 StoreFileInfo info = ServerRegionReplicaUtil.getStoreFileInfo(conf, fs, regionInfo, 250 regionInfoForFs, familyName, status.getPath()); 251 storeFiles.add(info); 252 253 } 254 return storeFiles; 255 } 256 257 /** 258 * Returns the store files' LocatedFileStatus which available for the family. 259 * This methods performs the filtering based on the valid store files. 260 * @param familyName Column Family Name 261 * @return a list of store files' LocatedFileStatus for the specified family. 262 */ 263 public static List<LocatedFileStatus> getStoreFilesLocatedStatus( 264 final HRegionFileSystem regionfs, final String familyName, 265 final boolean validate) throws IOException { 266 Path familyDir = regionfs.getStoreDir(familyName); 267 List<LocatedFileStatus> locatedFileStatuses = FSUtils.listLocatedStatus( 268 regionfs.getFileSystem(), familyDir); 269 if (locatedFileStatuses == null) { 270 if (LOG.isTraceEnabled()) { 271 LOG.trace("No StoreFiles for: " + familyDir); 272 } 273 return null; 274 } 275 276 List<LocatedFileStatus> validStoreFiles = Lists.newArrayList(); 277 for (LocatedFileStatus status : locatedFileStatuses) { 278 if (validate && !StoreFileInfo.isValid(status)) { 279 LOG.warn("Invalid StoreFile: " + status.getPath()); 280 } else { 281 validStoreFiles.add(status); 282 } 283 } 284 return validStoreFiles; 285 } 286 287 /** 288 * Return Qualified Path of the specified family/file 289 * 290 * @param familyName Column Family Name 291 * @param fileName File Name 292 * @return The qualified Path for the specified family/file 293 */ 294 Path getStoreFilePath(final String familyName, final String fileName) { 295 Path familyDir = getStoreDir(familyName); 296 return new Path(familyDir, fileName).makeQualified(fs.getUri(), fs.getWorkingDirectory()); 297 } 298 299 /** 300 * Return the store file information of the specified family/file. 301 * 302 * @param familyName Column Family Name 303 * @param fileName File Name 304 * @return The {@link StoreFileInfo} for the specified family/file 305 */ 306 StoreFileInfo getStoreFileInfo(final String familyName, final String fileName) 307 throws IOException { 308 Path familyDir = getStoreDir(familyName); 309 return ServerRegionReplicaUtil.getStoreFileInfo(conf, fs, regionInfo, 310 regionInfoForFs, familyName, new Path(familyDir, fileName)); 311 } 312 313 /** 314 * Returns true if the specified family has reference files 315 * @param familyName Column Family Name 316 * @return true if family contains reference files 317 * @throws IOException 318 */ 319 public boolean hasReferences(final String familyName) throws IOException { 320 Path storeDir = getStoreDir(familyName); 321 FileStatus[] files = FSUtils.listStatus(fs, storeDir); 322 if (files != null) { 323 for(FileStatus stat: files) { 324 if(stat.isDirectory()) { 325 continue; 326 } 327 if(StoreFileInfo.isReference(stat.getPath())) { 328 if (LOG.isTraceEnabled()) LOG.trace("Reference " + stat.getPath()); 329 return true; 330 } 331 } 332 } 333 return false; 334 } 335 336 /** 337 * Check whether region has Reference file 338 * @param htd table desciptor of the region 339 * @return true if region has reference file 340 * @throws IOException 341 */ 342 public boolean hasReferences(final TableDescriptor htd) throws IOException { 343 for (ColumnFamilyDescriptor family : htd.getColumnFamilies()) { 344 if (hasReferences(family.getNameAsString())) { 345 return true; 346 } 347 } 348 return false; 349 } 350 351 /** 352 * @return the set of families present on disk 353 * @throws IOException 354 */ 355 public Collection<String> getFamilies() throws IOException { 356 FileStatus[] fds = FSUtils.listStatus(fs, getRegionDir(), new FSUtils.FamilyDirFilter(fs)); 357 if (fds == null) return null; 358 359 ArrayList<String> families = new ArrayList<>(fds.length); 360 for (FileStatus status: fds) { 361 families.add(status.getPath().getName()); 362 } 363 364 return families; 365 } 366 367 /** 368 * Remove the region family from disk, archiving the store files. 369 * @param familyName Column Family Name 370 * @throws IOException if an error occours during the archiving 371 */ 372 public void deleteFamily(final String familyName) throws IOException { 373 // archive family store files 374 HFileArchiver.archiveFamily(fs, conf, regionInfoForFs, tableDir, Bytes.toBytes(familyName)); 375 376 // delete the family folder 377 Path familyDir = getStoreDir(familyName); 378 if(fs.exists(familyDir) && !deleteDir(familyDir)) 379 throw new IOException("Could not delete family " + familyName 380 + " from FileSystem for region " + regionInfoForFs.getRegionNameAsString() + "(" 381 + regionInfoForFs.getEncodedName() + ")"); 382 } 383 384 /** 385 * Generate a unique file name, used by createTempName() and commitStoreFile() 386 * @param suffix extra information to append to the generated name 387 * @return Unique file name 388 */ 389 private static String generateUniqueName(final String suffix) { 390 String name = UUID.randomUUID().toString().replaceAll("-", ""); 391 if (suffix != null) name += suffix; 392 return name; 393 } 394 395 /** 396 * Generate a unique temporary Path. Used in conjuction with commitStoreFile() 397 * to get a safer file creation. 398 * <code> 399 * Path file = fs.createTempName(); 400 * ...StoreFile.Writer(file)... 401 * fs.commitStoreFile("family", file); 402 * </code> 403 * 404 * @return Unique {@link Path} of the temporary file 405 */ 406 public Path createTempName() { 407 return createTempName(null); 408 } 409 410 /** 411 * Generate a unique temporary Path. Used in conjuction with commitStoreFile() 412 * to get a safer file creation. 413 * <code> 414 * Path file = fs.createTempName(); 415 * ...StoreFile.Writer(file)... 416 * fs.commitStoreFile("family", file); 417 * </code> 418 * 419 * @param suffix extra information to append to the generated name 420 * @return Unique {@link Path} of the temporary file 421 */ 422 public Path createTempName(final String suffix) { 423 return new Path(getTempDir(), generateUniqueName(suffix)); 424 } 425 426 /** 427 * Move the file from a build/temp location to the main family store directory. 428 * @param familyName Family that will gain the file 429 * @param buildPath {@link Path} to the file to commit. 430 * @return The new {@link Path} of the committed file 431 * @throws IOException 432 */ 433 public Path commitStoreFile(final String familyName, final Path buildPath) throws IOException { 434 Path dstPath = preCommitStoreFile(familyName, buildPath, -1, false); 435 return commitStoreFile(buildPath, dstPath); 436 } 437 438 /** 439 * Generate the filename in the main family store directory for moving the file from a build/temp 440 * location. 441 * @param familyName Family that will gain the file 442 * @param buildPath {@link Path} to the file to commit. 443 * @param seqNum Sequence Number to append to the file name (less then 0 if no sequence number) 444 * @param generateNewName False if you want to keep the buildPath name 445 * @return The new {@link Path} of the to be committed file 446 * @throws IOException 447 */ 448 private Path preCommitStoreFile(final String familyName, final Path buildPath, 449 final long seqNum, final boolean generateNewName) throws IOException { 450 Path storeDir = getStoreDir(familyName); 451 if(!fs.exists(storeDir) && !createDir(storeDir)) 452 throw new IOException("Failed creating " + storeDir); 453 454 String name = buildPath.getName(); 455 if (generateNewName) { 456 name = generateUniqueName((seqNum < 0) ? null : "_SeqId_" + seqNum + "_"); 457 } 458 Path dstPath = new Path(storeDir, name); 459 if (!fs.exists(buildPath)) { 460 throw new FileNotFoundException(buildPath.toString()); 461 } 462 if (LOG.isDebugEnabled()) { 463 LOG.debug("Committing " + buildPath + " as " + dstPath); 464 } 465 return dstPath; 466 } 467 468 /* 469 * Moves file from staging dir to region dir 470 * @param buildPath {@link Path} to the file to commit. 471 * @param dstPath {@link Path} to the file under region dir 472 * @return The {@link Path} of the committed file 473 * @throws IOException 474 */ 475 Path commitStoreFile(final Path buildPath, Path dstPath) throws IOException { 476 // buildPath exists, therefore not doing an exists() check. 477 if (!rename(buildPath, dstPath)) { 478 throw new IOException("Failed rename of " + buildPath + " to " + dstPath); 479 } 480 return dstPath; 481 } 482 483 /** 484 * Archives the specified store file from the specified family. 485 * @param familyName Family that contains the store files 486 * @param filePath {@link Path} to the store file to remove 487 * @throws IOException if the archiving fails 488 */ 489 public void removeStoreFile(final String familyName, final Path filePath) 490 throws IOException { 491 HFileArchiver.archiveStoreFile(this.conf, this.fs, this.regionInfoForFs, 492 this.tableDir, Bytes.toBytes(familyName), filePath); 493 } 494 495 /** 496 * Closes and archives the specified store files from the specified family. 497 * @param familyName Family that contains the store files 498 * @param storeFiles set of store files to remove 499 * @throws IOException if the archiving fails 500 */ 501 public void removeStoreFiles(String familyName, Collection<HStoreFile> storeFiles) 502 throws IOException { 503 HFileArchiver.archiveStoreFiles(this.conf, this.fs, this.regionInfoForFs, 504 this.tableDir, Bytes.toBytes(familyName), storeFiles); 505 } 506 507 /** 508 * Bulk load: Add a specified store file to the specified family. 509 * If the source file is on the same different file-system is moved from the 510 * source location to the destination location, otherwise is copied over. 511 * 512 * @param familyName Family that will gain the file 513 * @param srcPath {@link Path} to the file to import 514 * @param seqNum Bulk Load sequence number 515 * @return The destination {@link Path} of the bulk loaded file 516 * @throws IOException 517 */ 518 Pair<Path, Path> bulkLoadStoreFile(final String familyName, Path srcPath, long seqNum) 519 throws IOException { 520 // Copy the file if it's on another filesystem 521 FileSystem srcFs = srcPath.getFileSystem(conf); 522 srcPath = srcFs.resolvePath(srcPath); 523 FileSystem realSrcFs = srcPath.getFileSystem(conf); 524 FileSystem desFs = fs instanceof HFileSystem ? ((HFileSystem)fs).getBackingFs() : fs; 525 526 // We can't compare FileSystem instances as equals() includes UGI instance 527 // as part of the comparison and won't work when doing SecureBulkLoad 528 // TODO deal with viewFS 529 if (!FSHDFSUtils.isSameHdfs(conf, realSrcFs, desFs)) { 530 LOG.info("Bulk-load file " + srcPath + " is on different filesystem than " + 531 "the destination store. Copying file over to destination filesystem."); 532 Path tmpPath = createTempName(); 533 FileUtil.copy(realSrcFs, srcPath, fs, tmpPath, false, conf); 534 LOG.info("Copied " + srcPath + " to temporary path on destination filesystem: " + tmpPath); 535 srcPath = tmpPath; 536 } 537 538 return new Pair<>(srcPath, preCommitStoreFile(familyName, srcPath, seqNum, true)); 539 } 540 541 // =========================================================================== 542 // Splits Helpers 543 // =========================================================================== 544 /** @return {@link Path} to the temp directory used during split operations */ 545 Path getSplitsDir() { 546 return new Path(getRegionDir(), REGION_SPLITS_DIR); 547 } 548 549 public Path getSplitsDir(final RegionInfo hri) { 550 return new Path(getSplitsDir(), hri.getEncodedName()); 551 } 552 553 /** 554 * Clean up any split detritus that may have been left around from previous split attempts. 555 */ 556 void cleanupSplitsDir() throws IOException { 557 deleteDir(getSplitsDir()); 558 } 559 560 /** 561 * Clean up any split detritus that may have been left around from previous 562 * split attempts. 563 * Call this method on initial region deploy. 564 * @throws IOException 565 */ 566 void cleanupAnySplitDetritus() throws IOException { 567 Path splitdir = this.getSplitsDir(); 568 if (!fs.exists(splitdir)) return; 569 // Look at the splitdir. It could have the encoded names of the daughter 570 // regions we tried to make. See if the daughter regions actually got made 571 // out under the tabledir. If here under splitdir still, then the split did 572 // not complete. Try and do cleanup. This code WILL NOT catch the case 573 // where we successfully created daughter a but regionserver crashed during 574 // the creation of region b. In this case, there'll be an orphan daughter 575 // dir in the filesystem. TOOD: Fix. 576 FileStatus[] daughters = FSUtils.listStatus(fs, splitdir, new FSUtils.DirFilter(fs)); 577 if (daughters != null) { 578 for (FileStatus daughter: daughters) { 579 Path daughterDir = new Path(getTableDir(), daughter.getPath().getName()); 580 if (fs.exists(daughterDir) && !deleteDir(daughterDir)) { 581 throw new IOException("Failed delete of " + daughterDir); 582 } 583 } 584 } 585 cleanupSplitsDir(); 586 LOG.info("Cleaned up old failed split transaction detritus: " + splitdir); 587 } 588 589 /** 590 * Remove daughter region 591 * @param regionInfo daughter {@link RegionInfo} 592 * @throws IOException 593 */ 594 void cleanupDaughterRegion(final RegionInfo regionInfo) throws IOException { 595 Path regionDir = new Path(this.tableDir, regionInfo.getEncodedName()); 596 if (this.fs.exists(regionDir) && !deleteDir(regionDir)) { 597 throw new IOException("Failed delete of " + regionDir); 598 } 599 } 600 601 /** 602 * Commit a daughter region, moving it from the split temporary directory 603 * to the proper location in the filesystem. 604 * 605 * @param regionInfo daughter {@link org.apache.hadoop.hbase.client.RegionInfo} 606 * @throws IOException 607 */ 608 public Path commitDaughterRegion(final RegionInfo regionInfo) 609 throws IOException { 610 Path regionDir = new Path(this.tableDir, regionInfo.getEncodedName()); 611 Path daughterTmpDir = this.getSplitsDir(regionInfo); 612 613 if (fs.exists(daughterTmpDir)) { 614 615 // Write HRI to a file in case we need to recover hbase:meta 616 Path regionInfoFile = new Path(daughterTmpDir, REGION_INFO_FILE); 617 byte[] regionInfoContent = getRegionInfoFileContent(regionInfo); 618 writeRegionInfoFileContent(conf, fs, regionInfoFile, regionInfoContent); 619 620 // Move the daughter temp dir to the table dir 621 if (!rename(daughterTmpDir, regionDir)) { 622 throw new IOException("Unable to rename " + daughterTmpDir + " to " + regionDir); 623 } 624 } 625 626 return regionDir; 627 } 628 629 /** 630 * Create the region splits directory. 631 */ 632 public void createSplitsDir() throws IOException { 633 Path splitdir = getSplitsDir(); 634 if (fs.exists(splitdir)) { 635 LOG.info("The " + splitdir + " directory exists. Hence deleting it to recreate it"); 636 if (!deleteDir(splitdir)) { 637 throw new IOException("Failed deletion of " + splitdir 638 + " before creating them again."); 639 } 640 } 641 // splitDir doesn't exists now. No need to do an exists() call for it. 642 if (!createDir(splitdir)) { 643 throw new IOException("Failed create of " + splitdir); 644 } 645 } 646 647 /** 648 * Write out a split reference. Package local so it doesnt leak out of 649 * regionserver. 650 * @param hri {@link RegionInfo} of the destination 651 * @param familyName Column Family Name 652 * @param f File to split. 653 * @param splitRow Split Row 654 * @param top True if we are referring to the top half of the hfile. 655 * @param splitPolicy A split policy instance; be careful! May not be full populated; e.g. if 656 * this method is invoked on the Master side, then the RegionSplitPolicy will 657 * NOT have a reference to a Region. 658 * @return Path to created reference. 659 * @throws IOException 660 */ 661 public Path splitStoreFile(RegionInfo hri, String familyName, HStoreFile f, byte[] splitRow, 662 boolean top, RegionSplitPolicy splitPolicy) throws IOException { 663 if (splitPolicy == null || !splitPolicy.skipStoreFileRangeCheck(familyName)) { 664 // Check whether the split row lies in the range of the store file 665 // If it is outside the range, return directly. 666 f.initReader(); 667 try { 668 if (top) { 669 //check if larger than last key. 670 Cell splitKey = PrivateCellUtil.createFirstOnRow(splitRow); 671 Optional<Cell> lastKey = f.getLastKey(); 672 // If lastKey is null means storefile is empty. 673 if (!lastKey.isPresent()) { 674 return null; 675 } 676 if (f.getComparator().compare(splitKey, lastKey.get()) > 0) { 677 return null; 678 } 679 } else { 680 //check if smaller than first key 681 Cell splitKey = PrivateCellUtil.createLastOnRow(splitRow); 682 Optional<Cell> firstKey = f.getFirstKey(); 683 // If firstKey is null means storefile is empty. 684 if (!firstKey.isPresent()) { 685 return null; 686 } 687 if (f.getComparator().compare(splitKey, firstKey.get()) < 0) { 688 return null; 689 } 690 } 691 } finally { 692 f.closeStoreFile(f.getCacheConf() != null ? f.getCacheConf().shouldEvictOnClose() : true); 693 } 694 } 695 696 Path splitDir = new Path(getSplitsDir(hri), familyName); 697 // A reference to the bottom half of the hsf store file. 698 Reference r = 699 top ? Reference.createTopReference(splitRow): Reference.createBottomReference(splitRow); 700 // Add the referred-to regions name as a dot separated suffix. 701 // See REF_NAME_REGEX regex above. The referred-to regions name is 702 // up in the path of the passed in <code>f</code> -- parentdir is family, 703 // then the directory above is the region name. 704 String parentRegionName = regionInfoForFs.getEncodedName(); 705 // Write reference with same file id only with the other region name as 706 // suffix and into the new region location (under same family). 707 Path p = new Path(splitDir, f.getPath().getName() + "." + parentRegionName); 708 return r.write(fs, p); 709 } 710 711 // =========================================================================== 712 // Merge Helpers 713 // =========================================================================== 714 /** @return {@link Path} to the temp directory used during merge operations */ 715 public Path getMergesDir() { 716 return new Path(getRegionDir(), REGION_MERGES_DIR); 717 } 718 719 Path getMergesDir(final RegionInfo hri) { 720 return new Path(getMergesDir(), hri.getEncodedName()); 721 } 722 723 /** 724 * Clean up any merge detritus that may have been left around from previous merge attempts. 725 */ 726 void cleanupMergesDir() throws IOException { 727 deleteDir(getMergesDir()); 728 } 729 730 /** 731 * Remove merged region 732 * @param mergedRegion {@link RegionInfo} 733 * @throws IOException 734 */ 735 public void cleanupMergedRegion(final RegionInfo mergedRegion) throws IOException { 736 Path regionDir = new Path(this.tableDir, mergedRegion.getEncodedName()); 737 if (this.fs.exists(regionDir) && !this.fs.delete(regionDir, true)) { 738 throw new IOException("Failed delete of " + regionDir); 739 } 740 } 741 742 static boolean mkdirs(FileSystem fs, Configuration conf, Path dir) throws IOException { 743 if (FSUtils.isDistributedFileSystem(fs) || 744 !conf.getBoolean(HConstants.ENABLE_DATA_FILE_UMASK, false)) { 745 return fs.mkdirs(dir); 746 } 747 FsPermission perms = FSUtils.getFilePermissions(fs, conf, HConstants.DATA_FILE_UMASK_KEY); 748 return fs.mkdirs(dir, perms); 749 } 750 751 /** 752 * Create the region merges directory. 753 * @throws IOException If merges dir already exists or we fail to create it. 754 * @see HRegionFileSystem#cleanupMergesDir() 755 */ 756 public void createMergesDir() throws IOException { 757 Path mergesdir = getMergesDir(); 758 if (fs.exists(mergesdir)) { 759 LOG.info("The " + mergesdir 760 + " directory exists. Hence deleting it to recreate it"); 761 if (!fs.delete(mergesdir, true)) { 762 throw new IOException("Failed deletion of " + mergesdir 763 + " before creating them again."); 764 } 765 } 766 if (!mkdirs(fs, conf, mergesdir)) 767 throw new IOException("Failed create of " + mergesdir); 768 } 769 770 /** 771 * Write out a merge reference under the given merges directory. Package local 772 * so it doesnt leak out of regionserver. 773 * @param mergedRegion {@link RegionInfo} of the merged region 774 * @param familyName Column Family Name 775 * @param f File to create reference. 776 * @param mergedDir 777 * @return Path to created reference. 778 * @throws IOException 779 */ 780 public Path mergeStoreFile(RegionInfo mergedRegion, String familyName, HStoreFile f, 781 Path mergedDir) throws IOException { 782 Path referenceDir = new Path(new Path(mergedDir, 783 mergedRegion.getEncodedName()), familyName); 784 // A whole reference to the store file. 785 Reference r = Reference.createTopReference(regionInfoForFs.getStartKey()); 786 // Add the referred-to regions name as a dot separated suffix. 787 // See REF_NAME_REGEX regex above. The referred-to regions name is 788 // up in the path of the passed in <code>f</code> -- parentdir is family, 789 // then the directory above is the region name. 790 String mergingRegionName = regionInfoForFs.getEncodedName(); 791 // Write reference with same file id only with the other region name as 792 // suffix and into the new region location (under same family). 793 Path p = new Path(referenceDir, f.getPath().getName() + "." 794 + mergingRegionName); 795 return r.write(fs, p); 796 } 797 798 /** 799 * Commit a merged region, moving it from the merges temporary directory to 800 * the proper location in the filesystem. 801 * @param mergedRegionInfo merged region {@link RegionInfo} 802 * @throws IOException 803 */ 804 public void commitMergedRegion(final RegionInfo mergedRegionInfo) throws IOException { 805 Path regionDir = new Path(this.tableDir, mergedRegionInfo.getEncodedName()); 806 Path mergedRegionTmpDir = this.getMergesDir(mergedRegionInfo); 807 // Move the tmp dir in the expected location 808 if (mergedRegionTmpDir != null && fs.exists(mergedRegionTmpDir)) { 809 if (!fs.rename(mergedRegionTmpDir, regionDir)) { 810 throw new IOException("Unable to rename " + mergedRegionTmpDir + " to " 811 + regionDir); 812 } 813 } 814 } 815 816 // =========================================================================== 817 // Create/Open/Delete Helpers 818 // =========================================================================== 819 /** 820 * Log the current state of the region 821 * @param LOG log to output information 822 * @throws IOException if an unexpected exception occurs 823 */ 824 void logFileSystemState(final Logger LOG) throws IOException { 825 FSUtils.logFileSystemState(fs, this.getRegionDir(), LOG); 826 } 827 828 /** 829 * @param hri 830 * @return Content of the file we write out to the filesystem under a region 831 * @throws IOException 832 */ 833 private static byte[] getRegionInfoFileContent(final RegionInfo hri) throws IOException { 834 return RegionInfo.toDelimitedByteArray(hri); 835 } 836 837 /** 838 * Create a {@link RegionInfo} from the serialized version on-disk. 839 * @param fs {@link FileSystem} that contains the Region Info file 840 * @param regionDir {@link Path} to the Region Directory that contains the Info file 841 * @return An {@link RegionInfo} instance gotten from the Region Info file. 842 * @throws IOException if an error occurred during file open/read operation. 843 */ 844 public static RegionInfo loadRegionInfoFileContent(final FileSystem fs, final Path regionDir) 845 throws IOException { 846 FSDataInputStream in = fs.open(new Path(regionDir, REGION_INFO_FILE)); 847 try { 848 return RegionInfo.parseFrom(in); 849 } finally { 850 in.close(); 851 } 852 } 853 854 /** 855 * Write the .regioninfo file on-disk. 856 */ 857 private static void writeRegionInfoFileContent(final Configuration conf, final FileSystem fs, 858 final Path regionInfoFile, final byte[] content) throws IOException { 859 // First check to get the permissions 860 FsPermission perms = FSUtils.getFilePermissions(fs, conf, HConstants.DATA_FILE_UMASK_KEY); 861 // Write the RegionInfo file content 862 FSDataOutputStream out = FSUtils.create(conf, fs, regionInfoFile, perms, null); 863 try { 864 out.write(content); 865 } finally { 866 out.close(); 867 } 868 } 869 870 /** 871 * Write out an info file under the stored region directory. Useful recovering mangled regions. 872 * If the regionInfo already exists on-disk, then we fast exit. 873 */ 874 void checkRegionInfoOnFilesystem() throws IOException { 875 // Compose the content of the file so we can compare to length in filesystem. If not same, 876 // rewrite it (it may have been written in the old format using Writables instead of pb). The 877 // pb version is much shorter -- we write now w/o the toString version -- so checking length 878 // only should be sufficient. I don't want to read the file every time to check if it pb 879 // serialized. 880 byte[] content = getRegionInfoFileContent(regionInfoForFs); 881 882 // Verify if the region directory exists before opening a region. We need to do this since if 883 // the region directory doesn't exist we will re-create the region directory and a new HRI 884 // when HRegion.openHRegion() is called. 885 try { 886 FileStatus status = fs.getFileStatus(getRegionDir()); 887 } catch (FileNotFoundException e) { 888 LOG.warn(getRegionDir() + " doesn't exist for region: " + regionInfoForFs.getEncodedName() + 889 " on table " + regionInfo.getTable()); 890 } 891 892 try { 893 Path regionInfoFile = new Path(getRegionDir(), REGION_INFO_FILE); 894 FileStatus status = fs.getFileStatus(regionInfoFile); 895 if (status != null && status.getLen() == content.length) { 896 // Then assume the content good and move on. 897 // NOTE: that the length is not sufficient to define the the content matches. 898 return; 899 } 900 901 LOG.info("Rewriting .regioninfo file at: " + regionInfoFile); 902 if (!fs.delete(regionInfoFile, false)) { 903 throw new IOException("Unable to remove existing " + regionInfoFile); 904 } 905 } catch (FileNotFoundException e) { 906 LOG.warn(REGION_INFO_FILE + " file not found for region: " + regionInfoForFs.getEncodedName() + 907 " on table " + regionInfo.getTable()); 908 } 909 910 // Write HRI to a file in case we need to recover hbase:meta 911 writeRegionInfoOnFilesystem(content, true); 912 } 913 914 /** 915 * Write out an info file under the region directory. Useful recovering mangled regions. 916 * @param useTempDir indicate whether or not using the region .tmp dir for a safer file creation. 917 */ 918 private void writeRegionInfoOnFilesystem(boolean useTempDir) throws IOException { 919 byte[] content = getRegionInfoFileContent(regionInfoForFs); 920 writeRegionInfoOnFilesystem(content, useTempDir); 921 } 922 923 /** 924 * Write out an info file under the region directory. Useful recovering mangled regions. 925 * @param regionInfoContent serialized version of the {@link RegionInfo} 926 * @param useTempDir indicate whether or not using the region .tmp dir for a safer file creation. 927 */ 928 private void writeRegionInfoOnFilesystem(final byte[] regionInfoContent, 929 final boolean useTempDir) throws IOException { 930 Path regionInfoFile = new Path(getRegionDir(), REGION_INFO_FILE); 931 if (useTempDir) { 932 // Create in tmpDir and then move into place in case we crash after 933 // create but before close. If we don't successfully close the file, 934 // subsequent region reopens will fail the below because create is 935 // registered in NN. 936 937 // And then create the file 938 Path tmpPath = new Path(getTempDir(), REGION_INFO_FILE); 939 940 // If datanode crashes or if the RS goes down just before the close is called while trying to 941 // close the created regioninfo file in the .tmp directory then on next 942 // creation we will be getting AlreadyCreatedException. 943 // Hence delete and create the file if exists. 944 if (FSUtils.isExists(fs, tmpPath)) { 945 FSUtils.delete(fs, tmpPath, true); 946 } 947 948 // Write HRI to a file in case we need to recover hbase:meta 949 writeRegionInfoFileContent(conf, fs, tmpPath, regionInfoContent); 950 951 // Move the created file to the original path 952 if (fs.exists(tmpPath) && !rename(tmpPath, regionInfoFile)) { 953 throw new IOException("Unable to rename " + tmpPath + " to " + regionInfoFile); 954 } 955 } else { 956 // Write HRI to a file in case we need to recover hbase:meta 957 writeRegionInfoFileContent(conf, fs, regionInfoFile, regionInfoContent); 958 } 959 } 960 961 /** 962 * Create a new Region on file-system. 963 * @param conf the {@link Configuration} to use 964 * @param fs {@link FileSystem} from which to add the region 965 * @param tableDir {@link Path} to where the table is being stored 966 * @param regionInfo {@link RegionInfo} for region to be added 967 * @throws IOException if the region creation fails due to a FileSystem exception. 968 */ 969 public static HRegionFileSystem createRegionOnFileSystem(final Configuration conf, 970 final FileSystem fs, final Path tableDir, final RegionInfo regionInfo) throws IOException { 971 HRegionFileSystem regionFs = new HRegionFileSystem(conf, fs, tableDir, regionInfo); 972 973 // We only create a .regioninfo and the region directory if this is the default region replica 974 if (regionInfo.getReplicaId() == RegionInfo.DEFAULT_REPLICA_ID) { 975 Path regionDir = regionFs.getRegionDir(); 976 if (fs.exists(regionDir)) { 977 LOG.warn("Trying to create a region that already exists on disk: " + regionDir); 978 throw new IOException("The specified region already exists on disk: " + regionDir); 979 } 980 981 // Create the region directory 982 if (!createDirOnFileSystem(fs, conf, regionDir)) { 983 LOG.warn("Unable to create the region directory: " + regionDir); 984 throw new IOException("Unable to create region directory: " + regionDir); 985 } 986 987 // Write HRI to a file in case we need to recover hbase:meta 988 regionFs.writeRegionInfoOnFilesystem(false); 989 } else { 990 if (LOG.isDebugEnabled()) 991 LOG.debug("Skipping creation of .regioninfo file for " + regionInfo); 992 } 993 return regionFs; 994 } 995 996 /** 997 * Open Region from file-system. 998 * @param conf the {@link Configuration} to use 999 * @param fs {@link FileSystem} from which to add the region 1000 * @param tableDir {@link Path} to where the table is being stored 1001 * @param regionInfo {@link RegionInfo} for region to be added 1002 * @param readOnly True if you don't want to edit the region data 1003 * @throws IOException if the region creation fails due to a FileSystem exception. 1004 */ 1005 public static HRegionFileSystem openRegionFromFileSystem(final Configuration conf, 1006 final FileSystem fs, final Path tableDir, final RegionInfo regionInfo, boolean readOnly) 1007 throws IOException { 1008 HRegionFileSystem regionFs = new HRegionFileSystem(conf, fs, tableDir, regionInfo); 1009 Path regionDir = regionFs.getRegionDir(); 1010 1011 if (!fs.exists(regionDir)) { 1012 LOG.warn("Trying to open a region that do not exists on disk: " + regionDir); 1013 throw new IOException("The specified region do not exists on disk: " + regionDir); 1014 } 1015 1016 if (!readOnly) { 1017 // Cleanup temporary directories 1018 regionFs.cleanupTempDir(); 1019 regionFs.cleanupSplitsDir(); 1020 regionFs.cleanupMergesDir(); 1021 1022 // If it doesn't exists, Write HRI to a file, in case we need to recover hbase:meta 1023 // Only create HRI if we are the default replica 1024 if (regionInfo.getReplicaId() == RegionInfo.DEFAULT_REPLICA_ID) { 1025 regionFs.checkRegionInfoOnFilesystem(); 1026 } else { 1027 if (LOG.isDebugEnabled()) { 1028 LOG.debug("Skipping creation of .regioninfo file for " + regionInfo); 1029 } 1030 } 1031 } 1032 1033 return regionFs; 1034 } 1035 1036 /** 1037 * Remove the region from the table directory, archiving the region's hfiles. 1038 * @param conf the {@link Configuration} to use 1039 * @param fs {@link FileSystem} from which to remove the region 1040 * @param tableDir {@link Path} to where the table is being stored 1041 * @param regionInfo {@link RegionInfo} for region to be deleted 1042 * @throws IOException if the request cannot be completed 1043 */ 1044 public static void deleteRegionFromFileSystem(final Configuration conf, 1045 final FileSystem fs, final Path tableDir, final RegionInfo regionInfo) throws IOException { 1046 HRegionFileSystem regionFs = new HRegionFileSystem(conf, fs, tableDir, regionInfo); 1047 Path regionDir = regionFs.getRegionDir(); 1048 1049 if (!fs.exists(regionDir)) { 1050 LOG.warn("Trying to delete a region that do not exists on disk: " + regionDir); 1051 return; 1052 } 1053 1054 if (LOG.isDebugEnabled()) { 1055 LOG.debug("DELETING region " + regionDir); 1056 } 1057 1058 // Archive region 1059 Path rootDir = FSUtils.getRootDir(conf); 1060 HFileArchiver.archiveRegion(fs, rootDir, tableDir, regionDir); 1061 1062 // Delete empty region dir 1063 if (!fs.delete(regionDir, true)) { 1064 LOG.warn("Failed delete of " + regionDir); 1065 } 1066 } 1067 1068 /** 1069 * Creates a directory. Assumes the user has already checked for this directory existence. 1070 * @param dir 1071 * @return the result of fs.mkdirs(). In case underlying fs throws an IOException, it checks 1072 * whether the directory exists or not, and returns true if it exists. 1073 * @throws IOException 1074 */ 1075 boolean createDir(Path dir) throws IOException { 1076 int i = 0; 1077 IOException lastIOE = null; 1078 do { 1079 try { 1080 return mkdirs(fs, conf, dir); 1081 } catch (IOException ioe) { 1082 lastIOE = ioe; 1083 if (fs.exists(dir)) return true; // directory is present 1084 try { 1085 sleepBeforeRetry("Create Directory", i+1); 1086 } catch (InterruptedException e) { 1087 throw (InterruptedIOException)new InterruptedIOException().initCause(e); 1088 } 1089 } 1090 } while (++i <= hdfsClientRetriesNumber); 1091 throw new IOException("Exception in createDir", lastIOE); 1092 } 1093 1094 /** 1095 * Renames a directory. Assumes the user has already checked for this directory existence. 1096 * @param srcpath 1097 * @param dstPath 1098 * @return true if rename is successful. 1099 * @throws IOException 1100 */ 1101 boolean rename(Path srcpath, Path dstPath) throws IOException { 1102 IOException lastIOE = null; 1103 int i = 0; 1104 do { 1105 try { 1106 return fs.rename(srcpath, dstPath); 1107 } catch (IOException ioe) { 1108 lastIOE = ioe; 1109 if (!fs.exists(srcpath) && fs.exists(dstPath)) return true; // successful move 1110 // dir is not there, retry after some time. 1111 try { 1112 sleepBeforeRetry("Rename Directory", i+1); 1113 } catch (InterruptedException e) { 1114 throw (InterruptedIOException)new InterruptedIOException().initCause(e); 1115 } 1116 } 1117 } while (++i <= hdfsClientRetriesNumber); 1118 1119 throw new IOException("Exception in rename", lastIOE); 1120 } 1121 1122 /** 1123 * Deletes a directory. Assumes the user has already checked for this directory existence. 1124 * @param dir 1125 * @return true if the directory is deleted. 1126 * @throws IOException 1127 */ 1128 boolean deleteDir(Path dir) throws IOException { 1129 IOException lastIOE = null; 1130 int i = 0; 1131 do { 1132 try { 1133 return fs.delete(dir, true); 1134 } catch (IOException ioe) { 1135 lastIOE = ioe; 1136 if (!fs.exists(dir)) return true; 1137 // dir is there, retry deleting after some time. 1138 try { 1139 sleepBeforeRetry("Delete Directory", i+1); 1140 } catch (InterruptedException e) { 1141 throw (InterruptedIOException)new InterruptedIOException().initCause(e); 1142 } 1143 } 1144 } while (++i <= hdfsClientRetriesNumber); 1145 1146 throw new IOException("Exception in DeleteDir", lastIOE); 1147 } 1148 1149 /** 1150 * sleeping logic; handles the interrupt exception. 1151 */ 1152 private void sleepBeforeRetry(String msg, int sleepMultiplier) throws InterruptedException { 1153 sleepBeforeRetry(msg, sleepMultiplier, baseSleepBeforeRetries, hdfsClientRetriesNumber); 1154 } 1155 1156 /** 1157 * Creates a directory for a filesystem and configuration object. Assumes the user has already 1158 * checked for this directory existence. 1159 * @param fs 1160 * @param conf 1161 * @param dir 1162 * @return the result of fs.mkdirs(). In case underlying fs throws an IOException, it checks 1163 * whether the directory exists or not, and returns true if it exists. 1164 * @throws IOException 1165 */ 1166 private static boolean createDirOnFileSystem(FileSystem fs, Configuration conf, Path dir) 1167 throws IOException { 1168 int i = 0; 1169 IOException lastIOE = null; 1170 int hdfsClientRetriesNumber = conf.getInt("hdfs.client.retries.number", 1171 DEFAULT_HDFS_CLIENT_RETRIES_NUMBER); 1172 int baseSleepBeforeRetries = conf.getInt("hdfs.client.sleep.before.retries", 1173 DEFAULT_BASE_SLEEP_BEFORE_RETRIES); 1174 do { 1175 try { 1176 return fs.mkdirs(dir); 1177 } catch (IOException ioe) { 1178 lastIOE = ioe; 1179 if (fs.exists(dir)) return true; // directory is present 1180 try { 1181 sleepBeforeRetry("Create Directory", i+1, baseSleepBeforeRetries, hdfsClientRetriesNumber); 1182 } catch (InterruptedException e) { 1183 throw (InterruptedIOException)new InterruptedIOException().initCause(e); 1184 } 1185 } 1186 } while (++i <= hdfsClientRetriesNumber); 1187 1188 throw new IOException("Exception in createDir", lastIOE); 1189 } 1190 1191 /** 1192 * sleeping logic for static methods; handles the interrupt exception. Keeping a static version 1193 * for this to avoid re-looking for the integer values. 1194 */ 1195 private static void sleepBeforeRetry(String msg, int sleepMultiplier, int baseSleepBeforeRetries, 1196 int hdfsClientRetriesNumber) throws InterruptedException { 1197 if (sleepMultiplier > hdfsClientRetriesNumber) { 1198 if (LOG.isDebugEnabled()) { 1199 LOG.debug(msg + ", retries exhausted"); 1200 } 1201 return; 1202 } 1203 if (LOG.isDebugEnabled()) { 1204 LOG.debug(msg + ", sleeping " + baseSleepBeforeRetries + " times " + sleepMultiplier); 1205 } 1206 Thread.sleep((long)baseSleepBeforeRetries * sleepMultiplier); 1207 } 1208}