You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by mr...@apache.org on 2017/05/10 14:58:44 UTC

svn commit: r1794731 - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/oak/plugins/document/ test/java/org/apache/jackrabbit/oak/plugins/document/

Author: mreutegg
Date: Wed May 10 14:58:44 2017
New Revision: 1794731

URL: http://svn.apache.org/viewvc?rev=1794731&view=rev
Log:
OAK-3711: Clean up _revision entries on commit root documents

Remove responsibility for initial sweep from LastRevRecoveryAgent

Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryAgent.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreSweepTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryTest.java

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryAgent.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryAgent.java?rev=1794731&r1=1794730&r2=1794731&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryAgent.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryAgent.java Wed May 10 14:58:44 2017
@@ -54,6 +54,14 @@ import org.slf4j.LoggerFactory;
  * Utility class for recovering potential missing _lastRev updates of nodes due
  * to crash of a node. The recovery agent is also responsible for document
  * sweeping (reverting uncommitted changes).
+ * <p>
+ * The recovery agent will only sweep documents for a given clusterId if the
+ * root document contains a sweep revision for the clusterId. A missing sweep
+ * revision for a clusterId indicates an upgrade from an earlier Oak version and
+ * a crash before the initial sweep finished. This is not the responsibility of
+ * the recovery agent. An initial sweep for an upgrade must either happen with
+ * the oak-run 'revisions' sweep command or on startup of an upgraded Oak
+ * instance.
  */
 public class LastRevRecoveryAgent {
     private final Logger log = LoggerFactory.getLogger(getClass());
@@ -132,15 +140,7 @@ public class LastRevRecoveryAgent {
                             "no lastRev for root, using timestamp based on leaseEnd %d - leaseTime %d - asyncDelay %d", leaseEnd,
                             leaseTime, asyncDelay);
                 }
-                if (sweepRev == null) {
-                    // no sweep ever done for this cluster node. this is
-                    // quite unusual and means an upgrade happened for a
-                    // cluster node from 1.6 or older and then crashed
-                    // we need to scan the entire collection
-                    startTime = 0;
-                    reason = "no sweepRevision for cluster node " + clusterId +
-                            ", using timestamp 0 (scanning the entire collection)";
-                } else if (sweepRev.getTimestamp() < startTime) {
+                if (sweepRev != null && sweepRev.getTimestamp() < startTime) {
                     startTime = sweepRev.getTimestamp();
                     reason = "sweepRev: " + sweepRev.toString();
                 }
@@ -191,44 +191,50 @@ public class LastRevRecoveryAgent {
                        final int clusterId, final boolean dryRun)
             throws DocumentStoreException {
         final DocumentStore docStore = nodeStore.getDocumentStore();
+        NodeDocument rootDoc = Utils.getRootDocument(docStore);
 
         // first run a sweep
         final AtomicReference<Revision> sweepRev = new AtomicReference<>();
-        final RevisionContext context = new InactiveRevisionContext(
-                Utils.getRootDocument(docStore), nodeStore, clusterId);
-        final NodeDocumentSweeper sweeper = new NodeDocumentSweeper(context, true);
-        sweeper.sweep(suspects, new NodeDocumentSweepListener() {
-            @Override
-            public void sweepUpdate(Map<String, UpdateOp> updates)
-                    throws DocumentStoreException {
-                if (dryRun) {
-                    log.info("Dry run of sweeper identified [{}] documents for " +
-                            "cluster node [{}]: {}", updates.size(), clusterId,
-                            updates.values());
-                    return;
+        if (rootDoc.getSweepRevisions().getRevision(clusterId) != null) {
+            // only run a sweep for a cluster node that already has a
+            // sweep revision. Initial sweep is not the responsibility
+            // of the recovery agent.
+            final RevisionContext context = new InactiveRevisionContext(
+                    rootDoc, nodeStore, clusterId);
+            final NodeDocumentSweeper sweeper = new NodeDocumentSweeper(context, true);
+            sweeper.sweep(suspects, new NodeDocumentSweepListener() {
+                @Override
+                public void sweepUpdate(Map<String, UpdateOp> updates)
+                        throws DocumentStoreException {
+                    if (dryRun) {
+                        log.info("Dry run of sweeper identified [{}] documents for " +
+                                        "cluster node [{}]: {}", updates.size(), clusterId,
+                                updates.values());
+                        return;
+                    }
+                    // create an invalidate entry
+                    JournalEntry inv = JOURNAL.newDocument(docStore);
+                    inv.modified(updates.keySet());
+                    Revision r = context.newRevision().asBranchRevision();
+                    UpdateOp invOp = inv.asUpdateOp(r);
+                    // and reference it from a regular entry
+                    JournalEntry entry = JOURNAL.newDocument(docStore);
+                    entry.invalidate(Collections.singleton(r));
+                    Revision jRev = context.newRevision();
+                    UpdateOp jOp = entry.asUpdateOp(jRev);
+                    if (!docStore.create(JOURNAL, newArrayList(invOp, jOp))) {
+                        String msg = "Unable to create journal entries for " +
+                                "document invalidation.";
+                        throw new DocumentStoreException(msg);
+                    }
+                    sweepRev.set(Utils.max(sweepRev.get(), jRev));
+                    // now that journal entry is in place, perform the actual
+                    // updates on the documents
+                    docStore.createOrUpdate(NODES, newArrayList(updates.values()));
+                    log.info("Sweeper updated {}", updates.keySet());
                 }
-                // create an invalidate entry
-                JournalEntry inv = JOURNAL.newDocument(docStore);
-                inv.modified(updates.keySet());
-                Revision r = context.newRevision().asBranchRevision();
-                UpdateOp invOp = inv.asUpdateOp(r);
-                // and reference it from a regular entry
-                JournalEntry entry = JOURNAL.newDocument(docStore);
-                entry.invalidate(Collections.singleton(r));
-                Revision jRev = context.newRevision();
-                UpdateOp jOp = entry.asUpdateOp(jRev);
-                if (!docStore.create(JOURNAL, newArrayList(invOp, jOp))) {
-                    String msg = "Unable to create journal entries for " +
-                            "document invalidation.";
-                    throw new DocumentStoreException(msg);
-                }
-                sweepRev.set(Utils.max(sweepRev.get(), jRev));
-                // now that journal entry is in place, perform the actual
-                // updates on the documents
-                docStore.createOrUpdate(NODES, newArrayList(updates.values()));
-                log.info("Sweeper updated {}", updates.keySet());
-            }
-        });
+            });
+        }
 
         // now deal with missing _lastRev updates
         UnsavedModifications unsaved = new UnsavedModifications();

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreSweepTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreSweepTest.java?rev=1794731&r1=1794730&r2=1794731&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreSweepTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreSweepTest.java Wed May 10 14:58:44 2017
@@ -32,9 +32,11 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 
+import static org.apache.jackrabbit.oak.commons.PathUtils.ROOT_PATH;
 import static org.apache.jackrabbit.oak.plugins.document.TestUtils.NO_BINARY;
 import static org.apache.jackrabbit.oak.plugins.document.TestUtils.merge;
 import static org.apache.jackrabbit.oak.plugins.document.util.Utils.getAllDocuments;
+import static org.apache.jackrabbit.oak.plugins.document.util.Utils.getIdFromPath;
 import static org.apache.jackrabbit.oak.plugins.document.util.Utils.getRootDocument;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -196,6 +198,38 @@ public class DocumentNodeStoreSweepTest
         assertCleanStore();
     }
 
+    @Test
+    public void pre18ClusterNodeRecovery() throws Exception {
+        int clusterId = ns.getClusterId();
+        createUncommittedChanges();
+        // add a node, but do not run background write
+        NodeBuilder builder = ns.getRoot().builder();
+        builder.child("node");
+        merge(ns, builder);
+
+        // simulate a crashed node store
+        crashDocumentNodeStore();
+        // and remove the sweep revision for clusterId
+        // this will look like an upgraded and crashed pre 1.8 node store
+        UpdateOp op = new UpdateOp(getIdFromPath(ROOT_PATH), false);
+        op.removeMapEntry("_sweepRev", new Revision(0, 0, clusterId));
+        assertNotNull(store.findAndUpdate(Collection.NODES, op));
+        NodeDocument rootDoc = getRootDocument(store);
+        assertNull(rootDoc.getSweepRevisions().getRevision(clusterId));
+
+        // wait for lease to expire
+        clock.waitUntil(clock.getTime() + ClusterNodeInfo.DEFAULT_LEASE_DURATION_MILLIS);
+
+        // start a new node store with a different clusterId
+        ns = createDocumentNodeStore(clusterId + 1);
+
+        // then run recovery for the other cluster node
+        assertTrue(ns.getLastRevRecoveryAgent().recover(clusterId) > 0);
+        // must not set a sweep revision
+        rootDoc = getRootDocument(store);
+        assertNull(rootDoc.getSweepRevisions().getRevision(clusterId));
+    }
+
     private void assertNodeExists(String path) {
         NodeState n = ns.getRoot();
         for (String name : PathUtils.elements(path)) {

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryTest.java?rev=1794731&r1=1794730&r2=1794731&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryTest.java Wed May 10 14:58:44 2017
@@ -39,8 +39,11 @@ import org.junit.Test;
 
 import static com.google.common.collect.Lists.newArrayList;
 import static org.apache.jackrabbit.oak.plugins.document.Collection.CLUSTER_NODES;
+import static org.apache.jackrabbit.oak.plugins.document.Collection.NODES;
+import static org.apache.jackrabbit.oak.plugins.document.util.Utils.getRootDocument;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -287,6 +290,44 @@ public class LastRevRecoveryTest {
         assertFalse(info1.isBeingRecovered());
     }
 
+    @Test
+    public void recoveryMustNotPerformInitialSweep() throws Exception {
+        String clusterId = String.valueOf(c1Id);
+        ClusterNodeInfoDocument info1 = sharedStore.find(CLUSTER_NODES, clusterId);
+
+        NodeBuilder builder = ds1.getRoot().builder();
+        builder.child("x").child("y").child("z");
+        merge(ds1, builder);
+        ds1.dispose();
+
+        // reset clusterNodes entry to simulate a crash of ds1
+        sharedStore.remove(CLUSTER_NODES, clusterId);
+        sharedStore.create(CLUSTER_NODES, newArrayList(updateOpFromDocument(info1)));
+
+        // remove the sweep revision as well
+        UpdateOp op = new UpdateOp(Utils.getIdFromPath("/"), false);
+        op.removeMapEntry("_sweepRev", new Revision(0, 0, c1Id));
+        assertNotNull(sharedStore.findAndUpdate(NODES, op));
+        NodeDocument doc = getRootDocument(sharedStore);
+        assertNull(doc.getSweepRevisions().getRevision(c1Id));
+
+        // 'wait' until lease expires
+        clock.waitUntil(info1.getLeaseEndTime() + 1);
+        // make sure ds2 lease is still fine
+        ds2.getClusterInfo().renewLease();
+
+        // run recovery on ds2 for ds1
+        LastRevRecoveryAgent agent = new LastRevRecoveryAgent(ds2);
+        Iterable<Integer> clusterIds = agent.getRecoveryCandidateNodes();
+        assertTrue(Iterables.contains(clusterIds, c1Id));
+        // nothing to recover
+        assertEquals("must not recover any documents",
+                0, agent.recover(c1Id));
+        // must not set sweep revision
+        doc = getRootDocument(sharedStore);
+        assertNull(doc.getSweepRevisions().getRevision(c1Id));
+    }
+
     private NodeDocument getDocument(DocumentNodeStore nodeStore, String path) {
         return nodeStore.getDocumentStore().find(Collection.NODES, Utils.getIdFromPath(path));
     }