Author: frm
Date: Thu Nov 29 14:06:57 2018
New Revision: 1847723
URL: http://svn.apache.org/viewvc?rev=1847723&view=rev
Log:
OAK-7866 - Check the consistency of the recovered journal
Modified:
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tooling/ConsistencyChecker.java
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/RecoverJournal.java
Modified:
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tooling/ConsistencyChecker.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tooling/ConsistencyChecker.java?rev=1847723&r1=1847722&r2=1847723&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tooling/ConsistencyChecker.java
(original)
+++
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/tooling/ConsistencyChecker.java
Thu Nov 29 14:06:57 2018
@@ -47,7 +47,7 @@ import org.apache.jackrabbit.oak.spi.sta
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.apache.jackrabbit.oak.spi.state.NodeStateUtils;
-public abstract class ConsistencyChecker {
+public class ConsistencyChecker {
private static NodeState getDescendantOrNull(NodeState root, String path) {
NodeState descendant = NodeStateUtils.getNode(root, path);
@@ -323,6 +323,22 @@ public abstract class ConsistencyChecker
.anyMatch(p -> p.journalEntry == null);
}
+ /**
+ * Check the consistency of a given subtree and returns the first
+ * inconsistent path. If provided, this method probes a set of inconsistent
+ * paths before performing a full traversal of the subtree.
+ *
+ * @param root The root node of the subtree.
+ * @param corruptedPaths A set of possibly inconsistent paths.
+ * @param binaries Whether to check binaries for consistency.
+ * @return The first inconsistent path or {@code null}. The path might be
+ * either one of the provided inconsistent paths or a new one discovered
+ * during a full traversal of the tree.
+ */
+ public String checkTreeConsistency(NodeState root, Set<String>
corruptedPaths, boolean binaries) {
+ return checkTreeConsistency(root, "/", corruptedPaths, binaries);
+ }
+
public final ConsistencyCheckResult checkConsistency(
ReadOnlyFileStore store,
Iterator<JournalEntry> journal,
Modified:
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/RecoverJournal.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/RecoverJournal.java?rev=1847723&r1=1847722&r2=1847723&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/RecoverJournal.java
(original)
+++
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/RecoverJournal.java
Thu Nov 29 14:06:57 2018
@@ -34,14 +34,19 @@ import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
+import java.util.ListIterator;
import java.util.Set;
import org.apache.jackrabbit.oak.segment.RecordId;
import org.apache.jackrabbit.oak.segment.RecordType;
import org.apache.jackrabbit.oak.segment.SegmentId;
import org.apache.jackrabbit.oak.segment.SegmentNodeState;
+import org.apache.jackrabbit.oak.segment.SegmentNodeStore;
+import org.apache.jackrabbit.oak.segment.SegmentNodeStoreBuilders;
import org.apache.jackrabbit.oak.segment.SegmentNotFoundException;
import org.apache.jackrabbit.oak.segment.file.ReadOnlyFileStore;
+import org.apache.jackrabbit.oak.segment.file.tooling.ConsistencyChecker;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
public class RecoverJournal {
@@ -98,16 +103,21 @@ public class RecoverJournal {
}
public int run() {
- List<Entry> entries = new ArrayList<>();
+ List<Entry> entries;
try (ReadOnlyFileStore store = openReadOnlyFileStore(path)) {
- recoverEntries(store, entries);
+ entries = recoverEntries(store);
} catch (Exception e) {
out.println("Unable to recover the journal entries, aborting");
e.printStackTrace(err);
return 1;
}
+ if (entries.size() == 0) {
+ out.println("No valid journal entries found, aborting");
+ return 1;
+ }
+
File journalBackup = journalBackupName();
if (journalBackup == null) {
@@ -191,7 +201,9 @@ public class RecoverJournal {
}
- private void recoverEntries(ReadOnlyFileStore fileStore, List<Entry>
entries) {
+ private List<Entry> recoverEntries(ReadOnlyFileStore fileStore) {
+ List<Entry> entries = new ArrayList<>();
+
for (SegmentId segmentId : fileStore.getSegmentIds()) {
try {
recoverEntries(fileStore, segmentId, entries);
@@ -229,6 +241,67 @@ public class RecoverJournal {
int rightRecordNumber = right.recordId.getRecordNumber();
return Integer.compare(rightRecordNumber, leftRecordNumber);
});
+
+ // Filter out the most recent entries that are not valid for
+ // consistency. Make sure that the most recent entry is always
+ // consistent.
+
+ SegmentNodeStore nodeStore =
SegmentNodeStoreBuilders.builder(fileStore).build();
+ ConsistencyChecker checker = new ConsistencyChecker();
+ Set<String> corruptedPaths = new HashSet<>();
+
+ ListIterator<Entry> i = entries.listIterator(entries.size());
+
+ nextRevision:
+ while (i.hasPrevious()) {
+ Entry entry = i.previous();
+
+ fileStore.setRevision(entry.recordId.toString());
+
+ // If the head has a corrupted path, remove this revision and check
+ // the previous one. We don't even bother to check the checkpoints.
+
+ String badHeadPath =
checker.checkTreeConsistency(nodeStore.getRoot(), corruptedPaths, true);
+
+ if (badHeadPath != null) {
+ out.printf("Skipping revision %s, corrupted path in head:
%s\n", entry.recordId, badHeadPath);
+ corruptedPaths.add(badHeadPath);
+ i.remove();
+ continue;
+ }
+
+ // If one of the checkpoints is unreachable or has a corrupted
path,
+ // we remove this revision and check the previous one. We stop
+ // checking checkpoints as soon as we find an inconsistent one.
+
+ for (String checkpoint : nodeStore.checkpoints()) {
+ NodeState root = nodeStore.retrieve(checkpoint);
+
+ if (root == null) {
+ out.printf("Skipping revision %s, found unreachable
checkpoint %s\n", entry.recordId, checkpoint);
+ i.remove();
+ continue nextRevision;
+ }
+
+ String badCheckpointPath = checker.checkTreeConsistency(root,
corruptedPaths, true);
+
+ if (badCheckpointPath != null) {
+ out.printf("Skipping revision %s, corrupted path in
checkpoint %s: %s\n", entry.recordId, checkpoint, badCheckpointPath);
+ corruptedPaths.add(badCheckpointPath);
+ i.remove();
+ continue nextRevision;
+ }
+ }
+
+ // We didn't find any corruption in the head or in the checkpoints,
+ // so we are at the most recent uncorrupted revision. We can skip
+ // checking the other revisions because the list of entries now
ends
+ // with a usable revision.
+
+ break;
+ }
+
+ return entries;
}
private void recoverEntries(ReadOnlyFileStore fileStore, SegmentId
segmentId, List<Entry> entries) {