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));
}