Author: mreutegg
Date: Mon Nov 28 12:44:01 2016
New Revision: 1771729
URL: http://svn.apache.org/viewvc?rev=1771729&view=rev
Log:
OAK-5156: Limit JournalDiffLoader to subtree
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoader.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoaderTest.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java?rev=1771729&r1=1771728&r2=1771729&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
Mon Nov 28 12:44:01 2016
@@ -1994,7 +1994,8 @@ public final class DocumentNodeStore
// then there were external changes and reading them
// was successful -> apply them to the diff cache
try {
- JournalEntry.applyTo(externalSort, diffCache,
oldHead, newHead);
+ JournalEntry.applyTo(externalSort, diffCache,
+ PathUtils.ROOT_PATH, oldHead, newHead);
} catch (Exception e1) {
LOG.error("backgroundRead: Exception while
processing external changes from journal: {}", e1, e1);
}
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoader.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoader.java?rev=1771729&r1=1771728&r2=1771729&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoader.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoader.java
Mon Nov 28 12:44:01 2016
@@ -33,6 +33,8 @@ import org.apache.jackrabbit.oak.plugins
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.commons.io.FileUtils.byteCountToDisplaySize;
import static org.apache.jackrabbit.oak.plugins.document.JournalEntry.asId;
import static
org.apache.jackrabbit.oak.plugins.document.JournalEntry.fillExternalChanges;
@@ -55,16 +57,20 @@ class JournalDiffLoader implements DiffC
JournalDiffLoader(@Nonnull AbstractDocumentNodeState base,
@Nonnull AbstractDocumentNodeState node,
@Nonnull DocumentNodeStore ns) {
- this.base = base;
- this.node = node;
- this.ns = ns;
+ this.base = checkNotNull(base);
+ this.node = checkNotNull(node);
+ this.ns = checkNotNull(ns);
+ checkArgument(base.getPath().equals(node.getPath()),
+ "nodes must have matching paths: {} != {}",
+ base.getPath(), node.getPath());
}
@Override
public String call() {
- stats = new Stats();
+ String path = node.getPath();
RevisionVector afterRev = node.getRootRevision();
RevisionVector beforeRev = base.getRootRevision();
+ stats = new Stats(path, beforeRev, afterRev);
JournalEntry localPending = ns.getCurrentJournalEntry();
DocumentStore store = ns.getDocumentStore();
@@ -79,15 +85,15 @@ class JournalDiffLoader implements DiffC
StringSort changes = JournalEntry.newSorter();
try {
- readTrunkChanges(beforeRev, afterRev, localPending, localLastRev,
changes);
+ readTrunkChanges(path, beforeRev, afterRev, localPending,
localLastRev, changes);
- readBranchChanges(beforeRev, changes);
- readBranchChanges(afterRev, changes);
+ readBranchChanges(path, beforeRev, changes);
+ readBranchChanges(path, afterRev, changes);
changes.sort();
DiffCache df = ns.getDiffCache();
WrappedDiffCache wrappedCache = new
WrappedDiffCache(node.getPath(), df, stats);
- JournalEntry.applyTo(changes, wrappedCache, beforeRev, afterRev);
+ JournalEntry.applyTo(changes, wrappedCache, path, beforeRev,
afterRev);
return wrappedCache.changes;
} catch (IOException e) {
@@ -98,7 +104,8 @@ class JournalDiffLoader implements DiffC
}
}
- private void readBranchChanges(RevisionVector rv,
+ private void readBranchChanges(String path,
+ RevisionVector rv,
StringSort changes) throws IOException {
if (!rv.isBranch() || ns.isDisableBranches()) {
return;
@@ -116,7 +123,7 @@ class JournalDiffLoader implements DiffC
if (!bc.isRebase()) {
JournalEntry entry = store.find(Collection.JOURNAL, asId(br));
if (entry != null) {
- entry.addTo(changes);
+ entry.addTo(changes, path);
stats.numJournalEntries++;
} else {
LOG.warn("Missing journal entry for {}", asId(br));
@@ -125,7 +132,8 @@ class JournalDiffLoader implements DiffC
}
}
- private void readTrunkChanges(RevisionVector beforeRev,
+ private void readTrunkChanges(String path,
+ RevisionVector beforeRev,
RevisionVector afterRev,
JournalEntry localPending,
Revision localLastRev,
@@ -151,13 +159,13 @@ class JournalDiffLoader implements DiffC
// use revision with a timestamp of zero
from = new Revision(0, 0, to.getClusterId());
}
- stats.numJournalEntries += fillExternalChanges(changes, from, to,
ns.getDocumentStore());
+ stats.numJournalEntries += fillExternalChanges(changes, path,
from, to, ns.getDocumentStore());
}
// do we need to include changes from pending local changes?
if (!max.isRevisionNewer(localLastRev)
&& !localLastRev.equals(max.getRevision(clusterId))) {
// journal does not contain all local changes
- localPending.addTo(changes);
+ localPending.addTo(changes, path);
stats.numJournalEntries++;
}
}
@@ -197,16 +205,25 @@ class JournalDiffLoader implements DiffC
private static class Stats {
private final Stopwatch sw = Stopwatch.createStarted();
+ private final String path;
+ private final RevisionVector from, to;
private long numJournalEntries;
private long numDiffEntries;
private long keyMemory;
private long valueMemory;
+ Stats(String path, RevisionVector from, RevisionVector to) {
+ this.path = path;
+ this.from = from;
+ this.to = to;
+ }
+
@Override
public String toString() {
- String msg = "%d diffs loaded from %d journal entries in %s. " +
+ String msg = "%d diffs for %s (%s/%s) loaded from %d journal
entries in %s. " +
"Keys: %s, values: %s, total: %s";
- return String.format(msg, numDiffEntries, numJournalEntries, sw,
+ return String.format(msg, numDiffEntries, path, from, to,
+ numJournalEntries, sw,
byteCountToDisplaySize(keyMemory),
byteCountToDisplaySize(valueMemory),
byteCountToDisplaySize(keyMemory + valueMemory));
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java?rev=1771729&r1=1771728&r2=1771729&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java
Mon Nov 28 12:44:01 2016
@@ -101,9 +101,10 @@ public final class JournalEntry extends
static void applyTo(@Nonnull StringSort externalSort,
@Nonnull DiffCache diffCache,
+ @Nonnull String path,
@Nonnull RevisionVector from,
@Nonnull RevisionVector to) throws IOException {
- LOG.debug("applyTo: starting for {} to {}", from, to);
+ LOG.debug("applyTo: starting for {} from {} to {}", path, from, to);
// note that it is not de-duplicated yet
LOG.debug("applyTo: sorting done.");
@@ -113,8 +114,8 @@ public final class JournalEntry extends
if (!it.hasNext()) {
// nothing at all? that's quite unusual..
- // we apply this diff as one '/' to the entry then
- entry.append("/", "");
+ // we apply this diff as no change at the given path
+ entry.append(path, "");
entry.done();
return;
}
@@ -139,8 +140,10 @@ public final class JournalEntry extends
// part of that hierarchy anymore and we 'move elsewhere'.
// eg if 'currentPath' is /a/b/e, then we must flush /a/b/c/d and
/a/b/c
while (node != null && !node.isAncestorOf(currentNode)) {
- // add parent to the diff entry
- entry.append(node.getPath(), getChanges(node));
+ // add parent to the diff entry if within scope
+ if (inScope(node, path)) {
+ entry.append(node.getPath(), getChanges(node));
+ }
deDuplicatedCnt++;
// clean up the hierarchy when we are done with this
// part of the tree to avoid excessive memory usage
@@ -165,7 +168,7 @@ public final class JournalEntry extends
// once we're done we still have the last hierarchy line contained in
'node',
// eg /x, /x/y, /x/y/z
// and that one we must now append to the diff cache entry:
- while (node != null) {
+ while (node != null && inScope(node, path)) {
entry.append(node.getPath(), getChanges(node));
deDuplicatedCnt++;
node = node.parent;
@@ -176,6 +179,14 @@ public final class JournalEntry extends
LOG.debug("applyTo: done. totalCnt: {}, deDuplicatedCnt: {}",
totalCnt, deDuplicatedCnt);
}
+ private static boolean inScope(TreeNode node, String path) {
+ if (PathUtils.denotesRoot(path)) {
+ return true;
+ }
+ String p = node.getPath();
+ return p.startsWith(path) && (p.length() == path.length() ||
p.charAt(path.length()) == '/');
+ }
+
/**
* Reads all external changes between the two given revisions (with the
same
* clusterId) from the journal and appends the paths therein to the
provided
@@ -192,10 +203,38 @@ public final class JournalEntry extends
* @throws IOException
*/
static int fillExternalChanges(@Nonnull StringSort sorter,
- @Nonnull Revision from,
- @Nonnull Revision to,
- @Nonnull DocumentStore store)
+ @Nonnull Revision from,
+ @Nonnull Revision to,
+ @Nonnull DocumentStore store)
throws IOException {
+ return fillExternalChanges(sorter, PathUtils.ROOT_PATH, from, to,
store);
+ }
+
+ /**
+ * Reads external changes between the two given revisions (with the same
+ * clusterId) from the journal and appends the paths therein to the
provided
+ * sorter. If there is no exact match of a journal entry for the given
+ * {@code to} revision, this method will fill external changes from the
+ * next higher journal entry that contains the revision. The {@code path}
+ * defines the scope of the external changes that should be read and filled
+ * into the {@code sorter}.
+ *
+ * @param sorter the StringSort to which all externally changed paths
+ * between the provided revisions will be added
+ * @param path a path that defines the scope of the changes to read.
+ * @param from the lower bound of the revision range (exclusive).
+ * @param to the upper bound of the revision range (inclusive).
+ * @param store the document store to query.
+ * @return the number of journal entries read from the store.
+ * @throws IOException
+ */
+ static int fillExternalChanges(@Nonnull StringSort sorter,
+ @Nonnull String path,
+ @Nonnull Revision from,
+ @Nonnull Revision to,
+ @Nonnull DocumentStore store)
+ throws IOException {
+ checkNotNull(path);
checkArgument(checkNotNull(from).getClusterId() ==
checkNotNull(to).getClusterId());
if (from.compareRevisionTime(to) >= 0) {
@@ -231,7 +270,7 @@ public final class JournalEntry extends
}
for (JournalEntry d : partialResult) {
- d.addTo(sorter);
+ d.addTo(sorter, path);
}
if (partialResult.size() < READ_CHUNK_SIZE) {
break;
@@ -248,7 +287,7 @@ public final class JournalEntry extends
|| (lastEntry != null &&
!lastEntry.getId().equals(inclusiveToId))) {
String maxId = asId(new Revision(Long.MAX_VALUE, 0,
to.getClusterId()));
for (JournalEntry d : store.query(JOURNAL, inclusiveToId, maxId,
1)) {
- d.addTo(sorter);
+ d.addTo(sorter, path);
numEntries++;
}
}
@@ -287,14 +326,6 @@ public final class JournalEntry extends
put(BRANCH_COMMITS, branchCommits);
}
- String getChanges(String path) {
- TreeNode node = getNode(path);
- if (node == null) {
- return "";
- }
- return getChanges(node);
- }
-
UpdateOp asUpdateOp(@Nonnull Revision revision) {
String id = asId(revision);
UpdateOp op = new UpdateOp(id, true);
@@ -309,18 +340,31 @@ public final class JournalEntry extends
return op;
}
- void addTo(final StringSort sort) throws IOException {
- TreeNode n = getChanges();
+ /**
+ * Add changed paths in this journal entry that are in the scope of
+ * {@code path} to {@code sort}.
+ *
+ * @param sort where changed paths are added to.
+ * @param path the scope for added paths.
+ * @throws IOException if an exception occurs while adding a path to
+ * {@code sort}. In this case only some paths may have been added.
+ */
+ void addTo(final StringSort sort, String path) throws IOException {
TraversingVisitor v = new TraversingVisitor() {
-
@Override
- public void node(TreeNode node, String path) throws IOException {
- sort.add(path);
+ public void node(TreeNode node, String p) throws IOException {
+ sort.add(p);
}
};
- n.accept(v, "/");
+ TreeNode n = getNode(path);
+ if (n != null) {
+ n.accept(v, path);
+ }
for (JournalEntry e : getBranchCommits()) {
- e.getChanges().accept(v, "/");
+ n = e.getNode(path);
+ if (n != null) {
+ n.accept(v, path);
+ }
}
}
Modified:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoaderTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoaderTest.java?rev=1771729&r1=1771728&r2=1771729&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoaderTest.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoaderTest.java
Mon Nov 28 12:44:01 2016
@@ -19,6 +19,7 @@ package org.apache.jackrabbit.oak.plugin
import java.util.Set;
import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.cache.CacheStats;
import org.apache.jackrabbit.oak.plugins.document.memory.MemoryDocumentStore;
import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
@@ -29,6 +30,8 @@ import org.junit.Test;
import static com.google.common.collect.Sets.newHashSet;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class JournalDiffLoaderTest {
@@ -162,6 +165,48 @@ public class JournalDiffLoaderTest {
assertEquals(newHashSet("foo", "bar", "baz"), changeChildNodes(ns1,
s1, s2));
}
+ @Test
+ public void withPath() throws Exception {
+ DocumentNodeStore ns = builderProvider.newBuilder()
+ .setAsyncDelay(0).getNodeStore();
+ NodeBuilder builder = ns.getRoot().builder();
+ builder.child("foo");
+ merge(ns, builder);
+ ns.runBackgroundOperations();
+ DocumentNodeState before = (DocumentNodeState)
ns.getRoot().getChildNode("foo");
+ builder = ns.getRoot().builder();
+ builder.child("bar");
+ merge(ns, builder);
+ ns.runBackgroundOperations();
+ builder = ns.getRoot().builder();
+ builder.child("foo").child("a").child("b").child("c");
+ merge(ns, builder);
+ ns.runBackgroundOperations();
+ builder = ns.getRoot().builder();
+ builder.child("bar").child("a").child("b").child("c");
+ merge(ns, builder);
+ ns.runBackgroundOperations();
+ DocumentNodeState after = (DocumentNodeState)
ns.getRoot().getChildNode("foo");
+
+ CacheStats cs = getMemoryDiffStats(ns);
+ assertNotNull(cs);
+ cs.resetStats();
+ Set<String> changes = changeChildNodes(ns, before, after);
+ assertEquals(1, changes.size());
+ assertTrue(changes.contains("a"));
+ // must only push /foo, /foo/a, /foo/a/b and /foo/a/b/c into cache
+ assertEquals(4, cs.getElementCount());
+ }
+
+ private static CacheStats getMemoryDiffStats(DocumentNodeStore ns) {
+ for (CacheStats cs : ns.getDiffCache().getStats()) {
+ if (cs.getName().equals("Document-MemoryDiff")) {
+ return cs;
+ }
+ }
+ return null;
+ }
+
private static Set<String> changeChildNodes(DocumentNodeStore store,
AbstractDocumentNodeState
before,
AbstractDocumentNodeState
after) {
Modified:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java?rev=1771729&r1=1771728&r2=1771729&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java
Mon Nov 28 12:44:01 2016
@@ -56,7 +56,7 @@ public class JournalEntryTest {
RevisionVector from = new RevisionVector(new Revision(1, 0, 1));
RevisionVector to = new RevisionVector(new Revision(2, 0, 1));
sort.sort();
- JournalEntry.applyTo(sort, cache, from, to);
+ JournalEntry.applyTo(sort, cache, "/", from, to);
for (String p : paths) {
String changes = cache.getChanges(from, to, p, null);
@@ -68,6 +68,29 @@ public class JournalEntryTest {
sort.close();
}
+ @Test
+ public void applyToWithPath() throws Exception {
+ DiffCache cache = new MemoryDiffCache(new DocumentMK.Builder());
+ StringSort sort = JournalEntry.newSorter();
+ sort.add("/");
+ sort.add("/foo");
+ sort.add("/foo/a");
+ sort.add("/foo/b");
+ sort.add("/bar");
+ sort.add("/bar/a");
+ sort.add("/bar/b");
+ RevisionVector from = new RevisionVector(Revision.newRevision(1));
+ RevisionVector to = new RevisionVector(Revision.newRevision(1));
+ sort.sort();
+ JournalEntry.applyTo(sort, cache, "/foo", from, to);
+ assertNotNull(cache.getChanges(from, to, "/foo", null));
+ assertNotNull(cache.getChanges(from, to, "/foo/a", null));
+ assertNotNull(cache.getChanges(from, to, "/foo/b", null));
+ assertNull(cache.getChanges(from, to, "/bar", null));
+ assertNull(cache.getChanges(from, to, "/bar/a", null));
+ assertNull(cache.getChanges(from, to, "/bar/b", null));
+ }
+
//OAK-3494
@Test
public void useParentDiff() throws Exception {
@@ -98,7 +121,7 @@ public class JournalEntryTest {
StringSort sort = JournalEntry.newSorter();
add(sort, paths);
sort.sort();
- JournalEntry.applyTo(sort, cache, from, to);
+ JournalEntry.applyTo(sort, cache, "/", from, to);
validateCacheUsage(cache, from, to, "/topUnchanged", true);
validateCacheUsage(cache, from, to, "/content/changed/unchangedLeaf",
true);
@@ -206,6 +229,31 @@ public class JournalEntryTest {
}
@Test
+ public void fillExternalChangesWithPath() throws Exception {
+ Revision r1 = new Revision(1, 0, 1);
+ Revision r2 = new Revision(2, 0, 1);
+ DocumentStore store = new MemoryDocumentStore();
+ JournalEntry entry = JOURNAL.newDocument(store);
+ entry.modified("/");
+ entry.modified("/foo");
+ entry.modified("/foo/a");
+ entry.modified("/foo/b");
+ entry.modified("/foo/c");
+ entry.modified("/bar");
+ entry.modified("/bar/a");
+ entry.modified("/bar/b");
+ entry.modified("/bar/c");
+
+ UpdateOp op = entry.asUpdateOp(r2);
+ assertTrue(store.create(JOURNAL, Collections.singletonList(op)));
+
+ StringSort sort = JournalEntry.newSorter();
+ JournalEntry.fillExternalChanges(sort, "/foo", r1, r2, store);
+ assertEquals(4, sort.getSize());
+ sort.close();
+ }
+
+ @Test
public void getRevisionTimestamp() throws Exception {
DocumentStore store = new MemoryDocumentStore();
JournalEntry entry = JOURNAL.newDocument(store);
@@ -234,7 +282,7 @@ public class JournalEntryTest {
t.start();
StringSort sort = JournalEntry.newSorter();
try {
- entry.addTo(sort);
+ entry.addTo(sort, PathUtils.ROOT_PATH);
} finally {
sort.close();
}
@@ -244,6 +292,25 @@ public class JournalEntryTest {
}
}
+ @Test
+ public void addToWithPath() throws Exception {
+ DocumentStore store = new MemoryDocumentStore();
+ JournalEntry entry = JOURNAL.newDocument(store);
+ entry.modified("/");
+ entry.modified("/foo");
+ entry.modified("/foo/a");
+ entry.modified("/foo/b");
+ entry.modified("/foo/c");
+ entry.modified("/bar");
+ entry.modified("/bar/a");
+ entry.modified("/bar/b");
+ entry.modified("/bar/c");
+ StringSort sort = JournalEntry.newSorter();
+ entry.addTo(sort, "/foo");
+ assertEquals(4, sort.getSize());
+ sort.close();
+ }
+
private static void addRandomPaths(java.util.Collection<String> paths)
throws IOException {
paths.add("/");
Random random = new Random(42);