Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java (revision 1758542) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java (working copy) @@ -568,6 +568,7 @@ private boolean useSimpleRevision; private long maxReplicationLagMillis = TimeUnit.HOURS.toMillis(6); private boolean disableBranches; + private boolean prefetchExternalChanges; private Clock clock = Clock.SIMPLE; private Executor executor; private String persistentCacheURI = DEFAULT_PERSISTENT_CACHE_URI; @@ -1027,6 +1028,15 @@ return disableBranches; } + public Builder setPrefetchExternalChanges(boolean b) { + prefetchExternalChanges = b; + return this; + } + + public boolean isPrefetchExternalChanges() { + return prefetchExternalChanges; + } + VersionGCSupport createVersionGCSupport() { DocumentStore store = getDocumentStore(); if (store instanceof MongoDocumentStore) { Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java (revision 1758542) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java (working copy) @@ -529,7 +529,9 @@ // initialize branchCommits branches.init(store, this); - dispatcher = new ChangeDispatcher(getRoot()); + dispatcher = builder.isPrefetchExternalChanges() ? + new PrefetchDispatcher(getRoot(), executor) : + new ChangeDispatcher(getRoot()); commitQueue = new CommitQueue(this); String threadNamePostfix = "(" + clusterId + ")"; batchCommitQueue = new BatchCommitQueue(store); Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java (revision 1758542) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java (working copy) @@ -253,6 +253,13 @@ ) public static final String PROP_JOURNAL_GC_BATCH_SIZE = "journalGcBatchSize"; + @Property (boolValue = false, + label = "Pre-fetch external changes", + description = "Boolean value indicating if external changes should " + + "be pre-fetched in a background thread." + ) + public static final String PROP_PREFETCH_EXTERNAL_CHANGES = "prefetchExternalChanges"; + private static final long MB = 1024 * 1024; private static enum DocumentStoreType { @@ -418,6 +425,7 @@ String journalCache = PropertiesUtil.toString(prop(PROP_JOURNAL_CACHE), DEFAULT_JOURNAL_CACHE); int cacheSegmentCount = toInteger(prop(PROP_CACHE_SEGMENT_COUNT), DEFAULT_CACHE_SEGMENT_COUNT); int cacheStackMoveDistance = toInteger(prop(PROP_CACHE_STACK_MOVE_DISTANCE), DEFAULT_CACHE_STACK_MOVE_DISTANCE); + boolean prefetchExternalChanges = toBoolean(prop(PROP_PREFETCH_EXTERNAL_CHANGES), false); DocumentMK.Builder mkBuilder = new DocumentMK.Builder(). setStatisticsProvider(statisticsProvider). @@ -450,7 +458,8 @@ // plan B succeeded. } } - }); + }). + setPrefetchExternalChanges(prefetchExternalChanges); if (persistentCache != null && persistentCache.length() > 0) { mkBuilder.setPersistentCache(persistentCache); Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/PrefetchDispatcher.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/PrefetchDispatcher.java (nonexistent) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/PrefetchDispatcher.java (working copy) @@ -0,0 +1,96 @@ +/* + * 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 java.util.concurrent.Executor; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.spi.commit.ChangeDispatcher; +import org.apache.jackrabbit.oak.spi.commit.CommitInfo; +import org.apache.jackrabbit.oak.spi.commit.DefaultEditor; +import org.apache.jackrabbit.oak.spi.commit.Editor; +import org.apache.jackrabbit.oak.spi.commit.EditorDiff; +import org.apache.jackrabbit.oak.spi.commit.VisibleEditor; +import org.apache.jackrabbit.oak.spi.state.NodeState; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A change dispatcher that pre-fetches visible external changes in a background + * task. + */ +class PrefetchDispatcher extends ChangeDispatcher { + + private final Executor executor; + private NodeState root; + + public PrefetchDispatcher(@Nonnull NodeState root, + @Nonnull Executor executor) { + super(root); + this.root = root; + this.executor = checkNotNull(executor); + } + + @Override + public synchronized void contentChanged(@Nonnull NodeState root, + @Nullable CommitInfo info) { + if (root instanceof DocumentNodeState) { + final DocumentNodeState state = (DocumentNodeState) root; + if (state.isFromExternalChange()) { + executor.execute(new Runnable() { + private final NodeState before = PrefetchDispatcher.this.root; + @Override + public void run() { + EditorDiff.process( + new VisibleEditor(TraversingEditor.INSTANCE), + before, state); + } + }); + } + } + super.contentChanged(root, info); + this.root = root; + } + + private static final class TraversingEditor extends DefaultEditor { + + static final Editor INSTANCE = new TraversingEditor(); + + @Override + public Editor childNodeAdded(String name, NodeState after) + throws CommitFailedException { + return this; + } + + @Override + public Editor childNodeChanged(String name, + NodeState before, + NodeState after) + throws CommitFailedException { + return this; + } + + @Override + public Editor childNodeDeleted(String name, NodeState before) + throws CommitFailedException { + return this; + } + } +} Property changes on: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/PrefetchDispatcher.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/PrefetchDispatcherTest.java =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/PrefetchDispatcherTest.java (nonexistent) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/PrefetchDispatcherTest.java (working copy) @@ -0,0 +1,82 @@ +/* + * 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 java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.Nonnull; + +import com.google.common.util.concurrent.MoreExecutors; + +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; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.junit.Rule; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class PrefetchDispatcherTest { + + @Rule + public DocumentMKBuilderProvider builderProvider = new DocumentMKBuilderProvider(); + + @Test + public void prefetchVisibleChanges() throws Exception { + final AtomicInteger numQueries = new AtomicInteger(); + MemoryDocumentStore store = new MemoryDocumentStore() { + @Nonnull + @Override + public List query(Collection collection, + String fromKey, + String toKey, + int limit) { + if (collection == Collection.NODES) { + numQueries.incrementAndGet(); + } + return super.query(collection, fromKey, toKey, limit); + } + }; + DocumentNodeStore ns1 = builderProvider.newBuilder() + .setDocumentStore(store).setClusterId(1) + .setPrefetchExternalChanges(false) + .setAsyncDelay(0).getNodeStore(); + DocumentNodeStore ns2 = builderProvider.newBuilder() + .setDocumentStore(store).setClusterId(2) + .setPrefetchExternalChanges(false) + .setAsyncDelay(0).getNodeStore(); + + NodeBuilder builder = ns1.getRoot().builder(); + builder.child("foo").child("bar").child("baz"); + builder.child(":hidden").child("foo").child("bar"); + ns1.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + ns1.runBackgroundOperations(); + + DocumentNodeState before = ns2.getRoot(); + ns2.runBackgroundOperations(); + DocumentNodeState after = ns2.getRoot().fromExternalChange(); + + PrefetchDispatcher dispatcher = new PrefetchDispatcher( + before, MoreExecutors.sameThreadExecutor()); + numQueries.set(0); + dispatcher.contentChanged(after, null); + // expect two queries for children: below /foo and /foo/bar + assertEquals(2, numQueries.get()); + } +} Property changes on: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/PrefetchDispatcherTest.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property