diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Commit.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Commit.java index 334be1a..8b9ffa9 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Commit.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Commit.java @@ -569,7 +569,7 @@ public class Commit { } list.add(p); } - DiffCache.Entry cacheEntry = nodeStore.getLocalDiffCache().newEntry(before, revision); + DiffCache.Entry cacheEntry = nodeStore.getDiffCache().newEntry(before, revision); LastRevTracker tracker = nodeStore.createTracker(revision, isBranchCommit); List added = new ArrayList(); List removed = new ArrayList(); diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DiffCache.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DiffCache.java index edcee29..83e1f00 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DiffCache.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DiffCache.java @@ -20,6 +20,8 @@ import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.apache.jackrabbit.oak.cache.CacheStats; + /** * A cache for child node diffs. */ @@ -60,7 +62,13 @@ public interface DiffCache { Entry newEntry(@Nonnull Revision from, @Nonnull Revision to); - public interface Entry { + /** + * @return the statistics for this cache. + */ + @Nonnull + Iterable getStats(); + + interface Entry { /** * Appends changes about children of the node at the given path. @@ -81,7 +89,7 @@ public interface DiffCache { boolean done(); } - public interface Loader { + interface Loader { String call(); } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java index a374e49..f24f696 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java @@ -43,7 +43,6 @@ import org.apache.jackrabbit.oak.commons.json.JsopTokenizer; import org.apache.jackrabbit.oak.plugins.document.DocumentNodeState.Children; import org.apache.jackrabbit.oak.plugins.document.memory.MemoryDocumentStore; import org.apache.jackrabbit.oak.plugins.document.mongo.MongoBlobStore; -import org.apache.jackrabbit.oak.plugins.document.mongo.MongoDiffCache; import org.apache.jackrabbit.oak.plugins.document.mongo.MongoDocumentStore; import org.apache.jackrabbit.oak.plugins.document.mongo.MongoVersionGCSupport; import org.apache.jackrabbit.oak.plugins.document.persistentCache.CacheType; @@ -51,6 +50,7 @@ import org.apache.jackrabbit.oak.plugins.document.persistentCache.PersistentCach import org.apache.jackrabbit.oak.plugins.document.rdb.RDBBlobStore; import org.apache.jackrabbit.oak.plugins.document.rdb.RDBDocumentStore; import org.apache.jackrabbit.oak.plugins.document.rdb.RDBOptions; +import org.apache.jackrabbit.oak.plugins.document.util.RevisionsKey; import org.apache.jackrabbit.oak.plugins.document.util.StringValue; import org.apache.jackrabbit.oak.spi.blob.BlobStore; import org.apache.jackrabbit.oak.spi.blob.GarbageCollectableBlobStore; @@ -464,13 +464,11 @@ public class DocumentMK { private DocumentNodeStore nodeStore; private DocumentStore documentStore; private DiffCache diffCache; - private LocalDiffCache localDiffCache; private BlobStore blobStore; private int clusterId = Integer.getInteger("oak.documentMK.clusterId", 0); private int asyncDelay = 1000; private boolean timing; private boolean logging; - private boolean disableLocalDiffCache = Boolean.getBoolean("oak.documentMK.disableLocalDiffCache"); private Weigher weigher = new EmpiricalWeigher(); private long memoryCacheSize = DEFAULT_MEMORY_CACHE_SIZE; private int nodeCachePercentage = DEFAULT_NODE_CACHE_PERCENTAGE; @@ -512,10 +510,6 @@ public class DocumentMK { } this.blobStore = s; } - - if (this.diffCache == null) { - this.diffCache = new MongoDiffCache(db, changesSizeMB, this); - } } return this; } @@ -631,23 +625,11 @@ public class DocumentMK { public DiffCache getDiffCache() { if (diffCache == null) { - diffCache = new MemoryDiffCache(this); + diffCache = new TieredDiffCache(this); } return diffCache; } - public LocalDiffCache getLocalDiffCache() { - if (localDiffCache == null && !disableLocalDiffCache) { - localDiffCache = new LocalDiffCache(this); - } - return localDiffCache; - } - - public Builder setDisableLocalDiffCache(boolean disableLocalDiffCache) { - this.disableLocalDiffCache = disableLocalDiffCache; - return this; - } - public Builder setDiffCache(DiffCache diffCache) { this.diffCache = diffCache; return this; @@ -755,6 +737,14 @@ public class DocumentMK { return memoryCacheSize * diffCachePercentage / 100; } + public long getMemoryDiffCacheSize() { + return getDiffCacheSize() / 2; + } + + public long getLocalDiffCacheSize() { + return getDiffCacheSize() / 2; + } + public Builder setUseSimpleRevision(boolean useSimpleRevision) { this.useSimpleRevision = useSimpleRevision; return this; @@ -855,12 +845,12 @@ public class DocumentMK { return buildCache(CacheType.DOC_CHILDREN, getDocChildrenCacheSize(), null, null); } - public Cache buildDiffCache() { - return buildCache(CacheType.DIFF, getDiffCacheSize(), null, null); + public Cache buildMemoryDiffCache() { + return buildCache(CacheType.DIFF, getMemoryDiffCacheSize(), null, null); } - public Cache buildConsolidatedDiffCache() { - return buildCache(CacheType.CONSOLIDATED_DIFF, getDiffCacheSize(), null, null); + public Cache buildLocalDiffCache() { + return buildCache(CacheType.LOCAL_DIFF, getLocalDiffCacheSize(), null, null); } public Cache buildDocumentCache(DocumentStore docStore) { diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java index ce74ce0..06eb08b 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java @@ -54,7 +54,6 @@ import org.slf4j.LoggerFactory; import com.google.common.base.Function; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; -import com.google.common.collect.Maps; import static com.google.common.base.Preconditions.checkNotNull; import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE; @@ -82,29 +81,80 @@ public class DocumentNodeState extends AbstractNodeState implements CacheValue { final String path; final Revision rev; - final Map properties = Maps.newHashMap(); Revision lastRevision; + final Revision rootRevision; + final Map properties; final boolean hasChildren; private final DocumentNodeStore store; - DocumentNodeState(@Nonnull DocumentNodeStore store, @Nonnull String path, + DocumentNodeState(@Nonnull DocumentNodeStore store, + @Nonnull String path, @Nonnull Revision rev) { this(store, path, rev, false); } DocumentNodeState(@Nonnull DocumentNodeStore store, @Nonnull String path, @Nonnull Revision rev, boolean hasChildren) { + this(store, path, rev, new HashMap(), + hasChildren, null, null); + } + + private DocumentNodeState(@Nonnull DocumentNodeStore store, + @Nonnull String path, + @Nonnull Revision rev, + @Nonnull Map properties, + boolean hasChildren, + @Nullable Revision lastRevision, + @Nullable Revision rootRevision) { this.store = checkNotNull(store); this.path = checkNotNull(path); this.rev = checkNotNull(rev); + this.lastRevision = lastRevision; + this.rootRevision = rootRevision != null ? rootRevision : rev; this.hasChildren = hasChildren; + this.properties = checkNotNull(properties); + } + + /** + * Creates a copy of this {@code DocumentNodeState} with the + * {@link #rootRevision} set to the given {@code root} revision. This method + * returns {@code this} instance if the given {@code root} revision is + * the same as the one in this instance. + * + * @param root the root revision for the copy of this node state. + * @return a copy of this node state with the given root revision. + */ + DocumentNodeState withRootRevision(@Nonnull Revision root) { + if (rootRevision.equals(root)) { + return this; + } else { + return new DocumentNodeState(store, path, rev, properties, + hasChildren, lastRevision, root); + } } + @Nonnull Revision getRevision() { return rev; } + /** + * Returns the root revision for this node state. This is the read revision + * passed from the parent node state. This revision therefore reflects the + * revision of the root node state where the traversal down the tree + * started. The returned revision is only maintained on a best effort basis + * and may be the same as {@link #getRevision()} if this node state is + * retrieved directly from the {@code DocumentNodeStore}. + * + * @return the revision of the root node state is available, otherwise the + * same value as returned by {@link #getRevision()}. + */ + @Nonnull + Revision getRootRevision() { + return rootRevision; + } + //--------------------------< NodeState >----------------------------------- @Override @@ -168,7 +218,18 @@ public class DocumentNodeState extends AbstractNodeState implements CacheValue { @Nonnull @Override public NodeState getChildNode(@Nonnull String name) { - return getChildNode(name, lastRevision); + if (!hasChildren) { + checkValidName(name); + return EmptyNodeState.MISSING_NODE; + } + String p = PathUtils.concat(getPath(), name); + DocumentNodeState child = store.getNode(p, lastRevision); + if (child == null) { + checkValidName(name); + return EmptyNodeState.MISSING_NODE; + } else { + return child.withRootRevision(rootRevision); + } } @Override @@ -271,23 +332,6 @@ public class DocumentNodeState extends AbstractNodeState implements CacheValue { return super.compareAgainstBaseState(base, diff); } - @Nonnull - NodeState getChildNode(@Nonnull String name, - @Nonnull Revision revision) { - if (!hasChildren) { - checkValidName(name); - return EmptyNodeState.MISSING_NODE; - } - String p = PathUtils.concat(getPath(), name); - DocumentNodeState child = store.getNode(p, checkNotNull(revision)); - if (child == null) { - checkValidName(name); - return EmptyNodeState.MISSING_NODE; - } else { - return child; - } - } - void setProperty(String propertyName, String value) { if (value == null) { properties.remove(propertyName); @@ -379,7 +423,7 @@ public class DocumentNodeState extends AbstractNodeState implements CacheValue { @Override public int getMemory() { - int size = 180 + path.length() * 2; + int size = 212 + path.length() * 2; // rough approximation for properties for (Map.Entry entry : properties.entrySet()) { // name @@ -442,7 +486,7 @@ public class DocumentNodeState extends AbstractNodeState implements CacheValue { @Nonnull @Override public NodeState getNodeState() { - return input; + return input.withRootRevision(rootRevision); } }; } @@ -529,9 +573,12 @@ public class DocumentNodeState extends AbstractNodeState implements CacheValue { @Override public int getMemory() { if (cachedMemory == 0) { - int size = 114; - for (String c : children) { - size += c.length() * 2 + 56; + int size = 48; + if (!children.isEmpty()) { + size = 114; + for (String c : children) { + size += c.length() * 2 + 56; + } } cachedMemory = size; } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java index fdfc2d2..bab4369 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java @@ -81,8 +81,6 @@ import org.apache.jackrabbit.oak.plugins.document.cache.CacheInvalidationStats; import org.apache.jackrabbit.oak.plugins.document.mongo.MongoBlobReferenceIterator; import org.apache.jackrabbit.oak.plugins.document.mongo.MongoDocumentStore; import org.apache.jackrabbit.oak.plugins.document.persistentCache.PersistentCache; -import org.apache.jackrabbit.oak.plugins.observation.ContentChangeInfo; -import org.apache.jackrabbit.oak.plugins.observation.ContentChangeInfoProvider; import org.apache.jackrabbit.oak.spi.blob.BlobStore; import org.apache.jackrabbit.oak.commons.json.JsopStream; import org.apache.jackrabbit.oak.commons.json.JsopWriter; @@ -321,8 +319,6 @@ public final class DocumentNodeStore */ private final DiffCache diffCache; - private final LocalDiffCache localDiffCache; - /** * The blob store. */ @@ -429,7 +425,6 @@ public final class DocumentNodeStore builder.getWeigher(), builder.getDocChildrenCacheSize()); diffCache = builder.getDiffCache(); - localDiffCache = builder.getLocalDiffCache(); checkpoints = new Checkpoints(this); // check if root node exists @@ -696,6 +691,11 @@ public final class DocumentNodeStore return docChildrenCacheStats; } + @Nonnull + public Iterable getDiffCacheStats() { + return diffCache.getStats(); + } + void invalidateDocChildrenCache() { docChildrenCache.invalidateAll(); } @@ -1343,53 +1343,14 @@ public final class DocumentNodeStore if (node.hasNoChildren() && base.hasNoChildren()) { return true; } - String jsop = getJsopDiffIfLocalChange(diff, node); - - boolean useReadRevision = true; - if (jsop == null) { - // first lookup with read revisions of nodes and without loader - jsop = diffCache.getChanges(base.getRevision(), - node.getRevision(), node.getPath(), null); - if (jsop == null) { - useReadRevision = false; - // fall back to last revisions with loader, this - // guarantees we get a diff - jsop = diffCache.getChanges(base.getLastRevision(), - node.getLastRevision(), node.getPath(), - new DiffCache.Loader() { - @Override - public String call() { - return diffImpl(base, node); - } - }); - } - } - return dispatch(jsop, node, base, diff, useReadRevision); - } - - @CheckForNull - private String getJsopDiffIfLocalChange(NodeStateDiff diff, DocumentNodeState nodeState) { - if (localDiffCache == null){ - //Local diff cache support not enabled - return null; - } - - if (diff instanceof ContentChangeInfoProvider){ - ContentChangeInfo info = ((ContentChangeInfoProvider) diff).getChangeInfo(); - if (info.isLocalChange() && info.getAfter() instanceof DocumentNodeState){ - DocumentNodeState rootAfterState = (DocumentNodeState) info.getAfter(); - DocumentNodeState rootBeforeState = (DocumentNodeState) info.getBefore(); - String jsopDiff = localDiffCache.getChanges(rootBeforeState.getRevision(), - rootAfterState.getRevision(), - nodeState.getPath(), - null); - if (jsopDiff != null){ - LOG.trace("Got diff from local cache for path {}", nodeState.getPath()); - return jsopDiff; - } - } - } - return null; + return dispatch(diffCache.getChanges(base.getRootRevision(), + node.getRootRevision(), node.getPath(), + new DiffCache.Loader() { + @Override + public String call() { + return diffImpl(base, node); + } + }), node, base, diff); } String diff(@Nonnull final String fromRevisionId, @@ -1944,13 +1905,10 @@ public final class DocumentNodeStore private boolean dispatch(@Nonnull String jsonDiff, @Nonnull DocumentNodeState node, @Nonnull DocumentNodeState base, - @Nonnull NodeStateDiff diff, - boolean useReadRevision) { + @Nonnull NodeStateDiff diff) { if (jsonDiff.trim().isEmpty()) { return true; } - Revision nodeRev = useReadRevision ? node.getRevision() : node.getLastRevision(); - Revision baseRev = useReadRevision ? base.getRevision() : base.getLastRevision(); JsopTokenizer t = new JsopTokenizer(jsonDiff); boolean continueComparison = true; while (continueComparison) { @@ -1967,13 +1925,13 @@ public final class DocumentNodeStore // skip properties } continueComparison = diff.childNodeAdded(name, - node.getChildNode(name, nodeRev)); + node.getChildNode(name)); break; } case '-': { String name = unshareString(t.readString()); continueComparison = diff.childNodeDeleted(name, - base.getChildNode(name, baseRev)); + base.getChildNode(name)); break; } case '^': { @@ -1982,8 +1940,8 @@ public final class DocumentNodeStore if (t.matches('{')) { t.read('}'); continueComparison = diff.childNodeChanged(name, - base.getChildNode(name, baseRev), - node.getChildNode(name, nodeRev)); + base.getChildNode(name), + node.getChildNode(name)); } else if (t.matches('[')) { // ignore multi valued property while (t.read() != ']') { @@ -2084,29 +2042,32 @@ public final class DocumentNodeStore final long getChildrenDoneIn = debug ? now() : 0; String diffAlgo; + Revision fromRev = from.getLastRevision(); + Revision toRev = to.getLastRevision(); if (!fromChildren.hasMore && !toChildren.hasMore) { diffAlgo = "diffFewChildren"; diffFewChildren(w, from.getPath(), fromChildren, - from.getLastRevision(), toChildren, to.getLastRevision()); + fromRev, toChildren, toRev); } else { if (FAST_DIFF) { diffAlgo = "diffManyChildren"; - diffManyChildren(w, from.getPath(), - from.getLastRevision(), to.getLastRevision()); + fromRev = from.getRootRevision(); + toRev = to.getRootRevision(); + diffManyChildren(w, from.getPath(), fromRev, toRev); } else { diffAlgo = "diffAllChildren"; max = Integer.MAX_VALUE; fromChildren = getChildren(from, null, max); toChildren = getChildren(to, null, max); diffFewChildren(w, from.getPath(), fromChildren, - from.getLastRevision(), toChildren, to.getLastRevision()); + fromRev, toChildren, toRev); } } if (debug) { long end = now(); LOG.debug("Diff performed via '{}' at [{}] between revisions [{}] => [{}] took {} ms ({} ms)", - diffAlgo, from.getPath(), from.getLastRevision(), to.getLastRevision(), + diffAlgo, from.getPath(), fromRev, toRev, end - start, getChildrenDoneIn - start); } return w.toString(); @@ -2501,13 +2462,6 @@ public final class DocumentNodeStore return diffCache; } - public DiffCache getLocalDiffCache(){ - if (localDiffCache != null){ - return localDiffCache; - } - return diffCache; - } - public Clock getClock() { return clock; } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java index df06bfa..defcd28 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java @@ -56,6 +56,7 @@ import org.apache.felix.scr.annotations.ReferenceCardinality; import org.apache.felix.scr.annotations.ReferencePolicy; import org.apache.jackrabbit.oak.api.jmx.CacheStatsMBean; import org.apache.jackrabbit.oak.api.jmx.CheckpointMBean; +import org.apache.jackrabbit.oak.cache.CacheStats; import org.apache.jackrabbit.oak.commons.PropertiesUtil; import org.apache.jackrabbit.oak.osgi.ObserverTracker; import org.apache.jackrabbit.oak.osgi.OsgiWhiteboard; @@ -520,6 +521,23 @@ public class DocumentNodeStoreService { CacheStatsMBean.TYPE, store.getDocChildrenCacheStats().getName()) ); + for (CacheStats cs : store.getDiffCacheStats()) { + registrations.add( + registerMBean(whiteboard, + CacheStatsMBean.class, cs, + CacheStatsMBean.TYPE, cs.getName())); + } + DocumentStore ds = store.getDocumentStore(); + if (ds.getCacheStats() != null) { + registrations.add( + registerMBean(whiteboard, + CacheStatsMBean.class, + ds.getCacheStats(), + CacheStatsMBean.TYPE, + ds.getCacheStats().getName()) + ); + } + registrations.add( registerMBean(whiteboard, CheckpointMBean.class, @@ -536,39 +554,6 @@ public class DocumentNodeStoreService { "Document node store management") ); - DiffCache cl = store.getDiffCache(); - if (cl instanceof MemoryDiffCache) { - MemoryDiffCache mcl = (MemoryDiffCache) cl; - registrations.add( - registerMBean(whiteboard, - CacheStatsMBean.class, - mcl.getDiffCacheStats(), - CacheStatsMBean.TYPE, - mcl.getDiffCacheStats().getName())); - } - - DiffCache localCache = store.getLocalDiffCache(); - if (localCache instanceof LocalDiffCache) { - LocalDiffCache mcl = (LocalDiffCache) localCache; - registrations.add( - registerMBean(whiteboard, - CacheStatsMBean.class, - mcl.getDiffCacheStats(), - CacheStatsMBean.TYPE, - mcl.getDiffCacheStats().getName())); - } - - DocumentStore ds = store.getDocumentStore(); - if (ds.getCacheStats() != null) { - registrations.add( - registerMBean(whiteboard, - CacheStatsMBean.class, - ds.getCacheStats(), - CacheStatsMBean.TYPE, - ds.getCacheStats().getName()) - ); - } - if (store.getBlobStore() instanceof GarbageCollectableBlobStore) { BlobGarbageCollector gc = new BlobGarbageCollector() { @Override diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCache.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCache.java index 19181a1..d25518d 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCache.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCache.java @@ -1,22 +1,19 @@ /* - * 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 + * 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 + * 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. + * 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 java.util.Collections; @@ -25,31 +22,35 @@ import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import com.google.common.base.Joiner; -import com.google.common.base.Splitter; import com.google.common.cache.Cache; import com.google.common.collect.Maps; + import org.apache.jackrabbit.oak.cache.CacheStats; import org.apache.jackrabbit.oak.cache.CacheValue; +import org.apache.jackrabbit.oak.commons.json.JsopBuilder; +import org.apache.jackrabbit.oak.commons.json.JsopReader; +import org.apache.jackrabbit.oak.commons.json.JsopTokenizer; +import org.apache.jackrabbit.oak.plugins.document.util.RevisionsKey; import org.apache.jackrabbit.oak.plugins.document.util.StringValue; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +/** + * A diff cache, which is pro-actively filled after a commit. + */ public class LocalDiffCache implements DiffCache { + /** * Limit is arbitrary for now i.e. 16 MB. Same as in MongoDiffCache */ private static int MAX_ENTRY_SIZE = 16 * 1024 * 1024; - private static final String NO_DIFF = ""; - private final Logger log = LoggerFactory.getLogger(getClass()); - private final Cache diffCache; + + private final Cache diffCache; private final CacheStats diffCacheStats; - public LocalDiffCache(DocumentMK.Builder builder) { - diffCache = builder.buildConsolidatedDiffCache(); - diffCacheStats = new CacheStats(diffCache, "Document-Diff2", - builder.getWeigher(), builder.getDiffCacheSize()); + LocalDiffCache(DocumentMK.Builder builder) { + this.diffCache = builder.buildLocalDiffCache(); + this.diffCacheStats = new CacheStats(diffCache, + "Document-LocalDiff", + builder.getWeigher(), builder.getLocalDiffCacheSize()); } @Override @@ -57,27 +58,22 @@ public class LocalDiffCache implements DiffCache { @Nonnull Revision to, @Nonnull String path, @Nullable Loader loader) { - ConsolidatedDiff diff = diffCache.getIfPresent(new StringValue(to.toString())); - if (diff != null){ + RevisionsKey key = new RevisionsKey(from, to); + Diff diff = diffCache.getIfPresent(key); + if (diff != null) { String result = diff.get(path); - if (result == null){ - return NO_DIFF; - } - return result; - } else { - log.debug("Did not got the diff for local change in the cache for change {} => {} ", from, to); + return result != null ? result : ""; + } + if (loader != null) { + return loader.call(); } return null; } - ConsolidatedDiff getDiff(@Nonnull Revision from, - @Nonnull Revision to){ - return diffCache.getIfPresent(new StringValue(to.toString())); - } - @Nonnull @Override - public Entry newEntry(@Nonnull Revision from, final @Nonnull Revision to) { + public Entry newEntry(final @Nonnull Revision from, + final @Nonnull Revision to) { return new Entry() { private final Map changesPerPath = Maps.newHashMap(); private int size; @@ -95,7 +91,8 @@ public class LocalDiffCache implements DiffCache { if (exceedsSize()){ return false; } - diffCache.put(new StringValue(to.toString()), new ConsolidatedDiff(changesPerPath, size)); + diffCache.put(new RevisionsKey(from, to), + new Diff(changesPerPath, size)); return true; } @@ -105,33 +102,55 @@ public class LocalDiffCache implements DiffCache { }; } - public CacheStats getDiffCacheStats() { - return diffCacheStats; + @Nonnull + @Override + public Iterable getStats() { + return Collections.singleton(diffCacheStats); } - public static final class ConsolidatedDiff implements CacheValue{ - //TODO need to come up with better serialization strategy as changes are json themselves - //cannot use JSON. '/' and '*' are considered invalid chars so would not - //cause issue - static final Joiner.MapJoiner mapJoiner = Joiner.on("//").withKeyValueSeparator("**"); - static final Splitter.MapSplitter splitter = Splitter.on("//").withKeyValueSeparator("**"); + //-----------------------------< internal >--------------------------------- + + + public static final class Diff implements CacheValue { + private final Map changes; private int memory; - public ConsolidatedDiff(Map changes, int memory) { + public Diff(Map changes, int memory) { this.changes = changes; this.memory = memory; } - public static ConsolidatedDiff fromString(String value){ - if (value.isEmpty()){ - return new ConsolidatedDiff(Collections.emptyMap(), 0); + public static Diff fromString(String value) { + Map map = Maps.newHashMap(); + JsopReader reader = new JsopTokenizer(value); + while (true) { + if (reader.matches(JsopReader.END)) { + break; + } + String k = reader.readString(); + reader.read(':'); + String v = reader.readString(); + map.put(k, v); + if (reader.matches(JsopReader.END)) { + break; + } + reader.read(','); } - return new ConsolidatedDiff(splitter.split(value), 0); + return new Diff(map, 0); } public String asString(){ - return mapJoiner.join(changes); + JsopBuilder builder = new JsopBuilder(); + for (Map.Entry entry : changes.entrySet()) { + builder.key(entry.getKey()); + builder.value(entry.getValue()); + } + return builder.toString(); + } + + public Map getChanges() { + return Collections.unmodifiableMap(changes); } @Override @@ -146,38 +165,29 @@ public class LocalDiffCache implements DiffCache { return memory; } - @Override - public String toString() { - return changes.toString(); - } - String get(String path) { return changes.get(path); } - @SuppressWarnings("RedundantIfStatement") @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ConsolidatedDiff that = (ConsolidatedDiff) o; - - if (!changes.equals(that.changes)) return false; - - return true; + public String toString() { + return asString(); } @Override - public int hashCode() { - return changes.hashCode(); + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Diff) { + Diff other = (Diff) obj; + return changes.equals(other.changes); + } + return false; } } private static int size(String s){ - //Taken from StringValue - return 16 // shallow size - + 40 + s.length() * 2; + return StringValue.getMemory(s); } - } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java index 0bdb180..f962e00 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java @@ -16,6 +16,7 @@ */ package org.apache.jackrabbit.oak.plugins.document; +import java.util.Collections; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; @@ -45,9 +46,9 @@ public class MemoryDiffCache implements DiffCache { protected MemoryDiffCache(DocumentMK.Builder builder) { - diffCache = builder.buildDiffCache(); - diffCacheStats = new CacheStats(diffCache, "Document-Diff", - builder.getWeigher(), builder.getDiffCacheSize()); + diffCache = builder.buildMemoryDiffCache(); + diffCacheStats = new CacheStats(diffCache, "Document-MemoryDiff", + builder.getWeigher(), builder.getMemoryDiffCacheSize()); } @CheckForNull @@ -83,8 +84,10 @@ public class MemoryDiffCache implements DiffCache { return new MemoryEntry(from, to); } - public CacheStats getDiffCacheStats() { - return diffCacheStats; + @Nonnull + @Override + public Iterable getStats() { + return Collections.singleton(diffCacheStats); } protected class MemoryEntry implements Entry { diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/TieredDiffCache.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/TieredDiffCache.java new file mode 100644 index 0000000..df227d2 --- /dev/null +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/TieredDiffCache.java @@ -0,0 +1,71 @@ +/* + * 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 javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.common.collect.Iterables; + +import org.apache.jackrabbit.oak.cache.CacheStats; + +/** + * Implements a tiered diff cache which consists of a {@link LocalDiffCache} and + * a {@link MemoryDiffCache}. + */ +class TieredDiffCache implements DiffCache { + + private final LocalDiffCache localCache; + private final MemoryDiffCache memoryCache; + + TieredDiffCache(DocumentMK.Builder builder) { + this.localCache = new LocalDiffCache(builder); + this.memoryCache = new MemoryDiffCache(builder); + } + + @Override + public String getChanges(@Nonnull Revision from, + @Nonnull Revision to, + @Nonnull String path, + @Nullable Loader loader) { + // check local first without loader + String changes = localCache.getChanges(from, to, path, null); + if (changes != null) { + return changes; + } + return memoryCache.getChanges(from, to, path, loader); + } + + /** + * Creates a new entry in the {@link LocalDiffCache} only! + * + * @param from the from revision. + * @param to the to revision. + * @return the new entry. + */ + @Nonnull + @Override + public Entry newEntry(@Nonnull Revision from, @Nonnull Revision to) { + return localCache.newEntry(from, to); + } + + @Nonnull + @Override + public Iterable getStats() { + return Iterables.concat(localCache.getStats(), memoryCache.getStats()); + } +} diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheType.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheType.java index 3f4b3cb..3b16189 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheType.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheType.java @@ -22,6 +22,7 @@ import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore; import org.apache.jackrabbit.oak.plugins.document.DocumentStore; import org.apache.jackrabbit.oak.plugins.document.NodeDocument; import org.apache.jackrabbit.oak.plugins.document.PathRev; +import org.apache.jackrabbit.oak.plugins.document.util.RevisionsKey; import org.apache.jackrabbit.oak.plugins.document.util.StringValue; public enum CacheType { @@ -104,7 +105,7 @@ public enum CacheType { } }, - CONSOLIDATED_DIFF { + DOC_CHILDREN { @Override public String keyToString(K key) { return ((StringValue) key).asString(); @@ -117,20 +118,20 @@ public enum CacheType { @Override public int compareKeys(K a, K b) { return ((StringValue) a).asString().compareTo(((StringValue) b).asString()); - } + } @Override public String valueToString(V value) { - return ((LocalDiffCache.ConsolidatedDiff) value).asString(); + return ((NodeDocument.Children) value).asString(); } @SuppressWarnings("unchecked") @Override public V valueFromString( DocumentNodeStore store, DocumentStore docStore, String value) { - return (V) LocalDiffCache.ConsolidatedDiff.fromString(value); + return (V) NodeDocument.Children.fromString(value); } - }, + }, - DOC_CHILDREN { + DOCUMENT { @Override public String keyToString(K key) { return ((StringValue) key).asString(); @@ -146,42 +147,42 @@ public enum CacheType { } @Override public String valueToString(V value) { - return ((NodeDocument.Children) value).asString(); + return ((NodeDocument) value).asString(); } @SuppressWarnings("unchecked") @Override public V valueFromString( DocumentNodeStore store, DocumentStore docStore, String value) { - return (V) NodeDocument.Children.fromString(value); + return (V) NodeDocument.fromString(docStore, value); } - }, - - DOCUMENT { + }, + + LOCAL_DIFF { @Override public String keyToString(K key) { - return ((StringValue) key).asString(); + return ((RevisionsKey) key).asString(); } @SuppressWarnings("unchecked") @Override public K keyFromString(String key) { - return (K) StringValue.fromString(key); + return (K) RevisionsKey.fromString(key); } @Override public int compareKeys(K a, K b) { - return ((StringValue) a).asString().compareTo(((StringValue) b).asString()); - } + return ((RevisionsKey) a).compareTo((RevisionsKey) b); + } @Override public String valueToString(V value) { - return ((NodeDocument) value).asString(); + return ((LocalDiffCache.Diff) value).asString(); } @SuppressWarnings("unchecked") @Override public V valueFromString( DocumentNodeStore store, DocumentStore docStore, String value) { - return (V) NodeDocument.fromString(docStore, value); + return (V) LocalDiffCache.Diff.fromString(value); } - }; - + }; + public abstract String keyToString(K key); public abstract K keyFromString(String key); public abstract int compareKeys(K a, K b); diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCache.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCache.java index bbd9c8a..7f48c44 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCache.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCache.java @@ -49,6 +49,7 @@ public class PersistentCache { private boolean cacheNodes = true; private boolean cacheChildren = true; private boolean cacheDiff = true; + private boolean cacheLocalDiff = true; private boolean cacheDocs; private boolean cacheDocChildren; private boolean compactOnClose; @@ -82,6 +83,8 @@ public class PersistentCache { cacheChildren = false; } else if (p.equals("-diff")) { cacheDiff = false; + } else if (p.equals("-localDiff")) { + cacheLocalDiff = false; } else if (p.equals("+all")) { cacheDocs = true; cacheDocChildren = true; @@ -305,8 +308,8 @@ public class PersistentCache { case DIFF: wrap = cacheDiff; break; - case CONSOLIDATED_DIFF: - wrap = cacheDiff; + case LOCAL_DIFF: + wrap = cacheLocalDiff; break; case DOC_CHILDREN: wrap = cacheDocChildren; diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/RevisionsKey.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/RevisionsKey.java new file mode 100644 index 0000000..25394c3 --- /dev/null +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/RevisionsKey.java @@ -0,0 +1,84 @@ +/* + * 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.util; + +import javax.annotation.Nonnull; + +import org.apache.jackrabbit.oak.cache.CacheValue; +import org.apache.jackrabbit.oak.plugins.document.Revision; +import org.apache.jackrabbit.oak.plugins.document.StableRevisionComparator; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A cache key implementation which consists of two {@link Revision}s. + */ +public final class RevisionsKey implements CacheValue, Comparable { + + private final Revision r1, r2; + + public RevisionsKey(Revision r1, Revision r2) { + this.r1 = checkNotNull(r1); + this.r2 = checkNotNull(r2); + } + + @Override + public int getMemory() { + return 88; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof RevisionsKey)) { + return false; + } + RevisionsKey other = (RevisionsKey) obj; + return r1.equals(other.r1) && r2.equals(other.r2); + } + + @Override + public int hashCode() { + return r1.hashCode() ^ r2.hashCode(); + } + + @Override + public String toString() { + return asString(); + } + + public String asString() { + return r1 + "/" + r2; + } + + public int compareTo(@Nonnull RevisionsKey k) { + int c = StableRevisionComparator.INSTANCE.compare(r1, k.r1); + if (c != 0) { + return c; + } + return StableRevisionComparator.INSTANCE.compare(r2, k.r2); + } + + public static RevisionsKey fromString(String s) { + int idx = s.indexOf('/'); + if (idx == -1) { + throw new IllegalArgumentException(s); + } + return new RevisionsKey( + Revision.fromString(s.substring(0, idx)), + Revision.fromString(s.substring(idx + 1))); + } +} diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/StringValue.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/StringValue.java index be05538..78e36b0 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/StringValue.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/StringValue.java @@ -37,8 +37,12 @@ public final class StringValue implements CacheValue { @Override public int getMemory() { + return getMemory(value); + } + + public static int getMemory(@Nonnull String s) { return 16 // shallow size - + 40 + value.length() * 2; // value + + 40 + s.length() * 2; // value } @Override diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ContentChangeInfo.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ContentChangeInfo.java deleted file mode 100644 index 2305a40..0000000 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ContentChangeInfo.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.observation; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.apache.jackrabbit.oak.spi.commit.CommitInfo; -import org.apache.jackrabbit.oak.spi.state.NodeState; - -public class ContentChangeInfo { - private final NodeState before; - private final NodeState after; - private final CommitInfo commitInfo; - - public ContentChangeInfo(@Nonnull NodeState before, - @Nonnull NodeState after, - @Nullable CommitInfo commitInfo) { - this.before = before; - this.after = after; - this.commitInfo = commitInfo; - } - - public NodeState getBefore() { - return before; - } - - public NodeState getAfter() { - return after; - } - - public boolean isLocalChange(){ - return commitInfo != null; - } -} diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ContentChangeInfoProvider.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ContentChangeInfoProvider.java deleted file mode 100644 index 2ecee70..0000000 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ContentChangeInfoProvider.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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.observation; - -public interface ContentChangeInfoProvider { - - ContentChangeInfo getChangeInfo(); -} diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/EventGenerator.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/EventGenerator.java index 83d44da..f6abd03 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/EventGenerator.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/EventGenerator.java @@ -76,25 +76,18 @@ public class EventGenerator { private final LinkedList continuations = newLinkedList(); - private final ContentChangeInfo changeInfo; - /** * Creates a new generator instance. Changes to process need to be added * through {@link #addHandler(NodeState, NodeState, EventHandler)} - * @param changeInfo */ - public EventGenerator(ContentChangeInfo changeInfo) { - this.changeInfo = changeInfo; - } + public EventGenerator() {} /** * Creates a new generator instance for processing the given changes. */ public EventGenerator( @Nonnull NodeState before, @Nonnull NodeState after, - @Nonnull ContentChangeInfo changeInfo, @Nonnull EventHandler handler) { - this(changeInfo); continuations.addFirst(new Continuation(handler, before, after, 0)); } @@ -127,7 +120,7 @@ public class EventGenerator { } } - private class Continuation implements NodeStateDiff, Runnable, ContentChangeInfoProvider { + private class Continuation implements NodeStateDiff, Runnable { /** * Filtered handler of detected content changes. @@ -183,13 +176,6 @@ public class EventGenerator { } } - //-------------------------------------------------< ContentChangeInfoProvider >-- - - @Override - public ContentChangeInfo getChangeInfo() { - return changeInfo; - } - //-------------------------------------------------< NodeStateDiff >-- @Override diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/NodeObserver.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/NodeObserver.java index 6b9eec9..57072f3 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/NodeObserver.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/NodeObserver.java @@ -157,8 +157,7 @@ public abstract class NodeObserver implements Observer { handler = handler.getChildHandler(oakName, before, after); } - ContentChangeInfo changeInfo = new ContentChangeInfo(before, after, info); - EventGenerator generator = new EventGenerator(before, after, changeInfo, handler); + EventGenerator generator = new EventGenerator(before, after, handler); while (!generator.isDone()) { generator.generate(); } diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/AbstractMongoConnectionTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/AbstractMongoConnectionTest.java index c7fb23b..94bd960 100644 --- a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/AbstractMongoConnectionTest.java +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/AbstractMongoConnectionTest.java @@ -16,7 +16,6 @@ */ package org.apache.jackrabbit.oak.plugins.document; -import com.mongodb.DB; import org.apache.jackrabbit.oak.plugins.document.util.MongoConnection; import org.apache.jackrabbit.oak.stats.Clock; import org.junit.After; @@ -44,17 +43,13 @@ public abstract class AbstractMongoConnectionTest extends DocumentMKTestBase { mongoConnection = MongoUtils.getConnection(); MongoUtils.dropCollections(mongoConnection.getDB()); Revision.setClock(getTestClock()); - mk = prepare(new DocumentMK.Builder().clock(getTestClock()),mongoConnection.getDB()).open(); + mk = new DocumentMK.Builder().clock(getTestClock()).setMongoDB(mongoConnection.getDB()).open(); } protected Clock getTestClock() throws InterruptedException { return Clock.SIMPLE; } - protected DocumentMK.Builder prepare(DocumentMK.Builder builder, DB db){ - return builder.setMongoDB(db); - } - @After public void tearDownConnection() throws Exception { mk.dispose(); diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/AmnesiaDiffCache.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/AmnesiaDiffCache.java index 5cacaf9..2b696d5 100644 --- a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/AmnesiaDiffCache.java +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/AmnesiaDiffCache.java @@ -16,9 +16,13 @@ */ package org.apache.jackrabbit.oak.plugins.document; +import java.util.Collections; + import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.apache.jackrabbit.oak.cache.CacheStats; + /** * A diff cache implementation, which immediately forgets the diff. */ @@ -55,4 +59,10 @@ class AmnesiaDiffCache implements DiffCache { } }; } + + @Nonnull + @Override + public Iterable getStats() { + return Collections.emptyList(); + } } diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreDiffTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreDiffTest.java index 8b780a6..40e976a 100644 --- a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreDiffTest.java +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreDiffTest.java @@ -64,18 +64,16 @@ public class DocumentNodeStoreDiffTest extends AbstractMongoConnectionTest { merge(store, builder); } - CacheStats stats = ((MemoryDiffCache) store.getDiffCache()).getDiffCacheStats(); - stats.resetStats(); + Iterable stats = store.getDiffCacheStats(); + for (CacheStats cs : stats) { + cs.resetStats(); + } // must not cause cache misses Diff.perform(before, after); - assertEquals(0, stats.getMissCount()); - } - - @Override - protected DocumentMK.Builder prepare(DocumentMK.Builder builder, DB db) { - builder.setDisableLocalDiffCache(true); - return super.prepare(builder, db); + for (CacheStats cs : stats) { + assertEquals(0, cs.getMissCount()); + } } @Override diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java index 214d59f..e2bf48f 100644 --- a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java @@ -1672,6 +1672,90 @@ public class DocumentNodeStoreTest { ns.dispose(); } + @Test + public void rootRevision() throws Exception { + DocumentNodeStore ns = new DocumentMK.Builder().getNodeStore(); + + NodeBuilder builder = ns.getRoot().builder(); + builder.child("foo").child("child"); + builder.child("bar").child("child"); + merge(ns, builder); + + builder = ns.getRoot().builder(); + builder.child("foo").child("child").child("node"); + merge(ns, builder); + + Revision head = ns.getHeadRevision(); + NodeState child = ns.getRoot().getChildNode("bar").getChildNode("child"); + assertTrue(child instanceof DocumentNodeState); + DocumentNodeState state = (DocumentNodeState) child; + assertEquals(head, state.getRootRevision()); + + ns.dispose(); + } + + @Test + public void diffCache() throws Exception { + final AtomicInteger numQueries = new AtomicInteger(); + MemoryDocumentStore store = new MemoryDocumentStore() { + @Nonnull + @Override + public List query(Collection collection, + String fromKey, + String toKey, + int limit) { + numQueries.incrementAndGet(); + return super.query(collection, fromKey, toKey, limit); + } + }; + DocumentNodeStore ns = new DocumentMK.Builder() + .setDocumentStore(store).setAsyncDelay(0).getNodeStore(); + + NodeBuilder builder = ns.getRoot().builder(); + builder.child("foo").child("child"); + merge(ns, builder); + + builder = ns.getRoot().builder(); + builder.child("bar"); + merge(ns, builder); + + DocumentNodeState before = ns.getRoot(); + + builder = ns.getRoot().builder(); + builder.child("foo").child("child").child("node"); + merge(ns, builder); + + DocumentNodeState after = ns.getRoot(); + + numQueries.set(0); + final List added = Lists.newArrayList(); + ns.compare(asDocumentNodeState(after.getChildNode("foo").getChildNode("child")), + asDocumentNodeState(before.getChildNode("foo").getChildNode("child")), + new DefaultNodeStateDiff() { + @Override + public boolean childNodeAdded(String name, + NodeState after) { + added.add(name); + return super.childNodeAdded(name, after); + } + }); + + + assertEquals(1, added.size()); + assertEquals("node", added.get(0)); + assertEquals("must not run queries", 0, numQueries.get()); + + ns.dispose(); + + } + + private static DocumentNodeState asDocumentNodeState(NodeState state) { + if (!(state instanceof DocumentNodeState)) { + throw new IllegalArgumentException("Not a DocumentNodeState"); + } + return (DocumentNodeState) state; + } + private void doSomeChange(NodeStore ns) throws CommitFailedException { NodeBuilder b = ns.getRoot().builder(); b.setProperty("count", System.currentTimeMillis()); diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCacheTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCacheTest.java index 96469bf..9d960dd 100644 --- a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCacheTest.java +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCacheTest.java @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - package org.apache.jackrabbit.oak.plugins.document; import java.util.HashMap; @@ -25,15 +24,18 @@ import java.util.Set; import javax.annotation.Nonnull; +import com.google.common.collect.Maps; + import org.apache.jackrabbit.oak.api.CommitFailedException; import org.apache.jackrabbit.oak.cache.CacheStats; -import org.apache.jackrabbit.oak.plugins.document.LocalDiffCache.ConsolidatedDiff; +import org.apache.jackrabbit.oak.plugins.document.LocalDiffCache.Diff; import org.apache.jackrabbit.oak.plugins.document.memory.MemoryDocumentStore; import org.apache.jackrabbit.oak.plugins.observation.NodeObserver; 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.apache.jackrabbit.oak.spi.state.NodeStore; +import org.junit.After; import org.junit.Test; import static com.google.common.collect.Maps.newHashMap; @@ -45,59 +47,67 @@ public class LocalDiffCacheTest { DocumentNodeStore store; + @After + public void dispose() { + if (store != null) { + store.dispose(); + store = null; + } + } + @Test public void simpleDiff() throws Exception{ TestNodeObserver o = new TestNodeObserver("/"); - store = createMK().getNodeStore(); + store = createMK().getNodeStore(); store.addObserver(o); o.reset(); - LocalDiffCache cache = getLocalDiffCache(); - CacheStats stats = cache.getDiffCacheStats(); + DiffCache cache = store.getDiffCache(); + Iterable stats = cache.getStats(); - DocumentNodeState beforeState = store.getRoot(); NodeBuilder builder = store.getRoot().builder(); builder.child("a").child("a2").setProperty("foo", "bar"); builder.child("b"); - DocumentNodeState afterState = merge(store, builder); + merge(store, builder); - assertTrue(stats.getHitCount() > 0); - assertEquals(0, stats.getMissCount()); + assertTrue(getHitCount(stats) > 0); + assertEquals(0, getMissCount(stats)); assertEquals(3, o.added.size()); - ConsolidatedDiff diff = cache.getDiff(beforeState.getRevision(), afterState.getRevision()); - String serailized = diff.asString(); - ConsolidatedDiff diff2 = ConsolidatedDiff.fromString(serailized); - assertEquals(diff, diff2); - builder = store.getRoot().builder(); builder.child("a").child("a2").removeProperty("foo"); o.reset(); - stats.resetStats(); + resetStats(stats); merge(store, builder); - assertTrue(stats.getHitCount() > 0); - assertEquals(0, stats.getMissCount()); + assertTrue(getHitCount(stats) > 0); + assertEquals(0, getMissCount(stats)); assertEquals(1, o.changed.size()); + } - store.dispose(); + @Test + public void diffFromAsString() { + Map changes = Maps.newHashMap(); + changes.put("/", "+\"foo\":{}^\"bar\":{}-\"baz\""); + changes.put("/foo", ""); + changes.put("/bar", "+\"qux\""); + changes.put("/bar/qux", ""); + Diff diff = new Diff(changes, 0); + + assertEquals(changes, Diff.fromString(diff.asString()).getChanges()); } @Test public void emptyDiff() throws Exception{ Map changes = new HashMap(); - ConsolidatedDiff diff = new ConsolidatedDiff(changes, 100); + Diff diff = new Diff(changes, 100); String asString = diff.asString(); - ConsolidatedDiff diff2 = ConsolidatedDiff.fromString(asString); + Diff diff2 = Diff.fromString(asString); assertEquals(diff, diff2); } - private LocalDiffCache getLocalDiffCache(){ - return (LocalDiffCache) store.getLocalDiffCache(); - } - private static DocumentNodeState merge(NodeStore store, NodeBuilder builder) throws CommitFailedException { return (DocumentNodeState) store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); @@ -116,6 +126,28 @@ public class LocalDiffCacheTest { .open(); } + private static long getHitCount(Iterable stats) { + long hitCount = 0; + for (CacheStats cs : stats) { + hitCount += cs.getHitCount(); + } + return hitCount; + } + + private static long getMissCount(Iterable stats) { + long missCount = 0; + for (CacheStats cs : stats) { + missCount += cs.getMissCount(); + } + return missCount; + } + + private static void resetStats(Iterable stats) { + for (CacheStats cs : stats) { + cs.resetStats(); + } + } + //------------------------------------------------------------< TestNodeObserver >--- private static class TestNodeObserver extends NodeObserver { diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/MeasureMemory.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/MeasureMemory.java index 572f088..fbf657d 100644 --- a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/MeasureMemory.java +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/MeasureMemory.java @@ -23,6 +23,8 @@ import java.util.LinkedList; import java.util.concurrent.Callable; import com.mongodb.BasicDBObject; + +import org.apache.jackrabbit.oak.plugins.document.util.RevisionsKey; import org.apache.jackrabbit.oak.plugins.document.util.Utils; import org.junit.Test; @@ -126,6 +128,18 @@ public class MeasureMemory { }); } + @Test + public void revisionsKey() throws Exception { + measureMemory(new Callable() { + @Override + public Object[] call() { + RevisionsKey k = new RevisionsKey( + Revision.newRevision(0), Revision.newRevision(0)); + return new Object[]{k, k.getMemory() + OVERHEAD}; + } + }); + } + private static void measureMemory(Callable c) throws Exception { LinkedList list = new LinkedList(); long base = getMemoryUsed(); diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/RevisionsKeyTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/RevisionsKeyTest.java new file mode 100644 index 0000000..3883e20 --- /dev/null +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/RevisionsKeyTest.java @@ -0,0 +1,36 @@ +/* + * 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.util; + +import org.apache.jackrabbit.oak.plugins.document.Revision; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * RevisionsKeyTest... + */ +public class RevisionsKeyTest { + + @Test + public void fromAsString() { + RevisionsKey k1 = new RevisionsKey( + new Revision(1, 0, 1), new Revision(2, 1, 2)); + RevisionsKey k2 = RevisionsKey.fromString(k1.asString()); + assertEquals(k1, k2); + } +} diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/EventQueue.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/EventQueue.java index a7ba2fd..a716728 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/EventQueue.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/EventQueue.java @@ -29,7 +29,6 @@ import javax.jcr.observation.EventIterator; import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.namepath.NamePathMapper; -import org.apache.jackrabbit.oak.plugins.observation.ContentChangeInfo; import org.apache.jackrabbit.oak.plugins.observation.EventGenerator; import org.apache.jackrabbit.oak.plugins.observation.EventHandler; import org.apache.jackrabbit.oak.plugins.observation.FilteredHandler; @@ -52,7 +51,7 @@ class EventQueue implements EventIterator { @Nonnull NamePathMapper mapper, CommitInfo info, @Nonnull NodeState before, @Nonnull NodeState after, @Nonnull Iterable basePaths, @Nonnull EventFilter filter) { - this.generator = new EventGenerator(new ContentChangeInfo(before, after, info)); + this.generator = new EventGenerator(); EventFactory factory = new EventFactory(mapper, info); EventHandler handler = new FilteredHandler( filter, new QueueingHandler(this, factory, before, after));