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.wal; 019 020import java.io.EOFException; 021import java.io.FileNotFoundException; 022import java.io.IOException; 023import java.io.InterruptedIOException; 024import java.text.ParseException; 025import java.util.ArrayList; 026import java.util.Arrays; 027import java.util.Collections; 028import java.util.HashMap; 029import java.util.List; 030import java.util.Map; 031import java.util.NavigableSet; 032import java.util.Set; 033import java.util.TreeMap; 034import java.util.TreeSet; 035import java.util.UUID; 036import java.util.concurrent.Callable; 037import java.util.concurrent.CompletionService; 038import java.util.concurrent.ConcurrentHashMap; 039import java.util.concurrent.ExecutionException; 040import java.util.concurrent.ExecutorCompletionService; 041import java.util.concurrent.Future; 042import java.util.concurrent.ThreadFactory; 043import java.util.concurrent.ThreadPoolExecutor; 044import java.util.concurrent.TimeUnit; 045import java.util.concurrent.atomic.AtomicLong; 046import java.util.concurrent.atomic.AtomicReference; 047import java.util.regex.Matcher; 048import java.util.regex.Pattern; 049import org.apache.commons.lang3.ArrayUtils; 050import org.apache.hadoop.conf.Configuration; 051import org.apache.hadoop.fs.FileAlreadyExistsException; 052import org.apache.hadoop.fs.FileStatus; 053import org.apache.hadoop.fs.FileSystem; 054import org.apache.hadoop.fs.Path; 055import org.apache.hadoop.fs.PathFilter; 056import org.apache.hadoop.hbase.Cell; 057import org.apache.hadoop.hbase.CellScanner; 058import org.apache.hadoop.hbase.CellUtil; 059import org.apache.hadoop.hbase.HBaseConfiguration; 060import org.apache.hadoop.hbase.HConstants; 061import org.apache.hadoop.hbase.TableName; 062import org.apache.hadoop.hbase.client.Delete; 063import org.apache.hadoop.hbase.client.Durability; 064import org.apache.hadoop.hbase.client.Mutation; 065import org.apache.hadoop.hbase.client.Put; 066import org.apache.hadoop.hbase.client.RegionInfo; 067import org.apache.hadoop.hbase.coordination.SplitLogWorkerCoordination; 068import org.apache.hadoop.hbase.io.HeapSize; 069import org.apache.hadoop.hbase.log.HBaseMarkers; 070import org.apache.hadoop.hbase.master.SplitLogManager; 071import org.apache.hadoop.hbase.monitoring.MonitoredTask; 072import org.apache.hadoop.hbase.monitoring.TaskMonitor; 073import org.apache.hadoop.hbase.regionserver.HRegion; 074import org.apache.hadoop.hbase.regionserver.LastSequenceId; 075import org.apache.hadoop.hbase.regionserver.wal.AbstractFSWAL; 076import org.apache.hadoop.hbase.regionserver.wal.WALCellCodec; 077import org.apache.hadoop.hbase.util.Bytes; 078import org.apache.hadoop.hbase.util.CancelableProgressable; 079import org.apache.hadoop.hbase.util.ClassSize; 080import org.apache.hadoop.hbase.util.FSUtils; 081import org.apache.hadoop.hbase.util.Pair; 082import org.apache.hadoop.hbase.util.Threads; 083import org.apache.hadoop.hbase.wal.WAL.Entry; 084import org.apache.hadoop.hbase.wal.WAL.Reader; 085import org.apache.hadoop.hbase.wal.WALProvider.Writer; 086import org.apache.hadoop.hbase.zookeeper.ZKSplitLog; 087import org.apache.hadoop.io.MultipleIOException; 088import org.apache.hadoop.ipc.RemoteException; 089import org.apache.yetus.audience.InterfaceAudience; 090import org.slf4j.Logger; 091import org.slf4j.LoggerFactory; 092 093import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; 094import org.apache.hbase.thirdparty.com.google.common.base.Preconditions; 095import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 096import org.apache.hbase.thirdparty.com.google.protobuf.TextFormat; 097import org.apache.hbase.thirdparty.org.apache.commons.collections4.CollectionUtils; 098import org.apache.hbase.thirdparty.org.apache.commons.collections4.MapUtils; 099 100import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.WALEntry; 101import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.MutationProto.MutationType; 102import org.apache.hadoop.hbase.shaded.protobuf.generated.ClusterStatusProtos.RegionStoreSequenceIds; 103import org.apache.hadoop.hbase.shaded.protobuf.generated.ClusterStatusProtos.StoreSequenceId; 104import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; 105/** 106 * This class is responsible for splitting up a bunch of regionserver commit log 107 * files that are no longer being written to, into new files, one per region, for 108 * recovering data on startup. Delete the old log files when finished. 109 */ 110@InterfaceAudience.Private 111public class WALSplitter { 112 private static final Logger LOG = LoggerFactory.getLogger(WALSplitter.class); 113 114 /** By default we retry errors in splitting, rather than skipping. */ 115 public static final boolean SPLIT_SKIP_ERRORS_DEFAULT = false; 116 117 // Parameters for split process 118 protected final Path rootDir; 119 protected final FileSystem fs; 120 protected final Configuration conf; 121 122 // Major subcomponents of the split process. 123 // These are separated into inner classes to make testing easier. 124 OutputSink outputSink; 125 private EntryBuffers entryBuffers; 126 127 private SplitLogWorkerCoordination splitLogWorkerCoordination; 128 private final WALFactory walFactory; 129 130 private MonitoredTask status; 131 132 // For checking the latest flushed sequence id 133 protected final LastSequenceId sequenceIdChecker; 134 135 // Map encodedRegionName -> lastFlushedSequenceId 136 protected Map<String, Long> lastFlushedSequenceIds = new ConcurrentHashMap<>(); 137 138 // Map encodedRegionName -> maxSeqIdInStores 139 protected Map<String, Map<byte[], Long>> regionMaxSeqIdInStores = new ConcurrentHashMap<>(); 140 141 // the file being split currently 142 private FileStatus fileBeingSplit; 143 144 // if we limit the number of writers opened for sinking recovered edits 145 private final boolean splitWriterCreationBounded; 146 147 public final static String SPLIT_WRITER_CREATION_BOUNDED = "hbase.split.writer.creation.bounded"; 148 149 150 @VisibleForTesting 151 WALSplitter(final WALFactory factory, Configuration conf, Path rootDir, 152 FileSystem fs, LastSequenceId idChecker, 153 SplitLogWorkerCoordination splitLogWorkerCoordination) { 154 this.conf = HBaseConfiguration.create(conf); 155 String codecClassName = conf 156 .get(WALCellCodec.WAL_CELL_CODEC_CLASS_KEY, WALCellCodec.class.getName()); 157 this.conf.set(HConstants.RPC_CODEC_CONF_KEY, codecClassName); 158 this.rootDir = rootDir; 159 this.fs = fs; 160 this.sequenceIdChecker = idChecker; 161 this.splitLogWorkerCoordination = splitLogWorkerCoordination; 162 163 this.walFactory = factory; 164 PipelineController controller = new PipelineController(); 165 166 this.splitWriterCreationBounded = conf.getBoolean(SPLIT_WRITER_CREATION_BOUNDED, false); 167 168 entryBuffers = new EntryBuffers(controller, 169 this.conf.getInt("hbase.regionserver.hlog.splitlog.buffersize", 128 * 1024 * 1024), 170 splitWriterCreationBounded); 171 172 int numWriterThreads = this.conf.getInt("hbase.regionserver.hlog.splitlog.writer.threads", 3); 173 if(splitWriterCreationBounded){ 174 outputSink = new BoundedLogWriterCreationOutputSink( 175 controller, entryBuffers, numWriterThreads); 176 }else { 177 outputSink = new LogRecoveredEditsOutputSink(controller, entryBuffers, numWriterThreads); 178 } 179 } 180 181 /** 182 * Splits a WAL file into region's recovered-edits directory. 183 * This is the main entry point for distributed log splitting from SplitLogWorker. 184 * <p> 185 * If the log file has N regions then N recovered.edits files will be produced. 186 * <p> 187 * @return false if it is interrupted by the progress-able. 188 */ 189 public static boolean splitLogFile(Path rootDir, FileStatus logfile, FileSystem fs, 190 Configuration conf, CancelableProgressable reporter, LastSequenceId idChecker, 191 SplitLogWorkerCoordination splitLogWorkerCoordination, final WALFactory factory) 192 throws IOException { 193 WALSplitter s = new WALSplitter(factory, conf, rootDir, fs, idChecker, 194 splitLogWorkerCoordination); 195 return s.splitLogFile(logfile, reporter); 196 } 197 198 // A wrapper to split one log folder using the method used by distributed 199 // log splitting. Used by tools and unit tests. It should be package private. 200 // It is public only because TestWALObserver is in a different package, 201 // which uses this method to do log splitting. 202 @VisibleForTesting 203 public static List<Path> split(Path rootDir, Path logDir, Path oldLogDir, 204 FileSystem fs, Configuration conf, final WALFactory factory) throws IOException { 205 final FileStatus[] logfiles = SplitLogManager.getFileList(conf, 206 Collections.singletonList(logDir), null); 207 List<Path> splits = new ArrayList<>(); 208 if (ArrayUtils.isNotEmpty(logfiles)) { 209 for (FileStatus logfile: logfiles) { 210 WALSplitter s = new WALSplitter(factory, conf, rootDir, fs, null, null); 211 if (s.splitLogFile(logfile, null)) { 212 finishSplitLogFile(rootDir, oldLogDir, logfile.getPath(), conf); 213 if (s.outputSink.splits != null) { 214 splits.addAll(s.outputSink.splits); 215 } 216 } 217 } 218 } 219 if (!fs.delete(logDir, true)) { 220 throw new IOException("Unable to delete src dir: " + logDir); 221 } 222 return splits; 223 } 224 225 /** 226 * log splitting implementation, splits one log file. 227 * @param logfile should be an actual log file. 228 */ 229 @VisibleForTesting 230 boolean splitLogFile(FileStatus logfile, CancelableProgressable reporter) throws IOException { 231 Preconditions.checkState(status == null); 232 Preconditions.checkArgument(logfile.isFile(), 233 "passed in file status is for something other than a regular file."); 234 boolean isCorrupted = false; 235 boolean skipErrors = conf.getBoolean("hbase.hlog.split.skip.errors", 236 SPLIT_SKIP_ERRORS_DEFAULT); 237 int interval = conf.getInt("hbase.splitlog.report.interval.loglines", 1024); 238 Path logPath = logfile.getPath(); 239 boolean outputSinkStarted = false; 240 boolean progress_failed = false; 241 int editsCount = 0; 242 int editsSkipped = 0; 243 244 status = TaskMonitor.get().createStatus( 245 "Splitting log file " + logfile.getPath() + "into a temporary staging area."); 246 Reader logFileReader = null; 247 this.fileBeingSplit = logfile; 248 try { 249 long logLength = logfile.getLen(); 250 LOG.info("Splitting WAL={}, length={}", logPath, logLength); 251 status.setStatus("Opening log file"); 252 if (reporter != null && !reporter.progress()) { 253 progress_failed = true; 254 return false; 255 } 256 logFileReader = getReader(logfile, skipErrors, reporter); 257 if (logFileReader == null) { 258 LOG.warn("Nothing to split in WAL={}", logPath); 259 return true; 260 } 261 int numOpenedFilesBeforeReporting = conf.getInt("hbase.splitlog.report.openedfiles", 3); 262 int numOpenedFilesLastCheck = 0; 263 outputSink.setReporter(reporter); 264 outputSink.startWriterThreads(); 265 outputSinkStarted = true; 266 Entry entry; 267 Long lastFlushedSequenceId = -1L; 268 while ((entry = getNextLogLine(logFileReader, logPath, skipErrors)) != null) { 269 byte[] region = entry.getKey().getEncodedRegionName(); 270 String encodedRegionNameAsStr = Bytes.toString(region); 271 lastFlushedSequenceId = lastFlushedSequenceIds.get(encodedRegionNameAsStr); 272 if (lastFlushedSequenceId == null) { 273 if (sequenceIdChecker != null) { 274 RegionStoreSequenceIds ids = sequenceIdChecker.getLastSequenceId(region); 275 Map<byte[], Long> maxSeqIdInStores = new TreeMap<>(Bytes.BYTES_COMPARATOR); 276 for (StoreSequenceId storeSeqId : ids.getStoreSequenceIdList()) { 277 maxSeqIdInStores.put(storeSeqId.getFamilyName().toByteArray(), 278 storeSeqId.getSequenceId()); 279 } 280 regionMaxSeqIdInStores.put(encodedRegionNameAsStr, maxSeqIdInStores); 281 lastFlushedSequenceId = ids.getLastFlushedSequenceId(); 282 if (LOG.isDebugEnabled()) { 283 LOG.debug("DLS Last flushed sequenceid for " + encodedRegionNameAsStr + ": " + 284 TextFormat.shortDebugString(ids)); 285 } 286 } 287 if (lastFlushedSequenceId == null) { 288 lastFlushedSequenceId = -1L; 289 } 290 lastFlushedSequenceIds.put(encodedRegionNameAsStr, lastFlushedSequenceId); 291 } 292 if (lastFlushedSequenceId >= entry.getKey().getSequenceId()) { 293 editsSkipped++; 294 continue; 295 } 296 // Don't send Compaction/Close/Open region events to recovered edit type sinks. 297 if (entry.getEdit().isMetaEdit() && !outputSink.keepRegionEvent(entry)) { 298 editsSkipped++; 299 continue; 300 } 301 entryBuffers.appendEntry(entry); 302 editsCount++; 303 int moreWritersFromLastCheck = this.getNumOpenWriters() - numOpenedFilesLastCheck; 304 // If sufficient edits have passed, check if we should report progress. 305 if (editsCount % interval == 0 306 || moreWritersFromLastCheck > numOpenedFilesBeforeReporting) { 307 numOpenedFilesLastCheck = this.getNumOpenWriters(); 308 String countsStr = (editsCount - (editsSkipped + outputSink.getSkippedEdits())) 309 + " edits, skipped " + editsSkipped + " edits."; 310 status.setStatus("Split " + countsStr); 311 if (reporter != null && !reporter.progress()) { 312 progress_failed = true; 313 return false; 314 } 315 } 316 } 317 } catch (InterruptedException ie) { 318 IOException iie = new InterruptedIOException(); 319 iie.initCause(ie); 320 throw iie; 321 } catch (CorruptedLogFileException e) { 322 LOG.warn("Could not parse, corrupted WAL={}", logPath, e); 323 if (splitLogWorkerCoordination != null) { 324 // Some tests pass in a csm of null. 325 splitLogWorkerCoordination.markCorrupted(rootDir, logfile.getPath().getName(), fs); 326 } else { 327 // for tests only 328 ZKSplitLog.markCorrupted(rootDir, logfile.getPath().getName(), fs); 329 } 330 isCorrupted = true; 331 } catch (IOException e) { 332 e = e instanceof RemoteException ? ((RemoteException) e).unwrapRemoteException() : e; 333 throw e; 334 } finally { 335 LOG.debug("Finishing writing output logs and closing down"); 336 try { 337 if (null != logFileReader) { 338 logFileReader.close(); 339 } 340 } catch (IOException exception) { 341 LOG.warn("Could not close WAL reader", exception); 342 } 343 try { 344 if (outputSinkStarted) { 345 // Set progress_failed to true as the immediate following statement will reset its value 346 // when finishWritingAndClose() throws exception, progress_failed has the right value 347 progress_failed = true; 348 progress_failed = outputSink.finishWritingAndClose() == null; 349 } 350 } finally { 351 String msg = 352 "Processed " + editsCount + " edits across " + outputSink.getNumberOfRecoveredRegions() 353 + " regions; edits skipped=" + editsSkipped + "; log file=" + logPath + 354 ", length=" + logfile.getLen() + // See if length got updated post lease recovery 355 ", corrupted=" + isCorrupted + ", progress failed=" + progress_failed; 356 LOG.info(msg); 357 status.markComplete(msg); 358 } 359 } 360 return !progress_failed; 361 } 362 363 /** 364 * Completes the work done by splitLogFile by archiving logs 365 * <p> 366 * It is invoked by SplitLogManager once it knows that one of the 367 * SplitLogWorkers have completed the splitLogFile() part. If the master 368 * crashes then this function might get called multiple times. 369 * <p> 370 * @param logfile 371 * @param conf 372 * @throws IOException 373 */ 374 public static void finishSplitLogFile(String logfile, 375 Configuration conf) throws IOException { 376 Path rootdir = FSUtils.getWALRootDir(conf); 377 Path oldLogDir = new Path(rootdir, HConstants.HREGION_OLDLOGDIR_NAME); 378 Path logPath; 379 if (FSUtils.isStartingWithPath(rootdir, logfile)) { 380 logPath = new Path(logfile); 381 } else { 382 logPath = new Path(rootdir, logfile); 383 } 384 finishSplitLogFile(rootdir, oldLogDir, logPath, conf); 385 } 386 387 private static void finishSplitLogFile(Path rootdir, Path oldLogDir, 388 Path logPath, Configuration conf) throws IOException { 389 List<Path> processedLogs = new ArrayList<>(); 390 List<Path> corruptedLogs = new ArrayList<>(); 391 FileSystem fs; 392 fs = rootdir.getFileSystem(conf); 393 if (ZKSplitLog.isCorrupted(rootdir, logPath.getName(), fs)) { 394 corruptedLogs.add(logPath); 395 } else { 396 processedLogs.add(logPath); 397 } 398 archiveLogs(corruptedLogs, processedLogs, oldLogDir, fs, conf); 399 Path stagingDir = ZKSplitLog.getSplitLogDir(rootdir, logPath.getName()); 400 fs.delete(stagingDir, true); 401 } 402 403 /** 404 * Moves processed logs to a oldLogDir after successful processing Moves 405 * corrupted logs (any log that couldn't be successfully parsed to corruptDir 406 * (.corrupt) for later investigation 407 * 408 * @param corruptedLogs 409 * @param processedLogs 410 * @param oldLogDir 411 * @param fs 412 * @param conf 413 * @throws IOException 414 */ 415 private static void archiveLogs( 416 final List<Path> corruptedLogs, 417 final List<Path> processedLogs, final Path oldLogDir, 418 final FileSystem fs, final Configuration conf) throws IOException { 419 final Path corruptDir = new Path(FSUtils.getWALRootDir(conf), HConstants.CORRUPT_DIR_NAME); 420 if (conf.get("hbase.regionserver.hlog.splitlog.corrupt.dir") != null) { 421 LOG.warn("hbase.regionserver.hlog.splitlog.corrupt.dir is deprecated. Default to {}", 422 corruptDir); 423 } 424 if (!fs.mkdirs(corruptDir)) { 425 LOG.info("Unable to mkdir {}", corruptDir); 426 } 427 fs.mkdirs(oldLogDir); 428 429 // this method can get restarted or called multiple times for archiving 430 // the same log files. 431 for (Path corrupted : corruptedLogs) { 432 Path p = new Path(corruptDir, corrupted.getName()); 433 if (fs.exists(corrupted)) { 434 if (!fs.rename(corrupted, p)) { 435 LOG.warn("Unable to move corrupted log {} to {}", corrupted, p); 436 } else { 437 LOG.warn("Moved corrupted log {} to {}", corrupted, p); 438 } 439 } 440 } 441 442 for (Path p : processedLogs) { 443 Path newPath = AbstractFSWAL.getWALArchivePath(oldLogDir, p); 444 if (fs.exists(p)) { 445 if (!FSUtils.renameAndSetModifyTime(fs, p, newPath)) { 446 LOG.warn("Unable to move {} to {}", p, newPath); 447 } else { 448 LOG.info("Archived processed log {} to {}", p, newPath); 449 } 450 } 451 } 452 } 453 454 /** 455 * Path to a file under RECOVERED_EDITS_DIR directory of the region found in 456 * <code>logEntry</code> named for the sequenceid in the passed 457 * <code>logEntry</code>: e.g. /hbase/some_table/2323432434/recovered.edits/2332. 458 * This method also ensures existence of RECOVERED_EDITS_DIR under the region 459 * creating it if necessary. 460 * @param fs 461 * @param logEntry 462 * @param rootDir HBase root dir. 463 * @param fileNameBeingSplit the file being split currently. Used to generate tmp file name. 464 * @return Path to file into which to dump split log edits. 465 * @throws IOException 466 */ 467 @SuppressWarnings("deprecation") 468 @VisibleForTesting 469 static Path getRegionSplitEditsPath(final FileSystem fs, 470 final Entry logEntry, final Path rootDir, String fileNameBeingSplit) 471 throws IOException { 472 Path tableDir = FSUtils.getTableDir(rootDir, logEntry.getKey().getTableName()); 473 String encodedRegionName = Bytes.toString(logEntry.getKey().getEncodedRegionName()); 474 Path regiondir = HRegion.getRegionDir(tableDir, encodedRegionName); 475 Path dir = getRegionDirRecoveredEditsDir(regiondir); 476 477 if (!fs.exists(regiondir)) { 478 LOG.info("This region's directory does not exist: {}." 479 + "It is very likely that it was already split so it is " 480 + "safe to discard those edits.", regiondir); 481 return null; 482 } 483 if (fs.exists(dir) && fs.isFile(dir)) { 484 Path tmp = new Path("/tmp"); 485 if (!fs.exists(tmp)) { 486 fs.mkdirs(tmp); 487 } 488 tmp = new Path(tmp, 489 HConstants.RECOVERED_EDITS_DIR + "_" + encodedRegionName); 490 LOG.warn("Found existing old file: {}. It could be some " 491 + "leftover of an old installation. It should be a folder instead. " 492 + "So moving it to {}", dir, tmp); 493 if (!fs.rename(dir, tmp)) { 494 LOG.warn("Failed to sideline old file {}", dir); 495 } 496 } 497 498 if (!fs.exists(dir) && !fs.mkdirs(dir)) { 499 LOG.warn("mkdir failed on {}", dir); 500 } 501 // Append fileBeingSplit to prevent name conflict since we may have duplicate wal entries now. 502 // Append file name ends with RECOVERED_LOG_TMPFILE_SUFFIX to ensure 503 // region's replayRecoveredEdits will not delete it 504 String fileName = formatRecoveredEditsFileName(logEntry.getKey().getSequenceId()); 505 fileName = getTmpRecoveredEditsFileName(fileName + "-" + fileNameBeingSplit); 506 return new Path(dir, fileName); 507 } 508 509 private static String getTmpRecoveredEditsFileName(String fileName) { 510 return fileName + RECOVERED_LOG_TMPFILE_SUFFIX; 511 } 512 513 /** 514 * Get the completed recovered edits file path, renaming it to be by last edit 515 * in the file from its first edit. Then we could use the name to skip 516 * recovered edits when doing {@link HRegion#replayRecoveredEditsIfAny}. 517 * @param srcPath 518 * @param maximumEditLogSeqNum 519 * @return dstPath take file's last edit log seq num as the name 520 */ 521 private static Path getCompletedRecoveredEditsFilePath(Path srcPath, 522 long maximumEditLogSeqNum) { 523 String fileName = formatRecoveredEditsFileName(maximumEditLogSeqNum); 524 return new Path(srcPath.getParent(), fileName); 525 } 526 527 @VisibleForTesting 528 static String formatRecoveredEditsFileName(final long seqid) { 529 return String.format("%019d", seqid); 530 } 531 532 private static final Pattern EDITFILES_NAME_PATTERN = Pattern.compile("-?[0-9]+"); 533 private static final String RECOVERED_LOG_TMPFILE_SUFFIX = ".temp"; 534 535 /** 536 * @param regiondir 537 * This regions directory in the filesystem. 538 * @return The directory that holds recovered edits files for the region 539 * <code>regiondir</code> 540 */ 541 public static Path getRegionDirRecoveredEditsDir(final Path regiondir) { 542 return new Path(regiondir, HConstants.RECOVERED_EDITS_DIR); 543 } 544 545 /** 546 * Check whether there is recovered.edits in the region dir 547 * @param fs FileSystem 548 * @param conf conf 549 * @param regionInfo the region to check 550 * @throws IOException IOException 551 * @return true if recovered.edits exist in the region dir 552 */ 553 public static boolean hasRecoveredEdits(final FileSystem fs, 554 final Configuration conf, final RegionInfo regionInfo) throws IOException { 555 // No recovered.edits for non default replica regions 556 if (regionInfo.getReplicaId() != RegionInfo.DEFAULT_REPLICA_ID) { 557 return false; 558 } 559 Path rootDir = FSUtils.getRootDir(conf); 560 //Only default replica region can reach here, so we can use regioninfo 561 //directly without converting it to default replica's regioninfo. 562 Path regionDir = HRegion.getRegionDir(rootDir, regionInfo); 563 NavigableSet<Path> files = getSplitEditFilesSorted(fs, regionDir); 564 return files != null && !files.isEmpty(); 565 } 566 567 568 /** 569 * Returns sorted set of edit files made by splitter, excluding files 570 * with '.temp' suffix. 571 * 572 * @param fs 573 * @param regiondir 574 * @return Files in passed <code>regiondir</code> as a sorted set. 575 * @throws IOException 576 */ 577 public static NavigableSet<Path> getSplitEditFilesSorted(final FileSystem fs, 578 final Path regiondir) throws IOException { 579 NavigableSet<Path> filesSorted = new TreeSet<>(); 580 Path editsdir = getRegionDirRecoveredEditsDir(regiondir); 581 if (!fs.exists(editsdir)) { 582 return filesSorted; 583 } 584 FileStatus[] files = FSUtils.listStatus(fs, editsdir, new PathFilter() { 585 @Override 586 public boolean accept(Path p) { 587 boolean result = false; 588 try { 589 // Return files and only files that match the editfile names pattern. 590 // There can be other files in this directory other than edit files. 591 // In particular, on error, we'll move aside the bad edit file giving 592 // it a timestamp suffix. See moveAsideBadEditsFile. 593 Matcher m = EDITFILES_NAME_PATTERN.matcher(p.getName()); 594 result = fs.isFile(p) && m.matches(); 595 // Skip the file whose name ends with RECOVERED_LOG_TMPFILE_SUFFIX, 596 // because it means splitwal thread is writting this file. 597 if (p.getName().endsWith(RECOVERED_LOG_TMPFILE_SUFFIX)) { 598 result = false; 599 } 600 // Skip SeqId Files 601 if (isSequenceIdFile(p)) { 602 result = false; 603 } 604 } catch (IOException e) { 605 LOG.warn("Failed isFile check on {}", p, e); 606 } 607 return result; 608 } 609 }); 610 if (ArrayUtils.isNotEmpty(files)) { 611 Arrays.asList(files).forEach(status -> filesSorted.add(status.getPath())); 612 } 613 return filesSorted; 614 } 615 616 /** 617 * Move aside a bad edits file. 618 * 619 * @param fs 620 * @param edits 621 * Edits file to move aside. 622 * @return The name of the moved aside file. 623 * @throws IOException 624 */ 625 public static Path moveAsideBadEditsFile(final FileSystem fs, final Path edits) 626 throws IOException { 627 Path moveAsideName = new Path(edits.getParent(), edits.getName() + "." 628 + System.currentTimeMillis()); 629 if (!fs.rename(edits, moveAsideName)) { 630 LOG.warn("Rename failed from {} to {}", edits, moveAsideName); 631 } 632 return moveAsideName; 633 } 634 635 private static final String SEQUENCE_ID_FILE_SUFFIX = ".seqid"; 636 private static final String OLD_SEQUENCE_ID_FILE_SUFFIX = "_seqid"; 637 private static final int SEQUENCE_ID_FILE_SUFFIX_LENGTH = SEQUENCE_ID_FILE_SUFFIX.length(); 638 639 /** 640 * Is the given file a region open sequence id file. 641 */ 642 @VisibleForTesting 643 public static boolean isSequenceIdFile(final Path file) { 644 return file.getName().endsWith(SEQUENCE_ID_FILE_SUFFIX) 645 || file.getName().endsWith(OLD_SEQUENCE_ID_FILE_SUFFIX); 646 } 647 648 private static FileStatus[] getSequenceIdFiles(FileSystem fs, Path regionDir) throws IOException { 649 // TODO: Why are we using a method in here as part of our normal region open where 650 // there is no splitting involved? Fix. St.Ack 01/20/2017. 651 Path editsDir = WALSplitter.getRegionDirRecoveredEditsDir(regionDir); 652 try { 653 FileStatus[] files = fs.listStatus(editsDir, WALSplitter::isSequenceIdFile); 654 return files != null ? files : new FileStatus[0]; 655 } catch (FileNotFoundException e) { 656 return new FileStatus[0]; 657 } 658 } 659 660 private static long getMaxSequenceId(FileStatus[] files) { 661 long maxSeqId = -1L; 662 for (FileStatus file : files) { 663 String fileName = file.getPath().getName(); 664 try { 665 maxSeqId = Math.max(maxSeqId, Long 666 .parseLong(fileName.substring(0, fileName.length() - SEQUENCE_ID_FILE_SUFFIX_LENGTH))); 667 } catch (NumberFormatException ex) { 668 LOG.warn("Invalid SeqId File Name={}", fileName); 669 } 670 } 671 return maxSeqId; 672 } 673 674 /** 675 * Get the max sequence id which is stored in the region directory. -1 if none. 676 */ 677 public static long getMaxRegionSequenceId(FileSystem fs, Path regionDir) throws IOException { 678 return getMaxSequenceId(getSequenceIdFiles(fs, regionDir)); 679 } 680 681 /** 682 * Create a file with name as region's max sequence id 683 */ 684 public static void writeRegionSequenceIdFile(FileSystem fs, Path regionDir, long newMaxSeqId) 685 throws IOException { 686 FileStatus[] files = getSequenceIdFiles(fs, regionDir); 687 long maxSeqId = getMaxSequenceId(files); 688 if (maxSeqId > newMaxSeqId) { 689 throw new IOException("The new max sequence id " + newMaxSeqId + 690 " is less than the old max sequence id " + maxSeqId); 691 } 692 // write a new seqId file 693 Path newSeqIdFile = new Path(WALSplitter.getRegionDirRecoveredEditsDir(regionDir), 694 newMaxSeqId + SEQUENCE_ID_FILE_SUFFIX); 695 if (newMaxSeqId != maxSeqId) { 696 try { 697 if (!fs.createNewFile(newSeqIdFile) && !fs.exists(newSeqIdFile)) { 698 throw new IOException("Failed to create SeqId file:" + newSeqIdFile); 699 } 700 LOG.debug("Wrote file={}, newMaxSeqId={}, maxSeqId={}", newSeqIdFile, newMaxSeqId, 701 maxSeqId); 702 } catch (FileAlreadyExistsException ignored) { 703 // latest hdfs throws this exception. it's all right if newSeqIdFile already exists 704 } 705 } 706 // remove old ones 707 for (FileStatus status : files) { 708 if (!newSeqIdFile.equals(status.getPath())) { 709 fs.delete(status.getPath(), false); 710 } 711 } 712 } 713 714 /** 715 * Create a new {@link Reader} for reading logs to split. 716 * 717 * @param file 718 * @return A new Reader instance, caller should close 719 * @throws IOException 720 * @throws CorruptedLogFileException 721 */ 722 protected Reader getReader(FileStatus file, boolean skipErrors, CancelableProgressable reporter) 723 throws IOException, CorruptedLogFileException { 724 Path path = file.getPath(); 725 long length = file.getLen(); 726 Reader in; 727 728 // Check for possibly empty file. With appends, currently Hadoop reports a 729 // zero length even if the file has been sync'd. Revisit if HDFS-376 or 730 // HDFS-878 is committed. 731 if (length <= 0) { 732 LOG.warn("File {} might be still open, length is 0", path); 733 } 734 735 try { 736 FSUtils.getInstance(fs, conf).recoverFileLease(fs, path, conf, reporter); 737 try { 738 in = getReader(path, reporter); 739 } catch (EOFException e) { 740 if (length <= 0) { 741 // TODO should we ignore an empty, not-last log file if skip.errors 742 // is false? Either way, the caller should decide what to do. E.g. 743 // ignore if this is the last log in sequence. 744 // TODO is this scenario still possible if the log has been 745 // recovered (i.e. closed) 746 LOG.warn("Could not open {} for reading. File is empty", path, e); 747 } 748 // EOFException being ignored 749 return null; 750 } 751 } catch (IOException e) { 752 if (e instanceof FileNotFoundException) { 753 // A wal file may not exist anymore. Nothing can be recovered so move on 754 LOG.warn("File {} does not exist anymore", path, e); 755 return null; 756 } 757 if (!skipErrors || e instanceof InterruptedIOException) { 758 throw e; // Don't mark the file corrupted if interrupted, or not skipErrors 759 } 760 CorruptedLogFileException t = 761 new CorruptedLogFileException("skipErrors=true Could not open wal " + 762 path + " ignoring"); 763 t.initCause(e); 764 throw t; 765 } 766 return in; 767 } 768 769 static private Entry getNextLogLine(Reader in, Path path, boolean skipErrors) 770 throws CorruptedLogFileException, IOException { 771 try { 772 return in.next(); 773 } catch (EOFException eof) { 774 // truncated files are expected if a RS crashes (see HBASE-2643) 775 LOG.info("EOF from wal {}. Continuing.", path); 776 return null; 777 } catch (IOException e) { 778 // If the IOE resulted from bad file format, 779 // then this problem is idempotent and retrying won't help 780 if (e.getCause() != null && 781 (e.getCause() instanceof ParseException || 782 e.getCause() instanceof org.apache.hadoop.fs.ChecksumException)) { 783 LOG.warn("Parse exception from wal {}. Continuing", path, e); 784 return null; 785 } 786 if (!skipErrors) { 787 throw e; 788 } 789 CorruptedLogFileException t = 790 new CorruptedLogFileException("skipErrors=true Ignoring exception" + 791 " while parsing wal " + path + ". Marking as corrupted"); 792 t.initCause(e); 793 throw t; 794 } 795 } 796 797 /** 798 * Create a new {@link Writer} for writing log splits. 799 * @return a new Writer instance, caller should close 800 */ 801 protected Writer createWriter(Path logfile) 802 throws IOException { 803 return walFactory.createRecoveredEditsWriter(fs, logfile); 804 } 805 806 /** 807 * Create a new {@link Reader} for reading logs to split. 808 * @return new Reader instance, caller should close 809 */ 810 protected Reader getReader(Path curLogFile, CancelableProgressable reporter) throws IOException { 811 return walFactory.createReader(fs, curLogFile, reporter); 812 } 813 814 /** 815 * Get current open writers 816 */ 817 private int getNumOpenWriters() { 818 int result = 0; 819 if (this.outputSink != null) { 820 result += this.outputSink.getNumOpenWriters(); 821 } 822 return result; 823 } 824 825 /** 826 * Contains some methods to control WAL-entries producer / consumer interactions 827 */ 828 public static class PipelineController { 829 // If an exception is thrown by one of the other threads, it will be 830 // stored here. 831 AtomicReference<Throwable> thrown = new AtomicReference<>(); 832 833 // Wait/notify for when data has been produced by the writer thread, 834 // consumed by the reader thread, or an exception occurred 835 public final Object dataAvailable = new Object(); 836 837 void writerThreadError(Throwable t) { 838 thrown.compareAndSet(null, t); 839 } 840 841 /** 842 * Check for errors in the writer threads. If any is found, rethrow it. 843 */ 844 void checkForErrors() throws IOException { 845 Throwable thrown = this.thrown.get(); 846 if (thrown == null) return; 847 if (thrown instanceof IOException) { 848 throw new IOException(thrown); 849 } else { 850 throw new RuntimeException(thrown); 851 } 852 } 853 } 854 855 /** 856 * Class which accumulates edits and separates them into a buffer per region 857 * while simultaneously accounting RAM usage. Blocks if the RAM usage crosses 858 * a predefined threshold. 859 * 860 * Writer threads then pull region-specific buffers from this class. 861 */ 862 public static class EntryBuffers { 863 PipelineController controller; 864 865 Map<byte[], RegionEntryBuffer> buffers = new TreeMap<>(Bytes.BYTES_COMPARATOR); 866 867 /* Track which regions are currently in the middle of writing. We don't allow 868 an IO thread to pick up bytes from a region if we're already writing 869 data for that region in a different IO thread. */ 870 Set<byte[]> currentlyWriting = new TreeSet<>(Bytes.BYTES_COMPARATOR); 871 872 long totalBuffered = 0; 873 long maxHeapUsage; 874 boolean splitWriterCreationBounded; 875 876 public EntryBuffers(PipelineController controller, long maxHeapUsage) { 877 this(controller, maxHeapUsage, false); 878 } 879 880 public EntryBuffers(PipelineController controller, long maxHeapUsage, 881 boolean splitWriterCreationBounded){ 882 this.controller = controller; 883 this.maxHeapUsage = maxHeapUsage; 884 this.splitWriterCreationBounded = splitWriterCreationBounded; 885 } 886 887 /** 888 * Append a log entry into the corresponding region buffer. 889 * Blocks if the total heap usage has crossed the specified threshold. 890 * 891 * @throws InterruptedException 892 * @throws IOException 893 */ 894 public void appendEntry(Entry entry) throws InterruptedException, IOException { 895 WALKey key = entry.getKey(); 896 897 RegionEntryBuffer buffer; 898 long incrHeap; 899 synchronized (this) { 900 buffer = buffers.get(key.getEncodedRegionName()); 901 if (buffer == null) { 902 buffer = new RegionEntryBuffer(key.getTableName(), key.getEncodedRegionName()); 903 buffers.put(key.getEncodedRegionName(), buffer); 904 } 905 incrHeap= buffer.appendEntry(entry); 906 } 907 908 // If we crossed the chunk threshold, wait for more space to be available 909 synchronized (controller.dataAvailable) { 910 totalBuffered += incrHeap; 911 while (totalBuffered > maxHeapUsage && controller.thrown.get() == null) { 912 LOG.debug("Used {} bytes of buffered edits, waiting for IO threads", totalBuffered); 913 controller.dataAvailable.wait(2000); 914 } 915 controller.dataAvailable.notifyAll(); 916 } 917 controller.checkForErrors(); 918 } 919 920 /** 921 * @return RegionEntryBuffer a buffer of edits to be written. 922 */ 923 synchronized RegionEntryBuffer getChunkToWrite() { 924 // The core part of limiting opening writers is it doesn't return chunk only if the 925 // heap size is over maxHeapUsage. Thus it doesn't need to create a writer for each 926 // region during splitting. It will flush all the logs in the buffer after splitting 927 // through a threadpool, which means the number of writers it created is under control. 928 if (splitWriterCreationBounded && totalBuffered < maxHeapUsage) { 929 return null; 930 } 931 long biggestSize = 0; 932 byte[] biggestBufferKey = null; 933 934 for (Map.Entry<byte[], RegionEntryBuffer> entry : buffers.entrySet()) { 935 long size = entry.getValue().heapSize(); 936 if (size > biggestSize && (!currentlyWriting.contains(entry.getKey()))) { 937 biggestSize = size; 938 biggestBufferKey = entry.getKey(); 939 } 940 } 941 if (biggestBufferKey == null) { 942 return null; 943 } 944 945 RegionEntryBuffer buffer = buffers.remove(biggestBufferKey); 946 currentlyWriting.add(biggestBufferKey); 947 return buffer; 948 } 949 950 void doneWriting(RegionEntryBuffer buffer) { 951 synchronized (this) { 952 boolean removed = currentlyWriting.remove(buffer.encodedRegionName); 953 assert removed; 954 } 955 long size = buffer.heapSize(); 956 957 synchronized (controller.dataAvailable) { 958 totalBuffered -= size; 959 // We may unblock writers 960 controller.dataAvailable.notifyAll(); 961 } 962 } 963 964 synchronized boolean isRegionCurrentlyWriting(byte[] region) { 965 return currentlyWriting.contains(region); 966 } 967 968 public void waitUntilDrained() { 969 synchronized (controller.dataAvailable) { 970 while (totalBuffered > 0) { 971 try { 972 controller.dataAvailable.wait(2000); 973 } catch (InterruptedException e) { 974 LOG.warn("Got interrupted while waiting for EntryBuffers is drained"); 975 Thread.interrupted(); 976 break; 977 } 978 } 979 } 980 } 981 } 982 983 /** 984 * A buffer of some number of edits for a given region. 985 * This accumulates edits and also provides a memory optimization in order to 986 * share a single byte array instance for the table and region name. 987 * Also tracks memory usage of the accumulated edits. 988 */ 989 public static class RegionEntryBuffer implements HeapSize { 990 long heapInBuffer = 0; 991 List<Entry> entryBuffer; 992 TableName tableName; 993 byte[] encodedRegionName; 994 995 RegionEntryBuffer(TableName tableName, byte[] region) { 996 this.tableName = tableName; 997 this.encodedRegionName = region; 998 this.entryBuffer = new ArrayList<>(); 999 } 1000 1001 long appendEntry(Entry entry) { 1002 internify(entry); 1003 entryBuffer.add(entry); 1004 long incrHeap = entry.getEdit().heapSize() + 1005 ClassSize.align(2 * ClassSize.REFERENCE) + // WALKey pointers 1006 0; // TODO linkedlist entry 1007 heapInBuffer += incrHeap; 1008 return incrHeap; 1009 } 1010 1011 private void internify(Entry entry) { 1012 WALKeyImpl k = entry.getKey(); 1013 k.internTableName(this.tableName); 1014 k.internEncodedRegionName(this.encodedRegionName); 1015 } 1016 1017 @Override 1018 public long heapSize() { 1019 return heapInBuffer; 1020 } 1021 1022 public byte[] getEncodedRegionName() { 1023 return encodedRegionName; 1024 } 1025 1026 public List<Entry> getEntryBuffer() { 1027 return entryBuffer; 1028 } 1029 1030 public TableName getTableName() { 1031 return tableName; 1032 } 1033 } 1034 1035 public static class WriterThread extends Thread { 1036 private volatile boolean shouldStop = false; 1037 private PipelineController controller; 1038 private EntryBuffers entryBuffers; 1039 private OutputSink outputSink = null; 1040 1041 WriterThread(PipelineController controller, EntryBuffers entryBuffers, OutputSink sink, int i){ 1042 super(Thread.currentThread().getName() + "-Writer-" + i); 1043 this.controller = controller; 1044 this.entryBuffers = entryBuffers; 1045 outputSink = sink; 1046 } 1047 1048 @Override 1049 public void run() { 1050 try { 1051 doRun(); 1052 } catch (Throwable t) { 1053 LOG.error("Exiting thread", t); 1054 controller.writerThreadError(t); 1055 } 1056 } 1057 1058 private void doRun() throws IOException { 1059 LOG.trace("Writer thread starting"); 1060 while (true) { 1061 RegionEntryBuffer buffer = entryBuffers.getChunkToWrite(); 1062 if (buffer == null) { 1063 // No data currently available, wait on some more to show up 1064 synchronized (controller.dataAvailable) { 1065 if (shouldStop && !this.outputSink.flush()) { 1066 return; 1067 } 1068 try { 1069 controller.dataAvailable.wait(500); 1070 } catch (InterruptedException ie) { 1071 if (!shouldStop) { 1072 throw new RuntimeException(ie); 1073 } 1074 } 1075 } 1076 continue; 1077 } 1078 1079 assert buffer != null; 1080 try { 1081 writeBuffer(buffer); 1082 } finally { 1083 entryBuffers.doneWriting(buffer); 1084 } 1085 } 1086 } 1087 1088 private void writeBuffer(RegionEntryBuffer buffer) throws IOException { 1089 outputSink.append(buffer); 1090 } 1091 1092 void finish() { 1093 synchronized (controller.dataAvailable) { 1094 shouldStop = true; 1095 controller.dataAvailable.notifyAll(); 1096 } 1097 } 1098 } 1099 1100 /** 1101 * The following class is an abstraction class to provide a common interface to support 1102 * different ways of consuming recovered edits. 1103 */ 1104 public static abstract class OutputSink { 1105 1106 protected PipelineController controller; 1107 protected EntryBuffers entryBuffers; 1108 1109 protected ConcurrentHashMap<String, SinkWriter> writers = new ConcurrentHashMap<>(); 1110 protected final ConcurrentHashMap<String, Long> regionMaximumEditLogSeqNum = 1111 new ConcurrentHashMap<>(); 1112 1113 1114 protected final List<WriterThread> writerThreads = Lists.newArrayList(); 1115 1116 /* Set of regions which we've decided should not output edits */ 1117 protected final Set<byte[]> blacklistedRegions = Collections 1118 .synchronizedSet(new TreeSet<>(Bytes.BYTES_COMPARATOR)); 1119 1120 protected boolean closeAndCleanCompleted = false; 1121 1122 protected boolean writersClosed = false; 1123 1124 protected final int numThreads; 1125 1126 protected CancelableProgressable reporter = null; 1127 1128 protected AtomicLong skippedEdits = new AtomicLong(); 1129 1130 protected List<Path> splits = null; 1131 1132 public OutputSink(PipelineController controller, EntryBuffers entryBuffers, int numWriters) { 1133 numThreads = numWriters; 1134 this.controller = controller; 1135 this.entryBuffers = entryBuffers; 1136 } 1137 1138 void setReporter(CancelableProgressable reporter) { 1139 this.reporter = reporter; 1140 } 1141 1142 /** 1143 * Start the threads that will pump data from the entryBuffers to the output files. 1144 */ 1145 public synchronized void startWriterThreads() { 1146 for (int i = 0; i < numThreads; i++) { 1147 WriterThread t = new WriterThread(controller, entryBuffers, this, i); 1148 t.start(); 1149 writerThreads.add(t); 1150 } 1151 } 1152 1153 /** 1154 * 1155 * Update region's maximum edit log SeqNum. 1156 */ 1157 void updateRegionMaximumEditLogSeqNum(Entry entry) { 1158 synchronized (regionMaximumEditLogSeqNum) { 1159 String regionName = Bytes.toString(entry.getKey().getEncodedRegionName()); 1160 Long currentMaxSeqNum = regionMaximumEditLogSeqNum.get(regionName); 1161 if (currentMaxSeqNum == null || entry.getKey().getSequenceId() > currentMaxSeqNum) { 1162 regionMaximumEditLogSeqNum.put(regionName, entry.getKey().getSequenceId()); 1163 } 1164 } 1165 } 1166 1167 /** 1168 * @return the number of currently opened writers 1169 */ 1170 int getNumOpenWriters() { 1171 return this.writers.size(); 1172 } 1173 1174 long getSkippedEdits() { 1175 return this.skippedEdits.get(); 1176 } 1177 1178 /** 1179 * Wait for writer threads to dump all info to the sink 1180 * @return true when there is no error 1181 * @throws IOException 1182 */ 1183 protected boolean finishWriting(boolean interrupt) throws IOException { 1184 LOG.debug("Waiting for split writer threads to finish"); 1185 boolean progress_failed = false; 1186 for (WriterThread t : writerThreads) { 1187 t.finish(); 1188 } 1189 if (interrupt) { 1190 for (WriterThread t : writerThreads) { 1191 t.interrupt(); // interrupt the writer threads. We are stopping now. 1192 } 1193 } 1194 1195 for (WriterThread t : writerThreads) { 1196 if (!progress_failed && reporter != null && !reporter.progress()) { 1197 progress_failed = true; 1198 } 1199 try { 1200 t.join(); 1201 } catch (InterruptedException ie) { 1202 IOException iie = new InterruptedIOException(); 1203 iie.initCause(ie); 1204 throw iie; 1205 } 1206 } 1207 controller.checkForErrors(); 1208 LOG.info("{} split writers finished; closing.", this.writerThreads.size()); 1209 return (!progress_failed); 1210 } 1211 1212 public abstract List<Path> finishWritingAndClose() throws IOException; 1213 1214 /** 1215 * @return a map from encoded region ID to the number of edits written out for that region. 1216 */ 1217 public abstract Map<byte[], Long> getOutputCounts(); 1218 1219 /** 1220 * @return number of regions we've recovered 1221 */ 1222 public abstract int getNumberOfRecoveredRegions(); 1223 1224 /** 1225 * @param buffer A WAL Edit Entry 1226 * @throws IOException 1227 */ 1228 public abstract void append(RegionEntryBuffer buffer) throws IOException; 1229 1230 /** 1231 * WriterThread call this function to help flush internal remaining edits in buffer before close 1232 * @return true when underlying sink has something to flush 1233 */ 1234 public boolean flush() throws IOException { 1235 return false; 1236 } 1237 1238 /** 1239 * Some WALEdit's contain only KV's for account on what happened to a region. 1240 * Not all sinks will want to get all of those edits. 1241 * 1242 * @return Return true if this sink wants to accept this region-level WALEdit. 1243 */ 1244 public abstract boolean keepRegionEvent(Entry entry); 1245 } 1246 1247 /** 1248 * Class that manages the output streams from the log splitting process. 1249 */ 1250 class LogRecoveredEditsOutputSink extends OutputSink { 1251 1252 public LogRecoveredEditsOutputSink(PipelineController controller, EntryBuffers entryBuffers, 1253 int numWriters) { 1254 // More threads could potentially write faster at the expense 1255 // of causing more disk seeks as the logs are split. 1256 // 3. After a certain setting (probably around 3) the 1257 // process will be bound on the reader in the current 1258 // implementation anyway. 1259 super(controller, entryBuffers, numWriters); 1260 } 1261 1262 /** 1263 * @return null if failed to report progress 1264 * @throws IOException 1265 */ 1266 @Override 1267 public List<Path> finishWritingAndClose() throws IOException { 1268 boolean isSuccessful = false; 1269 List<Path> result = null; 1270 try { 1271 isSuccessful = finishWriting(false); 1272 } finally { 1273 result = close(); 1274 List<IOException> thrown = closeLogWriters(null); 1275 if (CollectionUtils.isNotEmpty(thrown)) { 1276 throw MultipleIOException.createIOException(thrown); 1277 } 1278 } 1279 if (isSuccessful) { 1280 splits = result; 1281 } 1282 return splits; 1283 } 1284 1285 // delete the one with fewer wal entries 1286 private void deleteOneWithFewerEntries(WriterAndPath wap, Path dst) throws IOException { 1287 long dstMinLogSeqNum = -1L; 1288 try (WAL.Reader reader = walFactory.createReader(fs, dst)) { 1289 WAL.Entry entry = reader.next(); 1290 if (entry != null) { 1291 dstMinLogSeqNum = entry.getKey().getSequenceId(); 1292 } 1293 } catch (EOFException e) { 1294 LOG.debug("Got EOF when reading first WAL entry from {}, an empty or broken WAL file?", 1295 dst, e); 1296 } 1297 if (wap.minLogSeqNum < dstMinLogSeqNum) { 1298 LOG.warn("Found existing old edits file. It could be the result of a previous failed" 1299 + " split attempt or we have duplicated wal entries. Deleting " + dst + ", length=" 1300 + fs.getFileStatus(dst).getLen()); 1301 if (!fs.delete(dst, false)) { 1302 LOG.warn("Failed deleting of old {}", dst); 1303 throw new IOException("Failed deleting of old " + dst); 1304 } 1305 } else { 1306 LOG.warn("Found existing old edits file and we have less entries. Deleting " + wap.p 1307 + ", length=" + fs.getFileStatus(wap.p).getLen()); 1308 if (!fs.delete(wap.p, false)) { 1309 LOG.warn("Failed deleting of {}", wap.p); 1310 throw new IOException("Failed deleting of " + wap.p); 1311 } 1312 } 1313 } 1314 1315 /** 1316 * Close all of the output streams. 1317 * @return the list of paths written. 1318 */ 1319 List<Path> close() throws IOException { 1320 Preconditions.checkState(!closeAndCleanCompleted); 1321 1322 final List<Path> paths = new ArrayList<>(); 1323 final List<IOException> thrown = Lists.newArrayList(); 1324 ThreadPoolExecutor closeThreadPool = Threads 1325 .getBoundedCachedThreadPool(numThreads, 30L, TimeUnit.SECONDS, new ThreadFactory() { 1326 private int count = 1; 1327 1328 @Override public Thread newThread(Runnable r) { 1329 Thread t = new Thread(r, "split-log-closeStream-" + count++); 1330 return t; 1331 } 1332 }); 1333 CompletionService<Void> completionService = new ExecutorCompletionService<>(closeThreadPool); 1334 boolean progress_failed; 1335 try { 1336 progress_failed = executeCloseTask(completionService, thrown, paths); 1337 } catch (InterruptedException e) { 1338 IOException iie = new InterruptedIOException(); 1339 iie.initCause(e); 1340 throw iie; 1341 } catch (ExecutionException e) { 1342 throw new IOException(e.getCause()); 1343 } finally { 1344 closeThreadPool.shutdownNow(); 1345 } 1346 if (!thrown.isEmpty()) { 1347 throw MultipleIOException.createIOException(thrown); 1348 } 1349 writersClosed = true; 1350 closeAndCleanCompleted = true; 1351 if (progress_failed) { 1352 return null; 1353 } 1354 return paths; 1355 } 1356 1357 /** 1358 * @param completionService threadPool to execute the closing tasks 1359 * @param thrown store the exceptions 1360 * @param paths arrayList to store the paths written 1361 * @return if close tasks executed successful 1362 */ 1363 boolean executeCloseTask(CompletionService<Void> completionService, 1364 List<IOException> thrown, List<Path> paths) 1365 throws InterruptedException, ExecutionException { 1366 for (final Map.Entry<String, SinkWriter> writersEntry : writers.entrySet()) { 1367 if (LOG.isTraceEnabled()) { 1368 LOG.trace("Submitting close of " + ((WriterAndPath) writersEntry.getValue()).p); 1369 } 1370 completionService.submit(new Callable<Void>() { 1371 @Override public Void call() throws Exception { 1372 WriterAndPath wap = (WriterAndPath) writersEntry.getValue(); 1373 Path dst = closeWriter(writersEntry.getKey(), wap, thrown); 1374 paths.add(dst); 1375 return null; 1376 } 1377 }); 1378 } 1379 boolean progress_failed = false; 1380 for (int i = 0, n = this.writers.size(); i < n; i++) { 1381 Future<Void> future = completionService.take(); 1382 future.get(); 1383 if (!progress_failed && reporter != null && !reporter.progress()) { 1384 progress_failed = true; 1385 } 1386 } 1387 return progress_failed; 1388 } 1389 1390 Path closeWriter(String encodedRegionName, WriterAndPath wap, 1391 List<IOException> thrown) throws IOException{ 1392 if (LOG.isTraceEnabled()) { 1393 LOG.trace("Closing " + wap.p); 1394 } 1395 try { 1396 wap.w.close(); 1397 } catch (IOException ioe) { 1398 LOG.error("Couldn't close log at " + wap.p, ioe); 1399 thrown.add(ioe); 1400 return null; 1401 } 1402 if (LOG.isDebugEnabled()) { 1403 LOG.debug("Closed wap " + wap.p + " (wrote " + wap.editsWritten 1404 + " edits, skipped " + wap.editsSkipped + " edits in " 1405 + (wap.nanosSpent / 1000 / 1000) + "ms"); 1406 } 1407 if (wap.editsWritten == 0) { 1408 // just remove the empty recovered.edits file 1409 if (fs.exists(wap.p) && !fs.delete(wap.p, false)) { 1410 LOG.warn("Failed deleting empty " + wap.p); 1411 throw new IOException("Failed deleting empty " + wap.p); 1412 } 1413 return null; 1414 } 1415 1416 Path dst = getCompletedRecoveredEditsFilePath(wap.p, 1417 regionMaximumEditLogSeqNum.get(encodedRegionName)); 1418 try { 1419 if (!dst.equals(wap.p) && fs.exists(dst)) { 1420 deleteOneWithFewerEntries(wap, dst); 1421 } 1422 // Skip the unit tests which create a splitter that reads and 1423 // writes the data without touching disk. 1424 // TestHLogSplit#testThreading is an example. 1425 if (fs.exists(wap.p)) { 1426 if (!fs.rename(wap.p, dst)) { 1427 throw new IOException("Failed renaming " + wap.p + " to " + dst); 1428 } 1429 LOG.info("Rename " + wap.p + " to " + dst); 1430 } 1431 } catch (IOException ioe) { 1432 LOG.error("Couldn't rename " + wap.p + " to " + dst, ioe); 1433 thrown.add(ioe); 1434 return null; 1435 } 1436 return dst; 1437 } 1438 1439 private List<IOException> closeLogWriters(List<IOException> thrown) throws IOException { 1440 if (writersClosed) { 1441 return thrown; 1442 } 1443 if (thrown == null) { 1444 thrown = Lists.newArrayList(); 1445 } 1446 try { 1447 for (WriterThread t : writerThreads) { 1448 while (t.isAlive()) { 1449 t.shouldStop = true; 1450 t.interrupt(); 1451 try { 1452 t.join(10); 1453 } catch (InterruptedException e) { 1454 IOException iie = new InterruptedIOException(); 1455 iie.initCause(e); 1456 throw iie; 1457 } 1458 } 1459 } 1460 } finally { 1461 WriterAndPath wap = null; 1462 for (SinkWriter tmpWAP : writers.values()) { 1463 try { 1464 wap = (WriterAndPath) tmpWAP; 1465 wap.w.close(); 1466 } catch (IOException ioe) { 1467 LOG.error("Couldn't close log at " + wap.p, ioe); 1468 thrown.add(ioe); 1469 continue; 1470 } 1471 LOG.info( 1472 "Closed log " + wap.p + " (wrote " + wap.editsWritten + " edits in " + (wap.nanosSpent 1473 / 1000 / 1000) + "ms)"); 1474 } 1475 writersClosed = true; 1476 } 1477 1478 return thrown; 1479 } 1480 1481 /** 1482 * Get a writer and path for a log starting at the given entry. This function is threadsafe so 1483 * long as multiple threads are always acting on different regions. 1484 * @return null if this region shouldn't output any logs 1485 */ 1486 WriterAndPath getWriterAndPath(Entry entry, boolean reusable) throws IOException { 1487 byte region[] = entry.getKey().getEncodedRegionName(); 1488 String regionName = Bytes.toString(region); 1489 WriterAndPath ret = (WriterAndPath) writers.get(regionName); 1490 if (ret != null) { 1491 return ret; 1492 } 1493 // If we already decided that this region doesn't get any output 1494 // we don't need to check again. 1495 if (blacklistedRegions.contains(region)) { 1496 return null; 1497 } 1498 ret = createWAP(region, entry, rootDir); 1499 if (ret == null) { 1500 blacklistedRegions.add(region); 1501 return null; 1502 } 1503 if(reusable) { 1504 writers.put(regionName, ret); 1505 } 1506 return ret; 1507 } 1508 1509 /** 1510 * @return a path with a write for that path. caller should close. 1511 */ 1512 WriterAndPath createWAP(byte[] region, Entry entry, Path rootdir) throws IOException { 1513 Path regionedits = getRegionSplitEditsPath(fs, entry, rootdir, fileBeingSplit.getPath().getName()); 1514 if (regionedits == null) { 1515 return null; 1516 } 1517 if (fs.exists(regionedits)) { 1518 LOG.warn("Found old edits file. It could be the " 1519 + "result of a previous failed split attempt. Deleting " + regionedits + ", length=" 1520 + fs.getFileStatus(regionedits).getLen()); 1521 if (!fs.delete(regionedits, false)) { 1522 LOG.warn("Failed delete of old {}", regionedits); 1523 } 1524 } 1525 Writer w = createWriter(regionedits); 1526 LOG.debug("Creating writer path={}", regionedits); 1527 return new WriterAndPath(regionedits, w, entry.getKey().getSequenceId()); 1528 } 1529 1530 void filterCellByStore(Entry logEntry) { 1531 Map<byte[], Long> maxSeqIdInStores = 1532 regionMaxSeqIdInStores.get(Bytes.toString(logEntry.getKey().getEncodedRegionName())); 1533 if (MapUtils.isEmpty(maxSeqIdInStores)) { 1534 return; 1535 } 1536 // Create the array list for the cells that aren't filtered. 1537 // We make the assumption that most cells will be kept. 1538 ArrayList<Cell> keptCells = new ArrayList<>(logEntry.getEdit().getCells().size()); 1539 for (Cell cell : logEntry.getEdit().getCells()) { 1540 if (CellUtil.matchingFamily(cell, WALEdit.METAFAMILY)) { 1541 keptCells.add(cell); 1542 } else { 1543 byte[] family = CellUtil.cloneFamily(cell); 1544 Long maxSeqId = maxSeqIdInStores.get(family); 1545 // Do not skip cell even if maxSeqId is null. Maybe we are in a rolling upgrade, 1546 // or the master was crashed before and we can not get the information. 1547 if (maxSeqId == null || maxSeqId.longValue() < logEntry.getKey().getSequenceId()) { 1548 keptCells.add(cell); 1549 } 1550 } 1551 } 1552 1553 // Anything in the keptCells array list is still live. 1554 // So rather than removing the cells from the array list 1555 // which would be an O(n^2) operation, we just replace the list 1556 logEntry.getEdit().setCells(keptCells); 1557 } 1558 1559 @Override 1560 public void append(RegionEntryBuffer buffer) throws IOException { 1561 appendBuffer(buffer, true); 1562 } 1563 1564 WriterAndPath appendBuffer(RegionEntryBuffer buffer, boolean reusable) throws IOException{ 1565 List<Entry> entries = buffer.entryBuffer; 1566 if (entries.isEmpty()) { 1567 LOG.warn("got an empty buffer, skipping"); 1568 return null; 1569 } 1570 1571 WriterAndPath wap = null; 1572 1573 long startTime = System.nanoTime(); 1574 try { 1575 int editsCount = 0; 1576 1577 for (Entry logEntry : entries) { 1578 if (wap == null) { 1579 wap = getWriterAndPath(logEntry, reusable); 1580 if (wap == null) { 1581 if (LOG.isTraceEnabled()) { 1582 // This log spews the full edit. Can be massive in the log. Enable only debugging 1583 // WAL lost edit issues. 1584 LOG.trace("getWriterAndPath decided we don't need to write edits for {}", logEntry); 1585 } 1586 return null; 1587 } 1588 } 1589 filterCellByStore(logEntry); 1590 if (!logEntry.getEdit().isEmpty()) { 1591 wap.w.append(logEntry); 1592 this.updateRegionMaximumEditLogSeqNum(logEntry); 1593 editsCount++; 1594 } else { 1595 wap.incrementSkippedEdits(1); 1596 } 1597 } 1598 // Pass along summary statistics 1599 wap.incrementEdits(editsCount); 1600 wap.incrementNanoTime(System.nanoTime() - startTime); 1601 } catch (IOException e) { 1602 e = e instanceof RemoteException ? 1603 ((RemoteException)e).unwrapRemoteException() : e; 1604 LOG.error(HBaseMarkers.FATAL, "Got while writing log entry to log", e); 1605 throw e; 1606 } 1607 return wap; 1608 } 1609 1610 @Override 1611 public boolean keepRegionEvent(Entry entry) { 1612 ArrayList<Cell> cells = entry.getEdit().getCells(); 1613 for (Cell cell : cells) { 1614 if (WALEdit.isCompactionMarker(cell)) { 1615 return true; 1616 } 1617 } 1618 return false; 1619 } 1620 1621 /** 1622 * @return a map from encoded region ID to the number of edits written out for that region. 1623 */ 1624 @Override 1625 public Map<byte[], Long> getOutputCounts() { 1626 TreeMap<byte[], Long> ret = new TreeMap<>(Bytes.BYTES_COMPARATOR); 1627 for (Map.Entry<String, SinkWriter> entry : writers.entrySet()) { 1628 ret.put(Bytes.toBytes(entry.getKey()), entry.getValue().editsWritten); 1629 } 1630 return ret; 1631 } 1632 1633 @Override 1634 public int getNumberOfRecoveredRegions() { 1635 return writers.size(); 1636 } 1637 } 1638 1639 /** 1640 * 1641 */ 1642 class BoundedLogWriterCreationOutputSink extends LogRecoveredEditsOutputSink { 1643 1644 private ConcurrentHashMap<String, Long> regionRecoverStatMap = new ConcurrentHashMap<>(); 1645 1646 public BoundedLogWriterCreationOutputSink(PipelineController controller, 1647 EntryBuffers entryBuffers, int numWriters) { 1648 super(controller, entryBuffers, numWriters); 1649 } 1650 1651 @Override 1652 public List<Path> finishWritingAndClose() throws IOException { 1653 boolean isSuccessful; 1654 List<Path> result; 1655 try { 1656 isSuccessful = finishWriting(false); 1657 } finally { 1658 result = close(); 1659 } 1660 if (isSuccessful) { 1661 splits = result; 1662 } 1663 return splits; 1664 } 1665 1666 @Override 1667 boolean executeCloseTask(CompletionService<Void> completionService, 1668 List<IOException> thrown, List<Path> paths) 1669 throws InterruptedException, ExecutionException { 1670 for (final Map.Entry<byte[], RegionEntryBuffer> buffer : entryBuffers.buffers.entrySet()) { 1671 LOG.info("Submitting writeThenClose of {}", 1672 Arrays.toString(buffer.getValue().encodedRegionName)); 1673 completionService.submit(new Callable<Void>() { 1674 @Override 1675 public Void call() throws Exception { 1676 Path dst = writeThenClose(buffer.getValue()); 1677 paths.add(dst); 1678 return null; 1679 } 1680 }); 1681 } 1682 boolean progress_failed = false; 1683 for (int i = 0, n = entryBuffers.buffers.size(); i < n; i++) { 1684 Future<Void> future = completionService.take(); 1685 future.get(); 1686 if (!progress_failed && reporter != null && !reporter.progress()) { 1687 progress_failed = true; 1688 } 1689 } 1690 1691 return progress_failed; 1692 } 1693 1694 /** 1695 * since the splitting process may create multiple output files, we need a map 1696 * regionRecoverStatMap to track the output count of each region. 1697 * @return a map from encoded region ID to the number of edits written out for that region. 1698 */ 1699 @Override 1700 public Map<byte[], Long> getOutputCounts() { 1701 Map<byte[], Long> regionRecoverStatMapResult = new HashMap<>(); 1702 for(Map.Entry<String, Long> entry: regionRecoverStatMap.entrySet()){ 1703 regionRecoverStatMapResult.put(Bytes.toBytes(entry.getKey()), entry.getValue()); 1704 } 1705 return regionRecoverStatMapResult; 1706 } 1707 1708 /** 1709 * @return the number of recovered regions 1710 */ 1711 @Override 1712 public int getNumberOfRecoveredRegions() { 1713 return regionRecoverStatMap.size(); 1714 } 1715 1716 /** 1717 * Append the buffer to a new recovered edits file, then close it after all done 1718 * @param buffer contain all entries of a certain region 1719 * @throws IOException when closeWriter failed 1720 */ 1721 @Override 1722 public void append(RegionEntryBuffer buffer) throws IOException { 1723 writeThenClose(buffer); 1724 } 1725 1726 private Path writeThenClose(RegionEntryBuffer buffer) throws IOException { 1727 WriterAndPath wap = appendBuffer(buffer, false); 1728 if(wap != null) { 1729 String encodedRegionName = Bytes.toString(buffer.encodedRegionName); 1730 Long value = regionRecoverStatMap.putIfAbsent(encodedRegionName, wap.editsWritten); 1731 if (value != null) { 1732 Long newValue = regionRecoverStatMap.get(encodedRegionName) + wap.editsWritten; 1733 regionRecoverStatMap.put(encodedRegionName, newValue); 1734 } 1735 } 1736 1737 Path dst = null; 1738 List<IOException> thrown = new ArrayList<>(); 1739 if(wap != null){ 1740 dst = closeWriter(Bytes.toString(buffer.encodedRegionName), wap, thrown); 1741 } 1742 if (!thrown.isEmpty()) { 1743 throw MultipleIOException.createIOException(thrown); 1744 } 1745 return dst; 1746 } 1747 } 1748 1749 /** 1750 * Class wraps the actual writer which writes data out and related statistics 1751 */ 1752 public abstract static class SinkWriter { 1753 /* Count of edits written to this path */ 1754 long editsWritten = 0; 1755 /* Count of edits skipped to this path */ 1756 long editsSkipped = 0; 1757 /* Number of nanos spent writing to this log */ 1758 long nanosSpent = 0; 1759 1760 void incrementEdits(int edits) { 1761 editsWritten += edits; 1762 } 1763 1764 void incrementSkippedEdits(int skipped) { 1765 editsSkipped += skipped; 1766 } 1767 1768 void incrementNanoTime(long nanos) { 1769 nanosSpent += nanos; 1770 } 1771 } 1772 1773 /** 1774 * Private data structure that wraps a Writer and its Path, also collecting statistics about the 1775 * data written to this output. 1776 */ 1777 private final static class WriterAndPath extends SinkWriter { 1778 final Path p; 1779 final Writer w; 1780 final long minLogSeqNum; 1781 1782 WriterAndPath(final Path p, final Writer w, final long minLogSeqNum) { 1783 this.p = p; 1784 this.w = w; 1785 this.minLogSeqNum = minLogSeqNum; 1786 } 1787 } 1788 1789 static class CorruptedLogFileException extends Exception { 1790 private static final long serialVersionUID = 1L; 1791 1792 CorruptedLogFileException(String s) { 1793 super(s); 1794 } 1795 } 1796 1797 /** A struct used by getMutationsFromWALEntry */ 1798 public static class MutationReplay implements Comparable<MutationReplay> { 1799 public MutationReplay(MutationType type, Mutation mutation, long nonceGroup, long nonce) { 1800 this.type = type; 1801 this.mutation = mutation; 1802 if(this.mutation.getDurability() != Durability.SKIP_WAL) { 1803 // using ASYNC_WAL for relay 1804 this.mutation.setDurability(Durability.ASYNC_WAL); 1805 } 1806 this.nonceGroup = nonceGroup; 1807 this.nonce = nonce; 1808 } 1809 1810 public final MutationType type; 1811 public final Mutation mutation; 1812 public final long nonceGroup; 1813 public final long nonce; 1814 1815 @Override 1816 public int compareTo(final MutationReplay d) { 1817 return this.mutation.compareTo(d.mutation); 1818 } 1819 1820 @Override 1821 public boolean equals(Object obj) { 1822 if(!(obj instanceof MutationReplay)) { 1823 return false; 1824 } else { 1825 return this.compareTo((MutationReplay)obj) == 0; 1826 } 1827 } 1828 1829 @Override 1830 public int hashCode() { 1831 return this.mutation.hashCode(); 1832 } 1833 } 1834 1835 /** 1836 * This function is used to construct mutations from a WALEntry. It also 1837 * reconstructs WALKey & WALEdit from the passed in WALEntry 1838 * @param entry 1839 * @param cells 1840 * @param logEntry pair of WALKey and WALEdit instance stores WALKey and WALEdit instances 1841 * extracted from the passed in WALEntry. 1842 * @return list of Pair<MutationType, Mutation> to be replayed 1843 * @throws IOException 1844 */ 1845 public static List<MutationReplay> getMutationsFromWALEntry(WALEntry entry, CellScanner cells, 1846 Pair<WALKey, WALEdit> logEntry, Durability durability) throws IOException { 1847 if (entry == null) { 1848 // return an empty array 1849 return Collections.emptyList(); 1850 } 1851 1852 long replaySeqId = (entry.getKey().hasOrigSequenceNumber()) ? 1853 entry.getKey().getOrigSequenceNumber() : entry.getKey().getLogSequenceNumber(); 1854 int count = entry.getAssociatedCellCount(); 1855 List<MutationReplay> mutations = new ArrayList<>(); 1856 Cell previousCell = null; 1857 Mutation m = null; 1858 WALKeyImpl key = null; 1859 WALEdit val = null; 1860 if (logEntry != null) { 1861 val = new WALEdit(); 1862 } 1863 1864 for (int i = 0; i < count; i++) { 1865 // Throw index out of bounds if our cell count is off 1866 if (!cells.advance()) { 1867 throw new ArrayIndexOutOfBoundsException("Expected=" + count + ", index=" + i); 1868 } 1869 Cell cell = cells.current(); 1870 if (val != null) val.add(cell); 1871 1872 boolean isNewRowOrType = 1873 previousCell == null || previousCell.getTypeByte() != cell.getTypeByte() 1874 || !CellUtil.matchingRows(previousCell, cell); 1875 if (isNewRowOrType) { 1876 // Create new mutation 1877 if (CellUtil.isDelete(cell)) { 1878 m = new Delete(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength()); 1879 // Deletes don't have nonces. 1880 mutations.add(new MutationReplay( 1881 MutationType.DELETE, m, HConstants.NO_NONCE, HConstants.NO_NONCE)); 1882 } else { 1883 m = new Put(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength()); 1884 // Puts might come from increment or append, thus we need nonces. 1885 long nonceGroup = entry.getKey().hasNonceGroup() 1886 ? entry.getKey().getNonceGroup() : HConstants.NO_NONCE; 1887 long nonce = entry.getKey().hasNonce() ? entry.getKey().getNonce() : HConstants.NO_NONCE; 1888 mutations.add(new MutationReplay(MutationType.PUT, m, nonceGroup, nonce)); 1889 } 1890 } 1891 if (CellUtil.isDelete(cell)) { 1892 ((Delete) m).add(cell); 1893 } else { 1894 ((Put) m).add(cell); 1895 } 1896 m.setDurability(durability); 1897 previousCell = cell; 1898 } 1899 1900 // reconstruct WALKey 1901 if (logEntry != null) { 1902 org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.WALKey walKeyProto = 1903 entry.getKey(); 1904 List<UUID> clusterIds = new ArrayList<>(walKeyProto.getClusterIdsCount()); 1905 for (HBaseProtos.UUID uuid : entry.getKey().getClusterIdsList()) { 1906 clusterIds.add(new UUID(uuid.getMostSigBits(), uuid.getLeastSigBits())); 1907 } 1908 key = new WALKeyImpl(walKeyProto.getEncodedRegionName().toByteArray(), TableName.valueOf( 1909 walKeyProto.getTableName().toByteArray()), replaySeqId, walKeyProto.getWriteTime(), 1910 clusterIds, walKeyProto.getNonceGroup(), walKeyProto.getNonce(), null); 1911 logEntry.setFirst(key); 1912 logEntry.setSecond(val); 1913 } 1914 1915 return mutations; 1916 } 1917}