Author: chetanm
Date: Mon Mar 31 15:06:12 2014
New Revision: 1583344
URL: http://svn.apache.org/r1583344
Log:
OAK-1295 - Recovery for missing _lastRev updates (WIP)
Added:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryTest.java
(with props)
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecovery.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecovery.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecovery.java?rev=1583344&r1=1583343&r2=1583344&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecovery.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecovery.java
Mon Mar 31 15:06:12 2014
@@ -20,17 +20,12 @@
package org.apache.jackrabbit.oak.plugins.document;
import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.CheckForNull;
import com.google.common.base.Predicate;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
import org.apache.jackrabbit.oak.commons.PathUtils;
-import org.apache.jackrabbit.oak.plugins.document.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -47,77 +42,44 @@ public class LastRevRecovery {
this.nodeStore = nodeStore;
}
- public void recover(Iterator<NodeDocument> suspects, int clusterId) {
+ public int recover(Iterator<NodeDocument> suspects, int clusterId) {
UnsavedModifications unsaved = new UnsavedModifications();
- //Set of parent path whose lastRev has been updated based on
- //last rev information obtained from suspects. Its possible
- //that lastRev for such parents present in DS has
- //higher value. So before persisting the changes for these
- //paths we need to ensure that there actual lastRev is lesser
- //than one being set via unsaved
- Set<String> unverifiedParentPaths = Sets.newHashSet();
-
- //Map of known last rev of checked paths
- Map<String, Revision> knownLastRevs = Maps.newHashMap();
-
while (suspects.hasNext()) {
NodeDocument doc = suspects.next();
Revision currentLastRev = doc.getLastRev().get(clusterId);
- if (currentLastRev != null) {
- knownLastRevs.put(doc.getPath(), currentLastRev);
- }
-
Revision lostLastRev = determineMissedLastRev(doc, clusterId);
- //lastRev is consistent
- if (lostLastRev == null) {
- continue;
- }
-
//1. Update lastRev for this doc
- unsaved.put(doc.getPath(), lostLastRev);
-
- //2. Update lastRev for parent paths
- String path = doc.getPath();
- while (true) {
- if (PathUtils.denotesRoot(path)) {
- break;
- }
- path = PathUtils.getParentPath(path);
- unsaved.put(path, lostLastRev);
- unverifiedParentPaths.add(path);
+ if (lostLastRev != null) {
+ unsaved.put(doc.getPath(), lostLastRev);
}
- }
- //By now we have iterated over all suspects so remove entries for paths
- //whose lastRev have been determined on the basis of state obtained
from
- //DS
- Iterator<String> unverifiedParentPathsItr =
unverifiedParentPaths.iterator();
- while (unverifiedParentPathsItr.hasNext()) {
- String unverifiedParentPath = unverifiedParentPathsItr.next();
- Revision knownRevision = knownLastRevs.get(unverifiedParentPath);
- if (knownRevision != null) {
- unverifiedParentPathsItr.remove();
- unsaved.put(unverifiedParentPath, knownRevision);
- }
- }
+ Revision lastRevForParents = lostLastRev != null ? lostLastRev :
currentLastRev;
- //Now for the left over unverifiedParentPaths determine the lastRev
- //from DS and add them to unsaved. This ensures that we do not set
lastRev
- //to a lower value
-
- //TODO For Mongo case we can fetch such documents more efficiently
- //via batch fetch
- for (String path : unverifiedParentPaths) {
- NodeDocument doc = getDocument(path);
- if (doc != null) {
- Revision lastRev = doc.getLastRev().get(clusterId);
- unsaved.put(path, lastRev);
+ //If both currentLastRev and lostLastRev are null it means
+ //that no change is done by suspect cluster on this document
+ //so nothing needs to be updated. Probably it was only changed by
+ //other cluster nodes. If this node is parent of any child node
which
+ //has been modified by cluster then that node roll up would
+ //add this node path to unsaved
+
+ //2. Update lastRev for parent paths aka rollup
+ if (lastRevForParents != null) {
+ String path = doc.getPath();
+ while (true) {
+ if (PathUtils.denotesRoot(path)) {
+ break;
+ }
+ path = PathUtils.getParentPath(path);
+ unsaved.put(path, lastRevForParents);
+ }
}
}
+ //Note the size before persist as persist operation
+ //would empty the internal state
int size = unsaved.getPaths().size();
if (log.isDebugEnabled()) {
@@ -131,6 +93,8 @@ public class LastRevRecovery {
log.info("Updated lastRev of [{}] documents while performing lastRev
recovery for " +
"cluster node [{}]", size, clusterId);
+
+ return size;
}
/**
@@ -154,9 +118,8 @@ public class LastRevRecovery {
//Merge sort the revs for which changes have been made
//to this doc
- //TODO Would looking into the Local map be sufficient
- //Probably yes as entries for a particular cluster node
- //are split by that cluster only
+ //localMap always keeps the most recent valid commit entry
+ //per cluster node so looking into that should be sufficient
Iterable<Revision> revs = mergeSorted(of(
filter(doc.getLocalCommitRoot().keySet(), cp),
filter(doc.getLocalRevisions().keySet(), cp)),
@@ -180,10 +143,6 @@ public class LastRevRecovery {
return null;
}
- private NodeDocument getDocument(String path) {
- return nodeStore.getDocumentStore().find(Collection.NODES,
Utils.getIdFromPath(path));
- }
-
private static class ClusterPredicate implements Predicate<Revision> {
private final int clusterId;
Added:
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=1583344&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryTest.java
(added)
+++
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryTest.java
Mon Mar 31 15:06:12 2014
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.jackrabbit.oak.plugins.document;
+
+import com.google.common.collect.Iterators;
+import org.apache.jackrabbit.oak.plugins.document.memory.MemoryDocumentStore;
+import org.apache.jackrabbit.oak.plugins.document.util.Utils;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+public class LastRevRecoveryTest {
+ private DocumentNodeStore ds1;
+ private DocumentNodeStore ds2;
+ private MemoryDocumentStore sharedStore;
+
+ @Before
+ public void setUp(){
+ sharedStore = new MemoryDocumentStore();
+ ds1 = new DocumentMK.Builder()
+ .setClusterId(1)
+ .setAsyncDelay(0)
+ .setDocumentStore(sharedStore)
+ .getNodeStore();
+
+ ds2 = new DocumentMK.Builder()
+ .setClusterId(2)
+ .setAsyncDelay(0)
+ .setDocumentStore(sharedStore)
+ .getNodeStore();
+ }
+
+
+ @Test
+ public void testRecover() throws Exception {
+ //1. Create base structure /x/y
+ NodeBuilder b1 = ds1.getRoot().builder();
+ b1.child("x").child("y");
+ ds1.merge(b1, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ ds1.runBackgroundOperations();
+
+ //lastRev are persisted directly for new nodes. In case of
+ // updates they are persisted via background jobs
+
+ //1.2 Get last rev populated for root node for ds2
+ ds2.runBackgroundOperations();
+ NodeBuilder b2 = ds2.getRoot().builder();
+ b2.child("x").setProperty("f1","b1");
+ ds2.merge(b2, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ ds2.runBackgroundOperations();
+
+ //2. Add a new node /x/y/z
+ b2 = ds2.getRoot().builder();
+ b2.child("x").child("y").child("z").setProperty("foo", "bar");
+ ds2.merge(b2, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+ //Refresh DS1
+ ds1.runBackgroundOperations();
+
+ NodeDocument z1 = getDocument(ds1, "/x/y/z");
+ NodeDocument y1 = getDocument(ds1, "/x/y");
+ NodeDocument x1 = getDocument(ds1, "/x");
+
+ Revision zlastRev2 = z1.getLastRev().get(2);
+ assertNotNull(zlastRev2);
+
+ //lastRev should not be updated for C #2
+ assertNull(y1.getLastRev().get(2));
+
+ LastRevRecovery recovery = new LastRevRecovery(ds1);
+
+ //Do not pass y1 but still y1 should be updated
+ recovery.recover(Iterators.forArray(x1,z1), 2);
+
+ //Post recovery the lastRev should be updated for /x/y and /x
+ assertEquals(zlastRev2, getDocument(ds1, "/x/y").getLastRev().get(2));
+ assertEquals(zlastRev2, getDocument(ds1, "/x").getLastRev().get(2));
+ assertEquals(zlastRev2, getDocument(ds1, "/").getLastRev().get(2));
+ }
+
+ private NodeDocument getDocument(DocumentNodeStore nodeStore, String path)
{
+ return nodeStore.getDocumentStore().find(Collection.NODES,
Utils.getIdFromPath(path));
+ }
+}
Propchange:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryTest.java
------------------------------------------------------------------------------
svn:eol-style = native