1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.wal;
20
21 import java.io.EOFException;
22 import java.io.FileNotFoundException;
23 import java.io.IOException;
24 import java.io.InterruptedIOException;
25 import java.text.ParseException;
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.HashSet;
29 import java.util.LinkedList;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.NavigableSet;
33 import java.util.Set;
34 import java.util.TreeMap;
35 import java.util.TreeSet;
36 import java.util.UUID;
37 import java.util.concurrent.Callable;
38 import java.util.concurrent.CompletionService;
39 import java.util.concurrent.ConcurrentHashMap;
40 import java.util.concurrent.ExecutionException;
41 import java.util.concurrent.ExecutorCompletionService;
42 import java.util.concurrent.Future;
43 import java.util.concurrent.ThreadFactory;
44 import java.util.concurrent.ThreadPoolExecutor;
45 import java.util.concurrent.TimeUnit;
46 import java.util.concurrent.atomic.AtomicBoolean;
47 import java.util.concurrent.atomic.AtomicLong;
48 import java.util.concurrent.atomic.AtomicReference;
49 import java.util.regex.Matcher;
50 import java.util.regex.Pattern;
51
52 import org.apache.commons.logging.Log;
53 import org.apache.commons.logging.LogFactory;
54 import org.apache.hadoop.conf.Configuration;
55 import org.apache.hadoop.fs.FileAlreadyExistsException;
56 import org.apache.hadoop.fs.FileStatus;
57 import org.apache.hadoop.fs.FileSystem;
58 import org.apache.hadoop.fs.Path;
59 import org.apache.hadoop.fs.PathFilter;
60 import org.apache.hadoop.hbase.Cell;
61 import org.apache.hadoop.hbase.CellScanner;
62 import org.apache.hadoop.hbase.CellUtil;
63 import org.apache.hadoop.hbase.CoordinatedStateException;
64 import org.apache.hadoop.hbase.CoordinatedStateManager;
65 import org.apache.hadoop.hbase.HBaseConfiguration;
66 import org.apache.hadoop.hbase.HConstants;
67 import org.apache.hadoop.hbase.HRegionInfo;
68 import org.apache.hadoop.hbase.HRegionLocation;
69 import org.apache.hadoop.hbase.RemoteExceptionHandler;
70 import org.apache.hadoop.hbase.ServerName;
71 import org.apache.hadoop.hbase.TableName;
72 import org.apache.hadoop.hbase.TableNotFoundException;
73 import org.apache.hadoop.hbase.TableStateManager;
74 import org.apache.hadoop.hbase.classification.InterfaceAudience;
75 import org.apache.hadoop.hbase.client.ConnectionUtils;
76 import org.apache.hadoop.hbase.client.Delete;
77 import org.apache.hadoop.hbase.client.Durability;
78 import org.apache.hadoop.hbase.client.HConnection;
79 import org.apache.hadoop.hbase.client.HConnectionManager;
80 import org.apache.hadoop.hbase.client.Mutation;
81 import org.apache.hadoop.hbase.client.Put;
82 import org.apache.hadoop.hbase.coordination.BaseCoordinatedStateManager;
83 import org.apache.hadoop.hbase.coordination.ZKSplitLogManagerCoordination;
84 import org.apache.hadoop.hbase.exceptions.RegionOpeningException;
85 import org.apache.hadoop.hbase.io.HeapSize;
86 import org.apache.hadoop.hbase.master.SplitLogManager;
87 import org.apache.hadoop.hbase.monitoring.MonitoredTask;
88 import org.apache.hadoop.hbase.monitoring.TaskMonitor;
89 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
90 import org.apache.hadoop.hbase.protobuf.RequestConverter;
91 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.AdminService.BlockingInterface;
92 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.GetRegionInfoRequest;
93 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.GetRegionInfoResponse;
94 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.WALEntry;
95 import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutationProto.MutationType;
96 import org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.RegionStoreSequenceIds;
97 import org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.StoreSequenceId;
98 import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
99 import org.apache.hadoop.hbase.protobuf.generated.WALProtos.CompactionDescriptor;
100 import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
101 import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos.SplitLogTask.RecoveryMode;
102 import org.apache.hadoop.hbase.regionserver.HRegion;
103 import org.apache.hadoop.hbase.regionserver.LastSequenceId;
104
105 import org.apache.hadoop.hbase.regionserver.wal.FSHLog;
106 import org.apache.hadoop.hbase.regionserver.wal.HLogKey;
107 import org.apache.hadoop.hbase.regionserver.wal.WALCellCodec;
108 import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
109 import org.apache.hadoop.hbase.regionserver.wal.WALEditsReplaySink;
110 import org.apache.hadoop.hbase.util.Bytes;
111 import org.apache.hadoop.hbase.util.CancelableProgressable;
112 import org.apache.hadoop.hbase.util.ClassSize;
113 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
114 import org.apache.hadoop.hbase.util.FSUtils;
115 import org.apache.hadoop.hbase.util.Pair;
116 import org.apache.hadoop.hbase.util.Threads;
117 import org.apache.hadoop.hbase.wal.WAL.Entry;
118 import org.apache.hadoop.hbase.wal.WAL.Reader;
119 import org.apache.hadoop.hbase.wal.WALProvider.Writer;
120 import org.apache.hadoop.hbase.zookeeper.ZKSplitLog;
121 import org.apache.hadoop.io.MultipleIOException;
122
123 import com.google.common.annotations.VisibleForTesting;
124 import com.google.common.base.Preconditions;
125 import com.google.common.collect.Lists;
126 import com.google.protobuf.ServiceException;
127 import com.google.protobuf.TextFormat;
128
129
130
131
132
133
134 @InterfaceAudience.Private
135 public class WALSplitter {
136 private static final Log LOG = LogFactory.getLog(WALSplitter.class);
137
138
139 public static final boolean SPLIT_SKIP_ERRORS_DEFAULT = false;
140
141
142 protected final Path rootDir;
143 protected final FileSystem fs;
144 protected final Configuration conf;
145
146
147
148 PipelineController controller;
149 OutputSink outputSink;
150 EntryBuffers entryBuffers;
151
152 private Set<TableName> disablingOrDisabledTables =
153 new HashSet<TableName>();
154 private BaseCoordinatedStateManager csm;
155 private final WALFactory walFactory;
156
157 private MonitoredTask status;
158
159
160 protected final LastSequenceId sequenceIdChecker;
161
162 protected boolean distributedLogReplay;
163
164
165 protected Map<String, Long> lastFlushedSequenceIds = new ConcurrentHashMap<String, Long>();
166
167
168 protected Map<String, Map<byte[], Long>> regionMaxSeqIdInStores =
169 new ConcurrentHashMap<String, Map<byte[], Long>>();
170
171
172 protected String failedServerName = "";
173
174
175 private final int numWriterThreads;
176
177
178 private final int minBatchSize;
179
180
181 private FileStatus fileBeingSplit;
182
183 @VisibleForTesting
184 WALSplitter(final WALFactory factory, Configuration conf, Path rootDir,
185 FileSystem fs, LastSequenceId idChecker,
186 CoordinatedStateManager csm, RecoveryMode mode) {
187 this.conf = HBaseConfiguration.create(conf);
188 String codecClassName = conf
189 .get(WALCellCodec.WAL_CELL_CODEC_CLASS_KEY, WALCellCodec.class.getName());
190 this.conf.set(HConstants.RPC_CODEC_CONF_KEY, codecClassName);
191 this.rootDir = rootDir;
192 this.fs = fs;
193 this.sequenceIdChecker = idChecker;
194 this.csm = (BaseCoordinatedStateManager)csm;
195 this.walFactory = factory;
196 this.controller = new PipelineController();
197
198 entryBuffers = new EntryBuffers(controller,
199 this.conf.getInt("hbase.regionserver.hlog.splitlog.buffersize",
200 128*1024*1024));
201
202
203
204 this.minBatchSize = this.conf.getInt("hbase.regionserver.wal.logreplay.batch.size", 64);
205 this.distributedLogReplay = (RecoveryMode.LOG_REPLAY == mode);
206
207 this.numWriterThreads = this.conf.getInt("hbase.regionserver.hlog.splitlog.writer.threads", 3);
208 if (csm != null && this.distributedLogReplay) {
209 outputSink = new LogReplayOutputSink(controller, entryBuffers, numWriterThreads);
210 } else {
211 if (this.distributedLogReplay) {
212 LOG.info("ZooKeeperWatcher is passed in as NULL so disable distrubitedLogRepaly.");
213 }
214 this.distributedLogReplay = false;
215 outputSink = new LogRecoveredEditsOutputSink(controller, entryBuffers, numWriterThreads);
216 }
217
218 }
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236 public static boolean splitLogFile(Path rootDir, FileStatus logfile, FileSystem fs,
237 Configuration conf, CancelableProgressable reporter, LastSequenceId idChecker,
238 CoordinatedStateManager cp, RecoveryMode mode, final WALFactory factory) throws IOException {
239 WALSplitter s = new WALSplitter(factory, conf, rootDir, fs, idChecker, cp, mode);
240 return s.splitLogFile(logfile, reporter);
241 }
242
243
244
245
246
247 public static List<Path> split(Path rootDir, Path logDir, Path oldLogDir,
248 FileSystem fs, Configuration conf, final WALFactory factory) throws IOException {
249 final FileStatus[] logfiles = SplitLogManager.getFileList(conf,
250 Collections.singletonList(logDir), null);
251 List<Path> splits = new ArrayList<Path>();
252 if (logfiles != null && logfiles.length > 0) {
253 for (FileStatus logfile: logfiles) {
254 WALSplitter s = new WALSplitter(factory, conf, rootDir, fs, null, null,
255 RecoveryMode.LOG_SPLITTING);
256 if (s.splitLogFile(logfile, null)) {
257 finishSplitLogFile(rootDir, oldLogDir, logfile.getPath(), conf);
258 if (s.outputSink.splits != null) {
259 splits.addAll(s.outputSink.splits);
260 }
261 }
262 }
263 }
264 if (!fs.delete(logDir, true)) {
265 throw new IOException("Unable to delete src dir: " + logDir);
266 }
267 return splits;
268 }
269
270
271
272
273
274 @VisibleForTesting
275 boolean splitLogFile(FileStatus logfile, CancelableProgressable reporter) throws IOException {
276 Preconditions.checkState(status == null);
277 Preconditions.checkArgument(logfile.isFile(),
278 "passed in file status is for something other than a regular file.");
279 boolean isCorrupted = false;
280 boolean skipErrors = conf.getBoolean("hbase.hlog.split.skip.errors",
281 SPLIT_SKIP_ERRORS_DEFAULT);
282 int interval = conf.getInt("hbase.splitlog.report.interval.loglines", 1024);
283 Path logPath = logfile.getPath();
284 boolean outputSinkStarted = false;
285 boolean progress_failed = false;
286 int editsCount = 0;
287 int editsSkipped = 0;
288
289 status =
290 TaskMonitor.get().createStatus(
291 "Splitting log file " + logfile.getPath() + "into a temporary staging area.");
292 Reader in = null;
293 this.fileBeingSplit = logfile;
294 try {
295 long logLength = logfile.getLen();
296 LOG.info("Splitting wal: " + logPath + ", length=" + logLength);
297 LOG.info("DistributedLogReplay = " + this.distributedLogReplay);
298 status.setStatus("Opening log file");
299 if (reporter != null && !reporter.progress()) {
300 progress_failed = true;
301 return false;
302 }
303 try {
304 in = getReader(logfile, skipErrors, reporter);
305 } catch (CorruptedLogFileException e) {
306 LOG.warn("Could not get reader, corrupted log file " + logPath, e);
307 ZKSplitLog.markCorrupted(rootDir, logfile.getPath().getName(), fs);
308 isCorrupted = true;
309 }
310 if (in == null) {
311 LOG.warn("Nothing to split in log file " + logPath);
312 return true;
313 }
314 if (csm != null) {
315 try {
316 TableStateManager tsm = csm.getTableStateManager();
317 disablingOrDisabledTables = tsm.getTablesInStates(
318 ZooKeeperProtos.Table.State.DISABLED, ZooKeeperProtos.Table.State.DISABLING);
319 } catch (CoordinatedStateException e) {
320 throw new IOException("Can't get disabling/disabled tables", e);
321 }
322 }
323 int numOpenedFilesBeforeReporting = conf.getInt("hbase.splitlog.report.openedfiles", 3);
324 int numOpenedFilesLastCheck = 0;
325 outputSink.setReporter(reporter);
326 outputSink.startWriterThreads();
327 outputSinkStarted = true;
328 Entry entry;
329 Long lastFlushedSequenceId = -1L;
330 ServerName serverName = DefaultWALProvider.getServerNameFromWALDirectoryName(logPath);
331 failedServerName = (serverName == null) ? "" : serverName.getServerName();
332 while ((entry = getNextLogLine(in, logPath, skipErrors)) != null) {
333 byte[] region = entry.getKey().getEncodedRegionName();
334 String encodedRegionNameAsStr = Bytes.toString(region);
335 lastFlushedSequenceId = lastFlushedSequenceIds.get(encodedRegionNameAsStr);
336 if (lastFlushedSequenceId == null) {
337 if (this.distributedLogReplay) {
338 RegionStoreSequenceIds ids =
339 csm.getSplitLogWorkerCoordination().getRegionFlushedSequenceId(failedServerName,
340 encodedRegionNameAsStr);
341 if (ids != null) {
342 lastFlushedSequenceId = ids.getLastFlushedSequenceId();
343 if (LOG.isDebugEnabled()) {
344 LOG.debug("DLR Last flushed sequenceid for " + encodedRegionNameAsStr + ": " +
345 TextFormat.shortDebugString(ids));
346 }
347 }
348 } else if (sequenceIdChecker != null) {
349 RegionStoreSequenceIds ids = sequenceIdChecker.getLastSequenceId(region);
350 Map<byte[], Long> maxSeqIdInStores = new TreeMap<byte[], Long>(Bytes.BYTES_COMPARATOR);
351 for (StoreSequenceId storeSeqId : ids.getStoreSequenceIdList()) {
352 maxSeqIdInStores.put(storeSeqId.getFamilyName().toByteArray(),
353 storeSeqId.getSequenceId());
354 }
355 regionMaxSeqIdInStores.put(encodedRegionNameAsStr, maxSeqIdInStores);
356 lastFlushedSequenceId = ids.getLastFlushedSequenceId();
357 if (LOG.isDebugEnabled()) {
358 LOG.debug("DLS Last flushed sequenceid for " + encodedRegionNameAsStr + ": " +
359 TextFormat.shortDebugString(ids));
360 }
361 }
362 if (lastFlushedSequenceId == null) {
363 lastFlushedSequenceId = -1L;
364 }
365 lastFlushedSequenceIds.put(encodedRegionNameAsStr, lastFlushedSequenceId);
366 }
367 if (lastFlushedSequenceId >= entry.getKey().getLogSeqNum()) {
368 editsSkipped++;
369 continue;
370 }
371
372 if (entry.getEdit().isMetaEdit() && !outputSink.keepRegionEvent(entry)) {
373 editsSkipped++;
374 continue;
375 }
376 entryBuffers.appendEntry(entry);
377 editsCount++;
378 int moreWritersFromLastCheck = this.getNumOpenWriters() - numOpenedFilesLastCheck;
379
380 if (editsCount % interval == 0
381 || moreWritersFromLastCheck > numOpenedFilesBeforeReporting) {
382 numOpenedFilesLastCheck = this.getNumOpenWriters();
383 String countsStr = (editsCount - (editsSkipped + outputSink.getSkippedEdits()))
384 + " edits, skipped " + editsSkipped + " edits.";
385 status.setStatus("Split " + countsStr);
386 if (reporter != null && !reporter.progress()) {
387 progress_failed = true;
388 return false;
389 }
390 }
391 }
392 } catch (InterruptedException ie) {
393 IOException iie = new InterruptedIOException();
394 iie.initCause(ie);
395 throw iie;
396 } catch (CorruptedLogFileException e) {
397 LOG.warn("Could not parse, corrupted log file " + logPath, e);
398 csm.getSplitLogWorkerCoordination().markCorrupted(rootDir,
399 logfile.getPath().getName(), fs);
400 isCorrupted = true;
401 } catch (IOException e) {
402 e = RemoteExceptionHandler.checkIOException(e);
403 throw e;
404 } finally {
405 LOG.debug("Finishing writing output logs and closing down.");
406 try {
407 if (null != in) {
408 in.close();
409 }
410 } catch (IOException exception) {
411 LOG.warn("Could not close wal reader: " + exception.getMessage());
412 LOG.debug("exception details", exception);
413 }
414 try {
415 if (outputSinkStarted) {
416
417
418 progress_failed = true;
419 progress_failed = outputSink.finishWritingAndClose() == null;
420 }
421 } finally {
422 String msg =
423 "Processed " + editsCount + " edits across " + outputSink.getNumberOfRecoveredRegions()
424 + " regions; edits skipped=" + editsSkipped + "; log file=" + logPath +
425 ", length=" + logfile.getLen() +
426 ", corrupted=" + isCorrupted + ", progress failed=" + progress_failed;
427 LOG.info(msg);
428 status.markComplete(msg);
429 }
430 }
431 return !progress_failed;
432 }
433
434
435
436
437
438
439
440
441
442
443
444
445 public static void finishSplitLogFile(String logfile,
446 Configuration conf) throws IOException {
447 Path rootdir = FSUtils.getRootDir(conf);
448 Path oldLogDir = new Path(rootdir, HConstants.HREGION_OLDLOGDIR_NAME);
449 Path logPath;
450 if (FSUtils.isStartingWithPath(rootdir, logfile)) {
451 logPath = new Path(logfile);
452 } else {
453 logPath = new Path(rootdir, logfile);
454 }
455 finishSplitLogFile(rootdir, oldLogDir, logPath, conf);
456 }
457
458 private static void finishSplitLogFile(Path rootdir, Path oldLogDir,
459 Path logPath, Configuration conf) throws IOException {
460 List<Path> processedLogs = new ArrayList<Path>();
461 List<Path> corruptedLogs = new ArrayList<Path>();
462 FileSystem fs;
463 fs = rootdir.getFileSystem(conf);
464 if (ZKSplitLog.isCorrupted(rootdir, logPath.getName(), fs)) {
465 corruptedLogs.add(logPath);
466 } else {
467 processedLogs.add(logPath);
468 }
469 archiveLogs(corruptedLogs, processedLogs, oldLogDir, fs, conf);
470 Path stagingDir = ZKSplitLog.getSplitLogDir(rootdir, logPath.getName());
471 fs.delete(stagingDir, true);
472 }
473
474
475
476
477
478
479
480
481
482
483
484
485
486 private static void archiveLogs(
487 final List<Path> corruptedLogs,
488 final List<Path> processedLogs, final Path oldLogDir,
489 final FileSystem fs, final Configuration conf) throws IOException {
490 final Path corruptDir = new Path(FSUtils.getRootDir(conf), conf.get(
491 "hbase.regionserver.hlog.splitlog.corrupt.dir", HConstants.CORRUPT_DIR_NAME));
492
493 if (!fs.mkdirs(corruptDir)) {
494 LOG.info("Unable to mkdir " + corruptDir);
495 }
496 fs.mkdirs(oldLogDir);
497
498
499
500 for (Path corrupted : corruptedLogs) {
501 Path p = new Path(corruptDir, corrupted.getName());
502 if (fs.exists(corrupted)) {
503 if (!fs.rename(corrupted, p)) {
504 LOG.warn("Unable to move corrupted log " + corrupted + " to " + p);
505 } else {
506 LOG.warn("Moved corrupted log " + corrupted + " to " + p);
507 }
508 }
509 }
510
511 for (Path p : processedLogs) {
512 Path newPath = FSHLog.getWALArchivePath(oldLogDir, p);
513 if (fs.exists(p)) {
514 if (!FSUtils.renameAndSetModifyTime(fs, p, newPath)) {
515 LOG.warn("Unable to move " + p + " to " + newPath);
516 } else {
517 LOG.info("Archived processed log " + p + " to " + newPath);
518 }
519 }
520 }
521 }
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536 @SuppressWarnings("deprecation")
537 @VisibleForTesting
538 static Path getRegionSplitEditsPath(final FileSystem fs,
539 final Entry logEntry, final Path rootDir, String fileBeingSplit)
540 throws IOException {
541 Path tableDir = FSUtils.getTableDir(rootDir, logEntry.getKey().getTablename());
542 String encodedRegionName = Bytes.toString(logEntry.getKey().getEncodedRegionName());
543 Path regiondir = HRegion.getRegionDir(tableDir, encodedRegionName);
544 Path dir = getRegionDirRecoveredEditsDir(regiondir);
545
546 if (!fs.exists(regiondir)) {
547 LOG.info("This region's directory doesn't exist: "
548 + regiondir.toString() + ". It is very likely that it was" +
549 " already split so it's safe to discard those edits.");
550 return null;
551 }
552 if (fs.exists(dir) && fs.isFile(dir)) {
553 Path tmp = new Path("/tmp");
554 if (!fs.exists(tmp)) {
555 fs.mkdirs(tmp);
556 }
557 tmp = new Path(tmp,
558 HConstants.RECOVERED_EDITS_DIR + "_" + encodedRegionName);
559 LOG.warn("Found existing old file: " + dir + ". It could be some "
560 + "leftover of an old installation. It should be a folder instead. "
561 + "So moving it to " + tmp);
562 if (!fs.rename(dir, tmp)) {
563 LOG.warn("Failed to sideline old file " + dir);
564 }
565 }
566
567 if (!fs.exists(dir) && !fs.mkdirs(dir)) {
568 LOG.warn("mkdir failed on " + dir);
569 }
570
571
572
573 String fileName = formatRecoveredEditsFileName(logEntry.getKey().getLogSeqNum());
574 fileName = getTmpRecoveredEditsFileName(fileName + "-" + fileBeingSplit);
575 return new Path(dir, fileName);
576 }
577
578 private static String getTmpRecoveredEditsFileName(String fileName) {
579 return fileName + RECOVERED_LOG_TMPFILE_SUFFIX;
580 }
581
582
583
584
585
586
587
588
589
590 private static Path getCompletedRecoveredEditsFilePath(Path srcPath,
591 long maximumEditLogSeqNum) {
592 String fileName = formatRecoveredEditsFileName(maximumEditLogSeqNum);
593 return new Path(srcPath.getParent(), fileName);
594 }
595
596 @VisibleForTesting
597 static String formatRecoveredEditsFileName(final long seqid) {
598 return String.format("%019d", seqid);
599 }
600
601 private static final Pattern EDITFILES_NAME_PATTERN = Pattern.compile("-?[0-9]+");
602 private static final String RECOVERED_LOG_TMPFILE_SUFFIX = ".temp";
603
604
605
606
607
608
609
610 public static Path getRegionDirRecoveredEditsDir(final Path regiondir) {
611 return new Path(regiondir, HConstants.RECOVERED_EDITS_DIR);
612 }
613
614
615
616
617
618
619
620
621
622
623 public static NavigableSet<Path> getSplitEditFilesSorted(final FileSystem fs,
624 final Path regiondir) throws IOException {
625 NavigableSet<Path> filesSorted = new TreeSet<Path>();
626 Path editsdir = getRegionDirRecoveredEditsDir(regiondir);
627 if (!fs.exists(editsdir))
628 return filesSorted;
629 FileStatus[] files = FSUtils.listStatus(fs, editsdir, new PathFilter() {
630 @Override
631 public boolean accept(Path p) {
632 boolean result = false;
633 try {
634
635
636
637
638 Matcher m = EDITFILES_NAME_PATTERN.matcher(p.getName());
639 result = fs.isFile(p) && m.matches();
640
641
642 if (p.getName().endsWith(RECOVERED_LOG_TMPFILE_SUFFIX)) {
643 result = false;
644 }
645
646 if (isSequenceIdFile(p)) {
647 result = false;
648 }
649 } catch (IOException e) {
650 LOG.warn("Failed isFile check on " + p);
651 }
652 return result;
653 }
654 });
655 if (files == null) {
656 return filesSorted;
657 }
658 for (FileStatus status : files) {
659 filesSorted.add(status.getPath());
660 }
661 return filesSorted;
662 }
663
664
665
666
667
668
669
670
671
672
673 public static Path moveAsideBadEditsFile(final FileSystem fs, final Path edits)
674 throws IOException {
675 Path moveAsideName = new Path(edits.getParent(), edits.getName() + "."
676 + System.currentTimeMillis());
677 if (!fs.rename(edits, moveAsideName)) {
678 LOG.warn("Rename failed from " + edits + " to " + moveAsideName);
679 }
680 return moveAsideName;
681 }
682
683 private static final String SEQUENCE_ID_FILE_SUFFIX = ".seqid";
684 private static final String OLD_SEQUENCE_ID_FILE_SUFFIX = "_seqid";
685 private static final int SEQUENCE_ID_FILE_SUFFIX_LENGTH = SEQUENCE_ID_FILE_SUFFIX.length();
686
687
688
689
690 @VisibleForTesting
691 public static boolean isSequenceIdFile(final Path file) {
692 return file.getName().endsWith(SEQUENCE_ID_FILE_SUFFIX)
693 || file.getName().endsWith(OLD_SEQUENCE_ID_FILE_SUFFIX);
694 }
695
696
697
698
699
700
701
702
703
704
705 public static long writeRegionSequenceIdFile(final FileSystem fs, final Path regiondir,
706 long newSeqId, long saftyBumper) throws IOException {
707
708 Path editsdir = WALSplitter.getRegionDirRecoveredEditsDir(regiondir);
709 long maxSeqId = 0;
710 FileStatus[] files = null;
711 if (fs.exists(editsdir)) {
712 files = FSUtils.listStatus(fs, editsdir, new PathFilter() {
713 @Override
714 public boolean accept(Path p) {
715 return isSequenceIdFile(p);
716 }
717 });
718 if (files != null) {
719 for (FileStatus status : files) {
720 String fileName = status.getPath().getName();
721 try {
722 Long tmpSeqId = Long.parseLong(fileName.substring(0, fileName.length()
723 - SEQUENCE_ID_FILE_SUFFIX_LENGTH));
724 maxSeqId = Math.max(tmpSeqId, maxSeqId);
725 } catch (NumberFormatException ex) {
726 LOG.warn("Invalid SeqId File Name=" + fileName);
727 }
728 }
729 }
730 }
731 if (maxSeqId > newSeqId) {
732 newSeqId = maxSeqId;
733 }
734 newSeqId += saftyBumper;
735
736
737 Path newSeqIdFile = new Path(editsdir, newSeqId + SEQUENCE_ID_FILE_SUFFIX);
738 if (newSeqId != maxSeqId) {
739 try {
740 if (!fs.createNewFile(newSeqIdFile) && !fs.exists(newSeqIdFile)) {
741 throw new IOException("Failed to create SeqId file:" + newSeqIdFile);
742 }
743 if (LOG.isDebugEnabled()) {
744 LOG.debug("Wrote region seqId=" + newSeqIdFile + " to file, newSeqId=" + newSeqId
745 + ", maxSeqId=" + maxSeqId);
746 }
747 } catch (FileAlreadyExistsException ignored) {
748
749 }
750 }
751
752 if (files != null) {
753 for (FileStatus status : files) {
754 if (newSeqIdFile.equals(status.getPath())) {
755 continue;
756 }
757 fs.delete(status.getPath(), false);
758 }
759 }
760 return newSeqId;
761 }
762
763
764
765
766
767
768
769
770
771 protected Reader getReader(FileStatus file, boolean skipErrors, CancelableProgressable reporter)
772 throws IOException, CorruptedLogFileException {
773 Path path = file.getPath();
774 long length = file.getLen();
775 Reader in;
776
777
778
779
780 if (length <= 0) {
781 LOG.warn("File " + path + " might be still open, length is 0");
782 }
783
784 try {
785 FSUtils.getInstance(fs, conf).recoverFileLease(fs, path, conf, reporter);
786 try {
787 in = getReader(path, reporter);
788 } catch (EOFException e) {
789 if (length <= 0) {
790
791
792
793
794
795 LOG.warn("Could not open " + path + " for reading. File is empty", e);
796 return null;
797 } else {
798
799 return null;
800 }
801 }
802 } catch (IOException e) {
803 if (e instanceof FileNotFoundException) {
804
805 LOG.warn("File " + path + " doesn't exist anymore.", e);
806 return null;
807 }
808 if (!skipErrors || e instanceof InterruptedIOException) {
809 throw e;
810 }
811 CorruptedLogFileException t =
812 new CorruptedLogFileException("skipErrors=true Could not open wal " +
813 path + " ignoring");
814 t.initCause(e);
815 throw t;
816 }
817 return in;
818 }
819
820 static private Entry getNextLogLine(Reader in, Path path, boolean skipErrors)
821 throws CorruptedLogFileException, IOException {
822 try {
823 return in.next();
824 } catch (EOFException eof) {
825
826 LOG.info("EOF from wal " + path + ". continuing");
827 return null;
828 } catch (IOException e) {
829
830
831 if (e.getCause() != null &&
832 (e.getCause() instanceof ParseException ||
833 e.getCause() instanceof org.apache.hadoop.fs.ChecksumException)) {
834 LOG.warn("Parse exception " + e.getCause().toString() + " from wal "
835 + path + ". continuing");
836 return null;
837 }
838 if (!skipErrors) {
839 throw e;
840 }
841 CorruptedLogFileException t =
842 new CorruptedLogFileException("skipErrors=true Ignoring exception" +
843 " while parsing wal " + path + ". Marking as corrupted");
844 t.initCause(e);
845 throw t;
846 }
847 }
848
849
850
851
852
853 protected Writer createWriter(Path logfile)
854 throws IOException {
855 return walFactory.createRecoveredEditsWriter(fs, logfile);
856 }
857
858
859
860
861
862 protected Reader getReader(Path curLogFile, CancelableProgressable reporter) throws IOException {
863 return walFactory.createReader(fs, curLogFile, reporter);
864 }
865
866
867
868
869 private int getNumOpenWriters() {
870 int result = 0;
871 if (this.outputSink != null) {
872 result += this.outputSink.getNumOpenWriters();
873 }
874 return result;
875 }
876
877
878
879
880 public static class PipelineController {
881
882
883 AtomicReference<Throwable> thrown = new AtomicReference<Throwable>();
884
885
886
887 public final Object dataAvailable = new Object();
888
889 void writerThreadError(Throwable t) {
890 thrown.compareAndSet(null, t);
891 }
892
893
894
895
896 void checkForErrors() throws IOException {
897 Throwable thrown = this.thrown.get();
898 if (thrown == null) return;
899 if (thrown instanceof IOException) {
900 throw new IOException(thrown);
901 } else {
902 throw new RuntimeException(thrown);
903 }
904 }
905 }
906
907
908
909
910
911
912
913
914 public static class EntryBuffers {
915 PipelineController controller;
916
917 Map<byte[], RegionEntryBuffer> buffers =
918 new TreeMap<byte[], RegionEntryBuffer>(Bytes.BYTES_COMPARATOR);
919
920
921
922
923 Set<byte[]> currentlyWriting = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
924
925 long totalBuffered = 0;
926 long maxHeapUsage;
927
928 public EntryBuffers(PipelineController controller, long maxHeapUsage) {
929 this.controller = controller;
930 this.maxHeapUsage = maxHeapUsage;
931 }
932
933
934
935
936
937
938
939
940 public void appendEntry(Entry entry) throws InterruptedException, IOException {
941 WALKey key = entry.getKey();
942
943 RegionEntryBuffer buffer;
944 long incrHeap;
945 synchronized (this) {
946 buffer = buffers.get(key.getEncodedRegionName());
947 if (buffer == null) {
948 buffer = new RegionEntryBuffer(key.getTablename(), key.getEncodedRegionName());
949 buffers.put(key.getEncodedRegionName(), buffer);
950 }
951 incrHeap= buffer.appendEntry(entry);
952 }
953
954
955 synchronized (controller.dataAvailable) {
956 totalBuffered += incrHeap;
957 while (totalBuffered > maxHeapUsage && controller.thrown.get() == null) {
958 LOG.debug("Used " + totalBuffered + " bytes of buffered edits, waiting for IO threads...");
959 controller.dataAvailable.wait(2000);
960 }
961 controller.dataAvailable.notifyAll();
962 }
963 controller.checkForErrors();
964 }
965
966
967
968
969 synchronized RegionEntryBuffer getChunkToWrite() {
970 long biggestSize = 0;
971 byte[] biggestBufferKey = null;
972
973 for (Map.Entry<byte[], RegionEntryBuffer> entry : buffers.entrySet()) {
974 long size = entry.getValue().heapSize();
975 if (size > biggestSize && (!currentlyWriting.contains(entry.getKey()))) {
976 biggestSize = size;
977 biggestBufferKey = entry.getKey();
978 }
979 }
980 if (biggestBufferKey == null) {
981 return null;
982 }
983
984 RegionEntryBuffer buffer = buffers.remove(biggestBufferKey);
985 currentlyWriting.add(biggestBufferKey);
986 return buffer;
987 }
988
989 void doneWriting(RegionEntryBuffer buffer) {
990 synchronized (this) {
991 boolean removed = currentlyWriting.remove(buffer.encodedRegionName);
992 assert removed;
993 }
994 long size = buffer.heapSize();
995
996 synchronized (controller.dataAvailable) {
997 totalBuffered -= size;
998
999 controller.dataAvailable.notifyAll();
1000 }
1001 }
1002
1003 synchronized boolean isRegionCurrentlyWriting(byte[] region) {
1004 return currentlyWriting.contains(region);
1005 }
1006
1007 public void waitUntilDrained() {
1008 synchronized (controller.dataAvailable) {
1009 while (totalBuffered > 0) {
1010 try {
1011 controller.dataAvailable.wait(2000);
1012 } catch (InterruptedException e) {
1013 LOG.warn("Got interrupted while waiting for EntryBuffers is drained");
1014 Thread.interrupted();
1015 break;
1016 }
1017 }
1018 }
1019 }
1020 }
1021
1022
1023
1024
1025
1026
1027
1028 public static class RegionEntryBuffer implements HeapSize {
1029 long heapInBuffer = 0;
1030 List<Entry> entryBuffer;
1031 TableName tableName;
1032 byte[] encodedRegionName;
1033
1034 RegionEntryBuffer(TableName tableName, byte[] region) {
1035 this.tableName = tableName;
1036 this.encodedRegionName = region;
1037 this.entryBuffer = new LinkedList<Entry>();
1038 }
1039
1040 long appendEntry(Entry entry) {
1041 internify(entry);
1042 entryBuffer.add(entry);
1043 long incrHeap = entry.getEdit().heapSize() +
1044 ClassSize.align(2 * ClassSize.REFERENCE) +
1045 0;
1046 heapInBuffer += incrHeap;
1047 return incrHeap;
1048 }
1049
1050 private void internify(Entry entry) {
1051 WALKey k = entry.getKey();
1052 k.internTableName(this.tableName);
1053 k.internEncodedRegionName(this.encodedRegionName);
1054 }
1055
1056 @Override
1057 public long heapSize() {
1058 return heapInBuffer;
1059 }
1060
1061 public byte[] getEncodedRegionName() {
1062 return encodedRegionName;
1063 }
1064
1065 public List<Entry> getEntryBuffer() {
1066 return entryBuffer;
1067 }
1068
1069 public TableName getTableName() {
1070 return tableName;
1071 }
1072 }
1073
1074 public static class WriterThread extends Thread {
1075 private volatile boolean shouldStop = false;
1076 private PipelineController controller;
1077 private EntryBuffers entryBuffers;
1078 private OutputSink outputSink = null;
1079
1080 WriterThread(PipelineController controller, EntryBuffers entryBuffers, OutputSink sink, int i){
1081 super(Thread.currentThread().getName() + "-Writer-" + i);
1082 this.controller = controller;
1083 this.entryBuffers = entryBuffers;
1084 outputSink = sink;
1085 }
1086
1087 @Override
1088 public void run() {
1089 try {
1090 doRun();
1091 } catch (Throwable t) {
1092 LOG.error("Exiting thread", t);
1093 controller.writerThreadError(t);
1094 }
1095 }
1096
1097 private void doRun() throws IOException {
1098 if (LOG.isTraceEnabled()) LOG.trace("Writer thread starting");
1099 while (true) {
1100 RegionEntryBuffer buffer = entryBuffers.getChunkToWrite();
1101 if (buffer == null) {
1102
1103 synchronized (controller.dataAvailable) {
1104 if (shouldStop && !this.outputSink.flush()) {
1105 return;
1106 }
1107 try {
1108 controller.dataAvailable.wait(500);
1109 } catch (InterruptedException ie) {
1110 if (!shouldStop) {
1111 throw new RuntimeException(ie);
1112 }
1113 }
1114 }
1115 continue;
1116 }
1117
1118 assert buffer != null;
1119 try {
1120 writeBuffer(buffer);
1121 } finally {
1122 entryBuffers.doneWriting(buffer);
1123 }
1124 }
1125 }
1126
1127 private void writeBuffer(RegionEntryBuffer buffer) throws IOException {
1128 outputSink.append(buffer);
1129 }
1130
1131 void finish() {
1132 synchronized (controller.dataAvailable) {
1133 shouldStop = true;
1134 controller.dataAvailable.notifyAll();
1135 }
1136 }
1137 }
1138
1139
1140
1141
1142
1143 public static abstract class OutputSink {
1144
1145 protected PipelineController controller;
1146 protected EntryBuffers entryBuffers;
1147
1148 protected Map<byte[], SinkWriter> writers = Collections
1149 .synchronizedMap(new TreeMap<byte[], SinkWriter>(Bytes.BYTES_COMPARATOR));;
1150
1151 protected final Map<byte[], Long> regionMaximumEditLogSeqNum = Collections
1152 .synchronizedMap(new TreeMap<byte[], Long>(Bytes.BYTES_COMPARATOR));
1153
1154 protected final List<WriterThread> writerThreads = Lists.newArrayList();
1155
1156
1157 protected final Set<byte[]> blacklistedRegions = Collections
1158 .synchronizedSet(new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR));
1159
1160 protected boolean closeAndCleanCompleted = false;
1161
1162 protected boolean writersClosed = false;
1163
1164 protected final int numThreads;
1165
1166 protected CancelableProgressable reporter = null;
1167
1168 protected AtomicLong skippedEdits = new AtomicLong();
1169
1170 protected List<Path> splits = null;
1171
1172 public OutputSink(PipelineController controller, EntryBuffers entryBuffers, int numWriters) {
1173 numThreads = numWriters;
1174 this.controller = controller;
1175 this.entryBuffers = entryBuffers;
1176 }
1177
1178 void setReporter(CancelableProgressable reporter) {
1179 this.reporter = reporter;
1180 }
1181
1182
1183
1184
1185 public synchronized void startWriterThreads() {
1186 for (int i = 0; i < numThreads; i++) {
1187 WriterThread t = new WriterThread(controller, entryBuffers, this, i);
1188 t.start();
1189 writerThreads.add(t);
1190 }
1191 }
1192
1193
1194
1195
1196
1197 void updateRegionMaximumEditLogSeqNum(Entry entry) {
1198 synchronized (regionMaximumEditLogSeqNum) {
1199 Long currentMaxSeqNum = regionMaximumEditLogSeqNum.get(entry.getKey()
1200 .getEncodedRegionName());
1201 if (currentMaxSeqNum == null || entry.getKey().getLogSeqNum() > currentMaxSeqNum) {
1202 regionMaximumEditLogSeqNum.put(entry.getKey().getEncodedRegionName(), entry.getKey()
1203 .getLogSeqNum());
1204 }
1205 }
1206 }
1207
1208 Long getRegionMaximumEditLogSeqNum(byte[] region) {
1209 return regionMaximumEditLogSeqNum.get(region);
1210 }
1211
1212
1213
1214
1215 int getNumOpenWriters() {
1216 return this.writers.size();
1217 }
1218
1219 long getSkippedEdits() {
1220 return this.skippedEdits.get();
1221 }
1222
1223
1224
1225
1226
1227
1228 protected boolean finishWriting(boolean interrupt) throws IOException {
1229 LOG.debug("Waiting for split writer threads to finish");
1230 boolean progress_failed = false;
1231 for (WriterThread t : writerThreads) {
1232 t.finish();
1233 }
1234 if (interrupt) {
1235 for (WriterThread t : writerThreads) {
1236 t.interrupt();
1237 }
1238 }
1239
1240 for (WriterThread t : writerThreads) {
1241 if (!progress_failed && reporter != null && !reporter.progress()) {
1242 progress_failed = true;
1243 }
1244 try {
1245 t.join();
1246 } catch (InterruptedException ie) {
1247 IOException iie = new InterruptedIOException();
1248 iie.initCause(ie);
1249 throw iie;
1250 }
1251 }
1252 controller.checkForErrors();
1253 LOG.info(this.writerThreads.size() + " split writers finished; closing...");
1254 return (!progress_failed);
1255 }
1256
1257 public abstract List<Path> finishWritingAndClose() throws IOException;
1258
1259
1260
1261
1262 public abstract Map<byte[], Long> getOutputCounts();
1263
1264
1265
1266
1267 public abstract int getNumberOfRecoveredRegions();
1268
1269
1270
1271
1272
1273 public abstract void append(RegionEntryBuffer buffer) throws IOException;
1274
1275
1276
1277
1278
1279 public boolean flush() throws IOException {
1280 return false;
1281 }
1282
1283
1284
1285
1286
1287
1288
1289 public abstract boolean keepRegionEvent(Entry entry);
1290 }
1291
1292
1293
1294
1295 class LogRecoveredEditsOutputSink extends OutputSink {
1296
1297 public LogRecoveredEditsOutputSink(PipelineController controller, EntryBuffers entryBuffers,
1298 int numWriters) {
1299
1300
1301
1302
1303
1304 super(controller, entryBuffers, numWriters);
1305 }
1306
1307
1308
1309
1310
1311 @Override
1312 public List<Path> finishWritingAndClose() throws IOException {
1313 boolean isSuccessful = false;
1314 List<Path> result = null;
1315 try {
1316 isSuccessful = finishWriting(false);
1317 } finally {
1318 result = close();
1319 List<IOException> thrown = closeLogWriters(null);
1320 if (thrown != null && !thrown.isEmpty()) {
1321 throw MultipleIOException.createIOException(thrown);
1322 }
1323 }
1324 if (isSuccessful) {
1325 splits = result;
1326 }
1327 return splits;
1328 }
1329
1330
1331 private void deleteOneWithFewerEntries(WriterAndPath wap, Path dst) throws IOException {
1332 long dstMinLogSeqNum = -1L;
1333 try (WAL.Reader reader = walFactory.createReader(fs, dst)) {
1334 WAL.Entry entry = reader.next();
1335 if (entry != null) {
1336 dstMinLogSeqNum = entry.getKey().getLogSeqNum();
1337 }
1338 } catch (EOFException e) {
1339 if (LOG.isDebugEnabled()) {
1340 LOG.debug(
1341 "Got EOF when reading first WAL entry from " + dst + ", an empty or broken WAL file?",
1342 e);
1343 }
1344 }
1345 if (wap.minLogSeqNum < dstMinLogSeqNum) {
1346 LOG.warn("Found existing old edits file. It could be the result of a previous failed"
1347 + " split attempt or we have duplicated wal entries. Deleting " + dst + ", length="
1348 + fs.getFileStatus(dst).getLen());
1349 if (!fs.delete(dst, false)) {
1350 LOG.warn("Failed deleting of old " + dst);
1351 throw new IOException("Failed deleting of old " + dst);
1352 }
1353 } else {
1354 LOG.warn("Found existing old edits file and we have less entries. Deleting " + wap.p
1355 + ", length=" + fs.getFileStatus(wap.p).getLen());
1356 if (!fs.delete(wap.p, false)) {
1357 LOG.warn("Failed deleting of " + wap.p);
1358 throw new IOException("Failed deleting of " + wap.p);
1359 }
1360 }
1361 }
1362
1363
1364
1365
1366
1367 private List<Path> close() throws IOException {
1368 Preconditions.checkState(!closeAndCleanCompleted);
1369
1370 final List<Path> paths = new ArrayList<Path>();
1371 final List<IOException> thrown = Lists.newArrayList();
1372 ThreadPoolExecutor closeThreadPool = Threads.getBoundedCachedThreadPool(numThreads, 30L,
1373 TimeUnit.SECONDS, new ThreadFactory() {
1374 private int count = 1;
1375
1376 @Override
1377 public Thread newThread(Runnable r) {
1378 Thread t = new Thread(r, "split-log-closeStream-" + count++);
1379 return t;
1380 }
1381 });
1382 CompletionService<Void> completionService =
1383 new ExecutorCompletionService<Void>(closeThreadPool);
1384 for (final Map.Entry<byte[], SinkWriter> writersEntry : writers.entrySet()) {
1385 if (LOG.isTraceEnabled()) {
1386 LOG.trace("Submitting close of " + ((WriterAndPath)writersEntry.getValue()).p);
1387 }
1388 completionService.submit(new Callable<Void>() {
1389 @Override
1390 public Void call() throws Exception {
1391 WriterAndPath wap = (WriterAndPath) writersEntry.getValue();
1392 if (LOG.isTraceEnabled()) LOG.trace("Closing " + wap.p);
1393 try {
1394 wap.w.close();
1395 } catch (IOException ioe) {
1396 LOG.error("Couldn't close log at " + wap.p, ioe);
1397 thrown.add(ioe);
1398 return null;
1399 }
1400 if (LOG.isDebugEnabled()) {
1401 LOG.debug("Closed wap " + wap.p + " (wrote " + wap.editsWritten
1402 + " edits, skipped " + wap.editsSkipped + " edits in "
1403 + (wap.nanosSpent / 1000 / 1000) + "ms");
1404 }
1405 if (wap.editsWritten == 0) {
1406
1407 if (fs.exists(wap.p) && !fs.delete(wap.p, false)) {
1408 LOG.warn("Failed deleting empty " + wap.p);
1409 throw new IOException("Failed deleting empty " + wap.p);
1410 }
1411 return null;
1412 }
1413
1414 Path dst = getCompletedRecoveredEditsFilePath(wap.p,
1415 regionMaximumEditLogSeqNum.get(writersEntry.getKey()));
1416 try {
1417 if (!dst.equals(wap.p) && fs.exists(dst)) {
1418 deleteOneWithFewerEntries(wap, dst);
1419 }
1420
1421
1422
1423 if (fs.exists(wap.p)) {
1424 if (!fs.rename(wap.p, dst)) {
1425 throw new IOException("Failed renaming " + wap.p + " to " + dst);
1426 }
1427 LOG.info("Rename " + wap.p + " to " + dst);
1428 }
1429 } catch (IOException ioe) {
1430 LOG.error("Couldn't rename " + wap.p + " to " + dst, ioe);
1431 thrown.add(ioe);
1432 return null;
1433 }
1434 paths.add(dst);
1435 return null;
1436 }
1437 });
1438 }
1439
1440 boolean progress_failed = false;
1441 try {
1442 for (int i = 0, n = this.writers.size(); i < n; i++) {
1443 Future<Void> future = completionService.take();
1444 future.get();
1445 if (!progress_failed && reporter != null && !reporter.progress()) {
1446 progress_failed = true;
1447 }
1448 }
1449 } catch (InterruptedException e) {
1450 IOException iie = new InterruptedIOException();
1451 iie.initCause(e);
1452 throw iie;
1453 } catch (ExecutionException e) {
1454 throw new IOException(e.getCause());
1455 } finally {
1456 closeThreadPool.shutdownNow();
1457 }
1458
1459 if (!thrown.isEmpty()) {
1460 throw MultipleIOException.createIOException(thrown);
1461 }
1462 writersClosed = true;
1463 closeAndCleanCompleted = true;
1464 if (progress_failed) {
1465 return null;
1466 }
1467 return paths;
1468 }
1469
1470 private List<IOException> closeLogWriters(List<IOException> thrown) throws IOException {
1471 if (writersClosed) {
1472 return thrown;
1473 }
1474
1475 if (thrown == null) {
1476 thrown = Lists.newArrayList();
1477 }
1478 try {
1479 for (WriterThread t : writerThreads) {
1480 while (t.isAlive()) {
1481 t.shouldStop = true;
1482 t.interrupt();
1483 try {
1484 t.join(10);
1485 } catch (InterruptedException e) {
1486 IOException iie = new InterruptedIOException();
1487 iie.initCause(e);
1488 throw iie;
1489 }
1490 }
1491 }
1492 } finally {
1493 synchronized (writers) {
1494 WriterAndPath wap = null;
1495 for (SinkWriter tmpWAP : writers.values()) {
1496 try {
1497 wap = (WriterAndPath) tmpWAP;
1498 wap.w.close();
1499 } catch (IOException ioe) {
1500 LOG.error("Couldn't close log at " + wap.p, ioe);
1501 thrown.add(ioe);
1502 continue;
1503 }
1504 LOG.info("Closed log " + wap.p + " (wrote " + wap.editsWritten + " edits in "
1505 + (wap.nanosSpent / 1000 / 1000) + "ms)");
1506 }
1507 }
1508 writersClosed = true;
1509 }
1510
1511 return thrown;
1512 }
1513
1514
1515
1516
1517
1518
1519 private WriterAndPath getWriterAndPath(Entry entry) throws IOException {
1520 byte region[] = entry.getKey().getEncodedRegionName();
1521 WriterAndPath ret = (WriterAndPath) writers.get(region);
1522 if (ret != null) {
1523 return ret;
1524 }
1525
1526
1527 if (blacklistedRegions.contains(region)) {
1528 return null;
1529 }
1530 ret = createWAP(region, entry, rootDir);
1531 if (ret == null) {
1532 blacklistedRegions.add(region);
1533 return null;
1534 }
1535 writers.put(region, ret);
1536 return ret;
1537 }
1538
1539
1540
1541
1542 private WriterAndPath createWAP(byte[] region, Entry entry, Path rootdir) throws IOException {
1543 Path regionedits = getRegionSplitEditsPath(fs, entry, rootdir,
1544 fileBeingSplit.getPath().getName());
1545 if (regionedits == null) {
1546 return null;
1547 }
1548 if (fs.exists(regionedits)) {
1549 LOG.warn("Found old edits file. It could be the "
1550 + "result of a previous failed split attempt. Deleting " + regionedits + ", length="
1551 + fs.getFileStatus(regionedits).getLen());
1552 if (!fs.delete(regionedits, false)) {
1553 LOG.warn("Failed delete of old " + regionedits);
1554 }
1555 }
1556 Writer w = createWriter(regionedits);
1557 LOG.debug("Creating writer path=" + regionedits);
1558 return new WriterAndPath(regionedits, w, entry.getKey().getLogSeqNum());
1559 }
1560
1561 private void filterCellByStore(Entry logEntry) {
1562 Map<byte[], Long> maxSeqIdInStores =
1563 regionMaxSeqIdInStores.get(Bytes.toString(logEntry.getKey().getEncodedRegionName()));
1564 if (maxSeqIdInStores == null || maxSeqIdInStores.isEmpty()) {
1565 return;
1566 }
1567
1568
1569 ArrayList<Cell> keptCells = new ArrayList<Cell>(logEntry.getEdit().getCells().size());
1570 for (Cell cell : logEntry.getEdit().getCells()) {
1571 if (CellUtil.matchingFamily(cell, WALEdit.METAFAMILY)) {
1572 keptCells.add(cell);
1573 } else {
1574 byte[] family = CellUtil.cloneFamily(cell);
1575 Long maxSeqId = maxSeqIdInStores.get(family);
1576
1577
1578 if (maxSeqId == null || maxSeqId.longValue() < logEntry.getKey().getLogSeqNum()) {
1579 keptCells.add(cell);
1580 }
1581 }
1582 }
1583
1584
1585
1586
1587 logEntry.getEdit().setCells(keptCells);
1588 }
1589
1590 @Override
1591 public void append(RegionEntryBuffer buffer) throws IOException {
1592 List<Entry> entries = buffer.entryBuffer;
1593 if (entries.isEmpty()) {
1594 LOG.warn("got an empty buffer, skipping");
1595 return;
1596 }
1597
1598 WriterAndPath wap = null;
1599
1600 long startTime = System.nanoTime();
1601 try {
1602 int editsCount = 0;
1603
1604 for (Entry logEntry : entries) {
1605 if (wap == null) {
1606 wap = getWriterAndPath(logEntry);
1607 if (wap == null) {
1608 if (LOG.isDebugEnabled()) {
1609 LOG.debug("getWriterAndPath decided we don't need to write edits for " + logEntry);
1610 }
1611 return;
1612 }
1613 }
1614 filterCellByStore(logEntry);
1615 if (!logEntry.getEdit().isEmpty()) {
1616 wap.w.append(logEntry);
1617 this.updateRegionMaximumEditLogSeqNum(logEntry);
1618 editsCount++;
1619 } else {
1620 wap.incrementSkippedEdits(1);
1621 }
1622 }
1623
1624 wap.incrementEdits(editsCount);
1625 wap.incrementNanoTime(System.nanoTime() - startTime);
1626 } catch (IOException e) {
1627 e = RemoteExceptionHandler.checkIOException(e);
1628 LOG.fatal(" Got while writing log entry to log", e);
1629 throw e;
1630 }
1631 }
1632
1633 @Override
1634 public boolean keepRegionEvent(Entry entry) {
1635 ArrayList<Cell> cells = entry.getEdit().getCells();
1636 for (int i = 0; i < cells.size(); i++) {
1637 if (WALEdit.isCompactionMarker(cells.get(i))) {
1638 return true;
1639 }
1640 }
1641 return false;
1642 }
1643
1644
1645
1646
1647 @Override
1648 public Map<byte[], Long> getOutputCounts() {
1649 TreeMap<byte[], Long> ret = new TreeMap<byte[], Long>(Bytes.BYTES_COMPARATOR);
1650 synchronized (writers) {
1651 for (Map.Entry<byte[], SinkWriter> entry : writers.entrySet()) {
1652 ret.put(entry.getKey(), entry.getValue().editsWritten);
1653 }
1654 }
1655 return ret;
1656 }
1657
1658 @Override
1659 public int getNumberOfRecoveredRegions() {
1660 return writers.size();
1661 }
1662 }
1663
1664
1665
1666
1667 public abstract static class SinkWriter {
1668
1669 long editsWritten = 0;
1670
1671 long editsSkipped = 0;
1672
1673 long nanosSpent = 0;
1674
1675 void incrementEdits(int edits) {
1676 editsWritten += edits;
1677 }
1678
1679 void incrementSkippedEdits(int skipped) {
1680 editsSkipped += skipped;
1681 }
1682
1683 void incrementNanoTime(long nanos) {
1684 nanosSpent += nanos;
1685 }
1686 }
1687
1688
1689
1690
1691
1692 private final static class WriterAndPath extends SinkWriter {
1693 final Path p;
1694 final Writer w;
1695 final long minLogSeqNum;
1696
1697 WriterAndPath(final Path p, final Writer w, final long minLogSeqNum) {
1698 this.p = p;
1699 this.w = w;
1700 this.minLogSeqNum = minLogSeqNum;
1701 }
1702 }
1703
1704
1705
1706
1707 class LogReplayOutputSink extends OutputSink {
1708 private static final double BUFFER_THRESHOLD = 0.35;
1709 private static final String KEY_DELIMITER = "#";
1710
1711 private long waitRegionOnlineTimeOut;
1712 private final Set<String> recoveredRegions = Collections.synchronizedSet(new HashSet<String>());
1713 private final Map<String, RegionServerWriter> writers =
1714 new ConcurrentHashMap<String, RegionServerWriter>();
1715
1716 private final Map<String, HRegionLocation> onlineRegions =
1717 new ConcurrentHashMap<String, HRegionLocation>();
1718
1719 private Map<TableName, HConnection> tableNameToHConnectionMap = Collections
1720 .synchronizedMap(new TreeMap<TableName, HConnection>());
1721
1722
1723
1724
1725 private Map<String, List<Pair<HRegionLocation, Entry>>> serverToBufferQueueMap =
1726 new ConcurrentHashMap<String, List<Pair<HRegionLocation, Entry>>>();
1727 private List<Throwable> thrown = new ArrayList<Throwable>();
1728
1729
1730
1731
1732
1733 private LogRecoveredEditsOutputSink logRecoveredEditsOutputSink;
1734 private boolean hasEditsInDisablingOrDisabledTables = false;
1735
1736 public LogReplayOutputSink(PipelineController controller, EntryBuffers entryBuffers,
1737 int numWriters) {
1738 super(controller, entryBuffers, numWriters);
1739 this.waitRegionOnlineTimeOut = conf.getInt(HConstants.HBASE_SPLITLOG_MANAGER_TIMEOUT,
1740 ZKSplitLogManagerCoordination.DEFAULT_TIMEOUT);
1741 this.logRecoveredEditsOutputSink = new LogRecoveredEditsOutputSink(controller,
1742 entryBuffers, numWriters);
1743 this.logRecoveredEditsOutputSink.setReporter(reporter);
1744 }
1745
1746 @Override
1747 public void append(RegionEntryBuffer buffer) throws IOException {
1748 List<Entry> entries = buffer.entryBuffer;
1749 if (entries.isEmpty()) {
1750 LOG.warn("got an empty buffer, skipping");
1751 return;
1752 }
1753
1754
1755 if (disablingOrDisabledTables.contains(buffer.tableName)) {
1756
1757 logRecoveredEditsOutputSink.append(buffer);
1758 hasEditsInDisablingOrDisabledTables = true;
1759
1760 addToRecoveredRegions(Bytes.toString(buffer.encodedRegionName));
1761 return;
1762 }
1763
1764
1765 groupEditsByServer(entries);
1766
1767
1768 String maxLocKey = null;
1769 int maxSize = 0;
1770 List<Pair<HRegionLocation, Entry>> maxQueue = null;
1771 synchronized (this.serverToBufferQueueMap) {
1772 for (String key : this.serverToBufferQueueMap.keySet()) {
1773 List<Pair<HRegionLocation, Entry>> curQueue = this.serverToBufferQueueMap.get(key);
1774 if (curQueue.size() > maxSize) {
1775 maxSize = curQueue.size();
1776 maxQueue = curQueue;
1777 maxLocKey = key;
1778 }
1779 }
1780 if (maxSize < minBatchSize
1781 && entryBuffers.totalBuffered < BUFFER_THRESHOLD * entryBuffers.maxHeapUsage) {
1782
1783 return;
1784 } else if (maxSize > 0) {
1785 this.serverToBufferQueueMap.remove(maxLocKey);
1786 }
1787 }
1788
1789 if (maxSize > 0) {
1790 processWorkItems(maxLocKey, maxQueue);
1791 }
1792 }
1793
1794 private void addToRecoveredRegions(String encodedRegionName) {
1795 if (!recoveredRegions.contains(encodedRegionName)) {
1796 recoveredRegions.add(encodedRegionName);
1797 }
1798 }
1799
1800
1801
1802
1803
1804 private void groupEditsByServer(List<Entry> entries) throws IOException {
1805 Set<TableName> nonExistentTables = null;
1806 Long cachedLastFlushedSequenceId = -1l;
1807 for (Entry entry : entries) {
1808 WALEdit edit = entry.getEdit();
1809 TableName table = entry.getKey().getTablename();
1810
1811 entry.getKey().setScopes(null);
1812 String encodeRegionNameStr = Bytes.toString(entry.getKey().getEncodedRegionName());
1813
1814 if (nonExistentTables != null && nonExistentTables.contains(table)) {
1815 this.skippedEdits.incrementAndGet();
1816 continue;
1817 }
1818
1819 Map<byte[], Long> maxStoreSequenceIds = null;
1820 boolean needSkip = false;
1821 HRegionLocation loc = null;
1822 String locKey = null;
1823 List<Cell> cells = edit.getCells();
1824 List<Cell> skippedCells = new ArrayList<Cell>();
1825 HConnection hconn = this.getConnectionByTableName(table);
1826
1827 for (Cell cell : cells) {
1828 byte[] row = CellUtil.cloneRow(cell);
1829 byte[] family = CellUtil.cloneFamily(cell);
1830 boolean isCompactionEntry = false;
1831 if (CellUtil.matchingFamily(cell, WALEdit.METAFAMILY)) {
1832 CompactionDescriptor compaction = WALEdit.getCompaction(cell);
1833 if (compaction != null && compaction.hasRegionName()) {
1834 try {
1835 byte[][] regionName = HRegionInfo.parseRegionName(compaction.getRegionName()
1836 .toByteArray());
1837 row = regionName[1];
1838 family = compaction.getFamilyName().toByteArray();
1839 isCompactionEntry = true;
1840 } catch (Exception ex) {
1841 LOG.warn("Unexpected exception received, ignoring " + ex);
1842 skippedCells.add(cell);
1843 continue;
1844 }
1845 } else {
1846 skippedCells.add(cell);
1847 continue;
1848 }
1849 }
1850
1851 try {
1852 loc =
1853 locateRegionAndRefreshLastFlushedSequenceId(hconn, table, row,
1854 encodeRegionNameStr);
1855
1856 if (isCompactionEntry && !encodeRegionNameStr.equalsIgnoreCase(
1857 loc.getRegionInfo().getEncodedName())) {
1858 LOG.info("Not replaying a compaction marker for an older region: "
1859 + encodeRegionNameStr);
1860 needSkip = true;
1861 }
1862 } catch (TableNotFoundException ex) {
1863
1864 LOG.info("Table " + table + " doesn't exist. Skip log replay for region "
1865 + encodeRegionNameStr);
1866 lastFlushedSequenceIds.put(encodeRegionNameStr, Long.MAX_VALUE);
1867 if (nonExistentTables == null) {
1868 nonExistentTables = new TreeSet<TableName>();
1869 }
1870 nonExistentTables.add(table);
1871 this.skippedEdits.incrementAndGet();
1872 needSkip = true;
1873 break;
1874 }
1875
1876 cachedLastFlushedSequenceId =
1877 lastFlushedSequenceIds.get(loc.getRegionInfo().getEncodedName());
1878 if (cachedLastFlushedSequenceId != null
1879 && cachedLastFlushedSequenceId >= entry.getKey().getLogSeqNum()) {
1880
1881 this.skippedEdits.incrementAndGet();
1882 needSkip = true;
1883 break;
1884 } else {
1885 if (maxStoreSequenceIds == null) {
1886 maxStoreSequenceIds =
1887 regionMaxSeqIdInStores.get(loc.getRegionInfo().getEncodedName());
1888 }
1889 if (maxStoreSequenceIds != null) {
1890 Long maxStoreSeqId = maxStoreSequenceIds.get(family);
1891 if (maxStoreSeqId == null || maxStoreSeqId >= entry.getKey().getLogSeqNum()) {
1892
1893 skippedCells.add(cell);
1894 continue;
1895 }
1896 }
1897 }
1898 }
1899
1900
1901 if (loc == null || needSkip) continue;
1902
1903 if (!skippedCells.isEmpty()) {
1904 cells.removeAll(skippedCells);
1905 }
1906
1907 synchronized (serverToBufferQueueMap) {
1908 locKey = loc.getHostnamePort() + KEY_DELIMITER + table;
1909 List<Pair<HRegionLocation, Entry>> queue = serverToBufferQueueMap.get(locKey);
1910 if (queue == null) {
1911 queue =
1912 Collections.synchronizedList(new ArrayList<Pair<HRegionLocation, Entry>>());
1913 serverToBufferQueueMap.put(locKey, queue);
1914 }
1915 queue.add(new Pair<HRegionLocation, Entry>(loc, entry));
1916 }
1917
1918 addToRecoveredRegions(loc.getRegionInfo().getEncodedName());
1919 }
1920 }
1921
1922
1923
1924
1925
1926
1927 private HRegionLocation locateRegionAndRefreshLastFlushedSequenceId(HConnection hconn,
1928 TableName table, byte[] row, String originalEncodedRegionName) throws IOException {
1929
1930 HRegionLocation loc = onlineRegions.get(originalEncodedRegionName);
1931 if(loc != null) return loc;
1932
1933 loc = hconn.getRegionLocation(table, row, true);
1934 if (loc == null) {
1935 throw new IOException("Can't locate location for row:" + Bytes.toString(row)
1936 + " of table:" + table);
1937 }
1938
1939 if (!originalEncodedRegionName.equalsIgnoreCase(loc.getRegionInfo().getEncodedName())) {
1940
1941 lastFlushedSequenceIds.put(originalEncodedRegionName, Long.MAX_VALUE);
1942 HRegionLocation tmpLoc = onlineRegions.get(loc.getRegionInfo().getEncodedName());
1943 if (tmpLoc != null) return tmpLoc;
1944 }
1945
1946 Long lastFlushedSequenceId = -1l;
1947 AtomicBoolean isRecovering = new AtomicBoolean(true);
1948 loc = waitUntilRegionOnline(loc, row, this.waitRegionOnlineTimeOut, isRecovering);
1949 if (!isRecovering.get()) {
1950
1951
1952 lastFlushedSequenceIds.put(loc.getRegionInfo().getEncodedName(), Long.MAX_VALUE);
1953 LOG.info("logReplay skip region: " + loc.getRegionInfo().getEncodedName()
1954 + " because it's not in recovering.");
1955 } else {
1956 Long cachedLastFlushedSequenceId =
1957 lastFlushedSequenceIds.get(loc.getRegionInfo().getEncodedName());
1958
1959
1960
1961 RegionStoreSequenceIds ids =
1962 csm.getSplitLogWorkerCoordination().getRegionFlushedSequenceId(failedServerName,
1963 loc.getRegionInfo().getEncodedName());
1964 if (ids != null) {
1965 lastFlushedSequenceId = ids.getLastFlushedSequenceId();
1966 Map<byte[], Long> storeIds = new TreeMap<byte[], Long>(Bytes.BYTES_COMPARATOR);
1967 List<StoreSequenceId> maxSeqIdInStores = ids.getStoreSequenceIdList();
1968 for (StoreSequenceId id : maxSeqIdInStores) {
1969 storeIds.put(id.getFamilyName().toByteArray(), id.getSequenceId());
1970 }
1971 regionMaxSeqIdInStores.put(loc.getRegionInfo().getEncodedName(), storeIds);
1972 }
1973
1974 if (cachedLastFlushedSequenceId == null
1975 || lastFlushedSequenceId > cachedLastFlushedSequenceId) {
1976 lastFlushedSequenceIds.put(loc.getRegionInfo().getEncodedName(), lastFlushedSequenceId);
1977 }
1978 }
1979
1980 onlineRegions.put(loc.getRegionInfo().getEncodedName(), loc);
1981 return loc;
1982 }
1983
1984 private void processWorkItems(String key, List<Pair<HRegionLocation, Entry>> actions)
1985 throws IOException {
1986 RegionServerWriter rsw = null;
1987
1988 long startTime = System.nanoTime();
1989 try {
1990 rsw = getRegionServerWriter(key);
1991 rsw.sink.replayEntries(actions);
1992
1993
1994 rsw.incrementEdits(actions.size());
1995 rsw.incrementNanoTime(System.nanoTime() - startTime);
1996 } catch (IOException e) {
1997 e = RemoteExceptionHandler.checkIOException(e);
1998 LOG.fatal(" Got while writing log entry to log", e);
1999 throw e;
2000 }
2001 }
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012 private HRegionLocation waitUntilRegionOnline(HRegionLocation loc, byte[] row,
2013 final long timeout, AtomicBoolean isRecovering)
2014 throws IOException {
2015 final long endTime = EnvironmentEdgeManager.currentTime() + timeout;
2016 final long pause = conf.getLong(HConstants.HBASE_CLIENT_PAUSE,
2017 HConstants.DEFAULT_HBASE_CLIENT_PAUSE);
2018 boolean reloadLocation = false;
2019 TableName tableName = loc.getRegionInfo().getTable();
2020 int tries = 0;
2021 Throwable cause = null;
2022 while (endTime > EnvironmentEdgeManager.currentTime()) {
2023 try {
2024
2025 HConnection hconn = getConnectionByTableName(tableName);
2026 if(reloadLocation) {
2027 loc = hconn.getRegionLocation(tableName, row, true);
2028 }
2029 BlockingInterface remoteSvr = hconn.getAdmin(loc.getServerName());
2030 HRegionInfo region = loc.getRegionInfo();
2031 try {
2032 GetRegionInfoRequest request =
2033 RequestConverter.buildGetRegionInfoRequest(region.getRegionName());
2034 GetRegionInfoResponse response = remoteSvr.getRegionInfo(null, request);
2035 if (HRegionInfo.convert(response.getRegionInfo()) != null) {
2036 isRecovering.set((response.hasIsRecovering()) ? response.getIsRecovering() : true);
2037 return loc;
2038 }
2039 } catch (ServiceException se) {
2040 throw ProtobufUtil.getRemoteException(se);
2041 }
2042 } catch (IOException e) {
2043 cause = e.getCause();
2044 if(!(cause instanceof RegionOpeningException)) {
2045 reloadLocation = true;
2046 }
2047 }
2048 long expectedSleep = ConnectionUtils.getPauseTime(pause, tries);
2049 try {
2050 Thread.sleep(expectedSleep);
2051 } catch (InterruptedException e) {
2052 throw new IOException("Interrupted when waiting region " +
2053 loc.getRegionInfo().getEncodedName() + " online.", e);
2054 }
2055 tries++;
2056 }
2057
2058 throw new IOException("Timeout when waiting region " + loc.getRegionInfo().getEncodedName() +
2059 " online for " + timeout + " milliseconds.", cause);
2060 }
2061
2062 @Override
2063 public boolean flush() throws IOException {
2064 String curLoc = null;
2065 int curSize = 0;
2066 List<Pair<HRegionLocation, Entry>> curQueue = null;
2067 synchronized (this.serverToBufferQueueMap) {
2068 for (String locationKey : this.serverToBufferQueueMap.keySet()) {
2069 curQueue = this.serverToBufferQueueMap.get(locationKey);
2070 if (!curQueue.isEmpty()) {
2071 curSize = curQueue.size();
2072 curLoc = locationKey;
2073 break;
2074 }
2075 }
2076 if (curSize > 0) {
2077 this.serverToBufferQueueMap.remove(curLoc);
2078 }
2079 }
2080
2081 if (curSize > 0) {
2082 this.processWorkItems(curLoc, curQueue);
2083
2084 synchronized(controller.dataAvailable) {
2085 controller.dataAvailable.notifyAll();
2086 }
2087 return true;
2088 }
2089 return false;
2090 }
2091
2092 @Override
2093 public boolean keepRegionEvent(Entry entry) {
2094 return true;
2095 }
2096
2097 void addWriterError(Throwable t) {
2098 thrown.add(t);
2099 }
2100
2101 @Override
2102 public List<Path> finishWritingAndClose() throws IOException {
2103 try {
2104 if (!finishWriting(false)) {
2105 return null;
2106 }
2107 if (hasEditsInDisablingOrDisabledTables) {
2108 splits = logRecoveredEditsOutputSink.finishWritingAndClose();
2109 } else {
2110 splits = new ArrayList<Path>();
2111 }
2112
2113 return splits;
2114 } finally {
2115 List<IOException> thrown = closeRegionServerWriters();
2116 if (thrown != null && !thrown.isEmpty()) {
2117 throw MultipleIOException.createIOException(thrown);
2118 }
2119 }
2120 }
2121
2122 @Override
2123 int getNumOpenWriters() {
2124 return this.writers.size() + this.logRecoveredEditsOutputSink.getNumOpenWriters();
2125 }
2126
2127 private List<IOException> closeRegionServerWriters() throws IOException {
2128 List<IOException> result = null;
2129 if (!writersClosed) {
2130 result = Lists.newArrayList();
2131 try {
2132 for (WriterThread t : writerThreads) {
2133 while (t.isAlive()) {
2134 t.shouldStop = true;
2135 t.interrupt();
2136 try {
2137 t.join(10);
2138 } catch (InterruptedException e) {
2139 IOException iie = new InterruptedIOException();
2140 iie.initCause(e);
2141 throw iie;
2142 }
2143 }
2144 }
2145 } finally {
2146 synchronized (writers) {
2147 for (String locationKey : writers.keySet()) {
2148 RegionServerWriter tmpW = writers.get(locationKey);
2149 try {
2150 tmpW.close();
2151 } catch (IOException ioe) {
2152 LOG.error("Couldn't close writer for region server:" + locationKey, ioe);
2153 result.add(ioe);
2154 }
2155 }
2156 }
2157
2158
2159 synchronized (this.tableNameToHConnectionMap) {
2160 for (TableName tableName : this.tableNameToHConnectionMap.keySet()) {
2161 HConnection hconn = this.tableNameToHConnectionMap.get(tableName);
2162 try {
2163 hconn.clearRegionCache();
2164 hconn.close();
2165 } catch (IOException ioe) {
2166 result.add(ioe);
2167 }
2168 }
2169 }
2170 writersClosed = true;
2171 }
2172 }
2173 return result;
2174 }
2175
2176 @Override
2177 public Map<byte[], Long> getOutputCounts() {
2178 TreeMap<byte[], Long> ret = new TreeMap<byte[], Long>(Bytes.BYTES_COMPARATOR);
2179 synchronized (writers) {
2180 for (Map.Entry<String, RegionServerWriter> entry : writers.entrySet()) {
2181 ret.put(Bytes.toBytes(entry.getKey()), entry.getValue().editsWritten);
2182 }
2183 }
2184 return ret;
2185 }
2186
2187 @Override
2188 public int getNumberOfRecoveredRegions() {
2189 return this.recoveredRegions.size();
2190 }
2191
2192
2193
2194
2195
2196
2197 private RegionServerWriter getRegionServerWriter(String loc) throws IOException {
2198 RegionServerWriter ret = writers.get(loc);
2199 if (ret != null) {
2200 return ret;
2201 }
2202
2203 TableName tableName = getTableFromLocationStr(loc);
2204 if(tableName == null){
2205 throw new IOException("Invalid location string:" + loc + " found. Replay aborted.");
2206 }
2207
2208 HConnection hconn = getConnectionByTableName(tableName);
2209 synchronized (writers) {
2210 ret = writers.get(loc);
2211 if (ret == null) {
2212 ret = new RegionServerWriter(conf, tableName, hconn);
2213 writers.put(loc, ret);
2214 }
2215 }
2216 return ret;
2217 }
2218
2219 private HConnection getConnectionByTableName(final TableName tableName) throws IOException {
2220 HConnection hconn = this.tableNameToHConnectionMap.get(tableName);
2221 if (hconn == null) {
2222 synchronized (this.tableNameToHConnectionMap) {
2223 hconn = this.tableNameToHConnectionMap.get(tableName);
2224 if (hconn == null) {
2225 hconn = HConnectionManager.getConnection(conf);
2226 this.tableNameToHConnectionMap.put(tableName, hconn);
2227 }
2228 }
2229 }
2230 return hconn;
2231 }
2232 private TableName getTableFromLocationStr(String loc) {
2233
2234
2235
2236 String[] splits = loc.split(KEY_DELIMITER);
2237 if (splits.length != 2) {
2238 return null;
2239 }
2240 return TableName.valueOf(splits[1]);
2241 }
2242 }
2243
2244
2245
2246
2247
2248 private final static class RegionServerWriter extends SinkWriter {
2249 final WALEditsReplaySink sink;
2250
2251 RegionServerWriter(final Configuration conf, final TableName tableName, final HConnection conn)
2252 throws IOException {
2253 this.sink = new WALEditsReplaySink(conf, tableName, conn);
2254 }
2255
2256 void close() throws IOException {
2257 }
2258 }
2259
2260 static class CorruptedLogFileException extends Exception {
2261 private static final long serialVersionUID = 1L;
2262
2263 CorruptedLogFileException(String s) {
2264 super(s);
2265 }
2266 }
2267
2268
2269 public static class MutationReplay {
2270 public MutationReplay(MutationType type, Mutation mutation, long nonceGroup, long nonce) {
2271 this.type = type;
2272 this.mutation = mutation;
2273 if(this.mutation.getDurability() != Durability.SKIP_WAL) {
2274
2275 this.mutation.setDurability(Durability.ASYNC_WAL);
2276 }
2277 this.nonceGroup = nonceGroup;
2278 this.nonce = nonce;
2279 }
2280
2281 public final MutationType type;
2282 public final Mutation mutation;
2283 public final long nonceGroup;
2284 public final long nonce;
2285 }
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298 public static List<MutationReplay> getMutationsFromWALEntry(WALEntry entry, CellScanner cells,
2299 Pair<WALKey, WALEdit> logEntry, Durability durability)
2300 throws IOException {
2301 if (entry == null) {
2302
2303 return new ArrayList<MutationReplay>();
2304 }
2305
2306 long replaySeqId = (entry.getKey().hasOrigSequenceNumber()) ?
2307 entry.getKey().getOrigSequenceNumber() : entry.getKey().getLogSequenceNumber();
2308 int count = entry.getAssociatedCellCount();
2309 List<MutationReplay> mutations = new ArrayList<MutationReplay>();
2310 Cell previousCell = null;
2311 Mutation m = null;
2312 WALKey key = null;
2313 WALEdit val = null;
2314 if (logEntry != null) val = new WALEdit();
2315
2316 for (int i = 0; i < count; i++) {
2317
2318 if (!cells.advance()) {
2319 throw new ArrayIndexOutOfBoundsException("Expected=" + count + ", index=" + i);
2320 }
2321 Cell cell = cells.current();
2322 if (val != null) val.add(cell);
2323
2324 boolean isNewRowOrType =
2325 previousCell == null || previousCell.getTypeByte() != cell.getTypeByte()
2326 || !CellUtil.matchingRow(previousCell, cell);
2327 if (isNewRowOrType) {
2328
2329 if (CellUtil.isDelete(cell)) {
2330 m = new Delete(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength());
2331
2332 mutations.add(new MutationReplay(
2333 MutationType.DELETE, m, HConstants.NO_NONCE, HConstants.NO_NONCE));
2334 } else {
2335 m = new Put(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength());
2336
2337 long nonceGroup = entry.getKey().hasNonceGroup()
2338 ? entry.getKey().getNonceGroup() : HConstants.NO_NONCE;
2339 long nonce = entry.getKey().hasNonce() ? entry.getKey().getNonce() : HConstants.NO_NONCE;
2340 mutations.add(new MutationReplay(MutationType.PUT, m, nonceGroup, nonce));
2341 }
2342 }
2343 if (CellUtil.isDelete(cell)) {
2344 ((Delete) m).addDeleteMarker(cell);
2345 } else {
2346 ((Put) m).add(cell);
2347 }
2348 m.setDurability(durability);
2349 previousCell = cell;
2350 }
2351
2352
2353 if (logEntry != null) {
2354 org.apache.hadoop.hbase.protobuf.generated.WALProtos.WALKey walKeyProto = entry.getKey();
2355 List<UUID> clusterIds = new ArrayList<UUID>(walKeyProto.getClusterIdsCount());
2356 for (HBaseProtos.UUID uuid : entry.getKey().getClusterIdsList()) {
2357 clusterIds.add(new UUID(uuid.getMostSigBits(), uuid.getLeastSigBits()));
2358 }
2359
2360 key = new HLogKey(walKeyProto.getEncodedRegionName().toByteArray(), TableName.valueOf(
2361 walKeyProto.getTableName().toByteArray()), replaySeqId, walKeyProto.getWriteTime(),
2362 clusterIds, walKeyProto.getNonceGroup(), walKeyProto.getNonce(), null);
2363 logEntry.setFirst(key);
2364 logEntry.setSecond(val);
2365 }
2366
2367 return mutations;
2368 }
2369 }