Author: tomekr
Date: Thu Jan 26 13:42:12 2017
New Revision: 1780391
URL: http://svn.apache.org/viewvc?rev=1780391&view=rev
Log:
OAK-5222: Optimize the multiplexing node store
-resolve node/builders children lazily
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/CopyOnReadIdentityMap.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeBuilder.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeState.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStore.java
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/CopyOnReadIdentityMap.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/CopyOnReadIdentityMap.java?rev=1780391&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/CopyOnReadIdentityMap.java
(added)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/CopyOnReadIdentityMap.java
Thu Jan 26 13:42:12 2017
@@ -0,0 +1,185 @@
+/*
+ * 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.multiplex;
+
+import com.google.common.base.Function;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This map wraps around the passed argument and caches all the returned
values.
+ * It is meant to be wrapped around the result of
+ * {@link com.google.common.collect.Maps#transformValues(Map, Function)} method
+ * or its variant. This allows to preserve the laziness of map transformation
and
+ * at the same time to avoid re-calculating the same values.
+ * <br>
+ * It's immutable and used IdentityHashMap for caching values.
+ *
+ * @param <K> - the type of keys maintained by this map
+ * @param <V> - the type of mapped values
+ */
+class CopyOnReadIdentityMap<K, V> implements Map<K, V> {
+
+ private final Map<K, V> map;
+
+ private Integer cachedSize;
+
+ private Boolean cachedIsEmpty;
+
+ private Map<K, V> cachedValues;
+
+ private boolean allValuesCached;
+
+ public CopyOnReadIdentityMap(Map<K, V> wrappedMap) {
+ this.map = wrappedMap;
+ }
+
+ @Override
+ public int size() {
+ if (allValuesCached) {
+ return cachedValues.size();
+ }
+ if (cachedSize == null) {
+ cachedSize = map.size();
+ }
+ return cachedSize;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ if (allValuesCached) {
+ return cachedValues.isEmpty();
+ }
+ if (cachedIsEmpty == null) {
+ if (cachedSize == null) {
+ cachedIsEmpty = map.isEmpty();
+ } else {
+ cachedIsEmpty = cachedSize > 0;
+ }
+ }
+ return cachedIsEmpty;
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ if (allValuesCached) {
+ return cachedValues.containsKey(key);
+ }
+ if (cachedValues != null && cachedValues.containsKey(key)) {
+ return true;
+ } else {
+ return map.containsKey(key);
+ }
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ if (allValuesCached) {
+ return cachedValues.containsValue(value);
+ }
+ for (K k : keySet()) {
+ V v = get(k);
+ if (value == null && v == null) {
+ return true;
+ } else if (value != null && value.equals(v)) {
+ return true;
+ }
+ }
+ if (cachedValues == null) {
+ cachedValues = Collections.emptyMap();
+ }
+ allValuesCached = true;
+ return false;
+ }
+
+ @Override
+ public V get(Object key) {
+ if (allValuesCached) {
+ return cachedValues.get(key);
+ }
+ if (cachedValues != null && cachedValues.containsKey(key)) {
+ return cachedValues.get(key);
+ } else if (map.containsKey(key)) {
+ initCachedValues();
+ V v = map.get(key);
+ cachedValues.put((K) key, v);
+ return v;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public V put(K key, V value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V remove(Object key) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void putAll(Map<? extends K, ? extends V> m) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Set<K> keySet() {
+ return map.keySet();
+ }
+
+ @Override
+ public Collection<V> values() {
+ readAll();
+ return cachedValues.values();
+ }
+
+ @Override
+ public Set<Entry<K, V>> entrySet() {
+ readAll();
+ return cachedValues.entrySet();
+ }
+
+ private void readAll() {
+ if (allValuesCached) {
+ return;
+ }
+ initCachedValues();
+ for (Entry<K, V> e : map.entrySet()) {
+ if (!cachedValues.containsKey(e.getKey())) {
+ cachedValues.put(e.getKey(), e.getValue());
+ }
+ }
+ allValuesCached = true;
+ }
+
+ private void initCachedValues() {
+ if (cachedValues == null) {
+ cachedValues = new IdentityHashMap<>(map.size());
+ }
+ }
+}
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeBuilder.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeBuilder.java?rev=1780391&r1=1780390&r2=1780391&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeBuilder.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeBuilder.java
Thu Jan 26 13:42:12 2017
@@ -18,6 +18,7 @@ package org.apache.jackrabbit.oak.plugin
import com.google.common.base.Function;
import com.google.common.base.Objects;
+import com.google.common.collect.Maps;
import org.apache.jackrabbit.oak.api.Blob;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Type;
@@ -29,17 +30,16 @@ import org.apache.jackrabbit.oak.spi.sta
import java.io.IOException;
import java.io.InputStream;
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.collect.ImmutableMap.copyOf;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.transform;
-import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Maps.transformValues;
import static java.lang.Long.MAX_VALUE;
import static java.util.Collections.singleton;
@@ -54,7 +54,7 @@ class MultiplexingNodeBuilder implements
private final MultiplexingContext ctx;
- private final Map<MountedNodeStore, NodeBuilder> nodeBuilders;
+ private Map<MountedNodeStore, NodeBuilder> nodeBuilders;
private final MountedNodeStore owningStore;
@@ -70,7 +70,7 @@ class MultiplexingNodeBuilder implements
checkArgument(nodeBuilders.size() == ctx.getStoresCount(), "Got %s
builders but the context manages %s stores", nodeBuilders.size(),
ctx.getStoresCount());
this.path = path;
this.ctx = ctx;
- this.nodeBuilders = newHashMap(nodeBuilders);
+ this.nodeBuilders = new CopyOnReadIdentityMap<>(nodeBuilders);
this.owningStore = ctx.getOwningStore(path);
this.parent = parent;
if (parent == null) {
@@ -86,7 +86,7 @@ class MultiplexingNodeBuilder implements
@Override
public NodeState getNodeState() {
- return new MultiplexingNodeState(path,
buildersToNodeStates(nodeBuilders), ctx);
+ return new MultiplexingNodeState(path, new
IdentityHashMap<>(buildersToNodeStates(nodeBuilders)), ctx);
}
@Override
@@ -95,7 +95,7 @@ class MultiplexingNodeBuilder implements
}
private static Map<MountedNodeStore, NodeState>
buildersToNodeStates(Map<MountedNodeStore, NodeBuilder> builders) {
- return copyOf(transformValues(builders, new Function<NodeBuilder,
NodeState>() {
+ return transformValues(builders, new Function<NodeBuilder,
NodeState>() {
@Override
public NodeState apply(NodeBuilder input) {
if (input.exists()) {
@@ -104,16 +104,16 @@ class MultiplexingNodeBuilder implements
return MISSING_NODE;
}
}
- }));
+ });
}
private static Map<MountedNodeStore, NodeState>
buildersToBaseStates(Map<MountedNodeStore, NodeBuilder> builders) {
- return copyOf(transformValues(builders, new Function<NodeBuilder,
NodeState>() {
+ return transformValues(builders, new Function<NodeBuilder,
NodeState>() {
@Override
public NodeState apply(NodeBuilder input) {
return input.getBaseState();
}
- }));
+ });
}
// node or property-related methods ; directly delegate to wrapped builder
@@ -247,7 +247,7 @@ class MultiplexingNodeBuilder implements
@Override
public boolean hasChildNode(String name) {
- String childPath = PathUtils.concat(path, name);
+ String childPath = simpleConcat(path, name);
MountedNodeStore mountedStore = ctx.getOwningStore(childPath);
return nodeBuilders.get(mountedStore).hasChildNode(name);
}
@@ -266,20 +266,21 @@ class MultiplexingNodeBuilder implements
for (String element : PathUtils.elements(path)) {
builder = builder.child(element);
}
+ if (nodeBuilders instanceof CopyOnReadIdentityMap) {
+ nodeBuilders = new IdentityHashMap<>(nodeBuilders);
+ }
nodeBuilders.put(mountedNodeStore, builder);
}
@Override
- public NodeBuilder getChildNode(String name) {
- String childPath = PathUtils.concat(path, name);
- MountedNodeStore mountedStore = ctx.getOwningStore(childPath);
- if (!nodeBuilders.get(mountedStore).hasChildNode(name)) {
- return MISSING_NODE.builder();
- }
- Map<MountedNodeStore, NodeBuilder> newNodeBuilders = newHashMap();
- for (MountedNodeStore mns : ctx.getAllMountedNodeStores()) {
- newNodeBuilders.put(mns, nodeBuilders.get(mns).getChildNode(name));
- }
+ public NodeBuilder getChildNode(final String name) {
+ String childPath = simpleConcat(path, name);
+ Map<MountedNodeStore, NodeBuilder> newNodeBuilders =
Maps.transformValues(nodeBuilders, new Function<NodeBuilder, NodeBuilder>() {
+ @Override
+ public NodeBuilder apply(NodeBuilder input) {
+ return input.getChildNode(name);
+ }
+ });
return new MultiplexingNodeBuilder(childPath, newNodeBuilders, ctx,
this);
}
@@ -289,23 +290,25 @@ class MultiplexingNodeBuilder implements
}
@Override
- public NodeBuilder setChildNode(String name, NodeState nodeState) {
+ public NodeBuilder setChildNode(final String name, NodeState nodeState) {
checkState(exists(), "This builder does not exist: " +
PathUtils.getName(path));
-
- String childPath = PathUtils.concat(path, name);
- MountedNodeStore childStore = ctx.getOwningStore(childPath);
+ String childPath = simpleConcat(path, name);
+ final MountedNodeStore childStore = ctx.getOwningStore(childPath);
if (childStore != owningStore &&
!nodeBuilders.get(childStore).exists()) {
createAncestors(childStore);
}
- NodeBuilder childBuilder =
nodeBuilders.get(childStore).setChildNode(name, nodeState);
+ final NodeBuilder childBuilder =
nodeBuilders.get(childStore).setChildNode(name, nodeState);
- Map<MountedNodeStore, NodeBuilder> newNodeBuilders = newHashMap();
- newNodeBuilders.put(childStore, childBuilder);
- for (MountedNodeStore mns : ctx.getAllMountedNodeStores()) {
- if (!newNodeBuilders.containsKey(mns)) {
- newNodeBuilders.put(mns,
nodeBuilders.get(mns).getChildNode(name));
+ Map<MountedNodeStore, NodeBuilder> newNodeBuilders =
Maps.transformEntries(nodeBuilders, new Maps.EntryTransformer<MountedNodeStore,
NodeBuilder, NodeBuilder>() {
+ @Override
+ public NodeBuilder transformEntry(MountedNodeStore key,
NodeBuilder value) {
+ if (key == childStore) {
+ return childBuilder;
+ } else {
+ return value.getChildNode(name);
+ }
}
- }
+ });
return new MultiplexingNodeBuilder(childPath, newNodeBuilders, ctx,
this);
}
@@ -392,7 +395,29 @@ class MultiplexingNodeBuilder implements
return !node.exists();
}
- private String getPath() {
+ String getPath() {
return path;
}
+
+ /**
+ * This simplified version of {@link PathUtils#concat(String, String)}
method
+ * assumes that the parentPath is valid and not null, while the second
argument
+ * is just a name (not a subpath).
+ *
+ * @param parentPath the parent path
+ * @param name name to concatenate
+ * @return the parentPath concatenated with name
+ */
+ static String simpleConcat(String parentPath, String name) {
+ checkValidName(name);
+ if (PathUtils.denotesRoot(parentPath)) {
+ return parentPath + name;
+ } else {
+ return new StringBuilder(parentPath.length() + name.length() + 1)
+ .append(parentPath)
+ .append('/')
+ .append(name)
+ .toString();
+ }
+ }
}
\ No newline at end of file
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeState.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeState.java?rev=1780391&r1=1780390&r2=1780391&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeState.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeState.java
Thu Jan 26 13:42:12 2017
@@ -19,6 +19,7 @@
package org.apache.jackrabbit.oak.plugins.multiplex;
import com.google.common.base.Function;
+import com.google.common.collect.Maps;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.plugins.memory.MemoryChildNodeEntry;
@@ -33,15 +34,13 @@ import java.util.Map;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Predicates.compose;
-import static com.google.common.collect.ImmutableMap.copyOf;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.transform;
-import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Maps.transformValues;
import static java.lang.Long.MAX_VALUE;
import static java.util.Collections.singleton;
-import static
org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.MISSING_NODE;
+import static
org.apache.jackrabbit.oak.plugins.multiplex.MultiplexingNodeBuilder.simpleConcat;
import static org.apache.jackrabbit.oak.spi.state.ChildNodeEntry.GET_NAME;
class MultiplexingNodeState extends AbstractNodeState {
@@ -73,7 +72,7 @@ class MultiplexingNodeState extends Abst
checkArgument(nodeStates.size() == ctx.getStoresCount(), "Got %s node
states but the context manages %s stores", nodeStates.size(),
ctx.getStoresCount());
this.path = path;
this.ctx = ctx;
- this.nodeStates = copyOf(nodeStates);
+ this.nodeStates = new CopyOnReadIdentityMap<>(nodeStates);
this.owningStore = ctx.getOwningStore(path);
}
@@ -106,23 +105,20 @@ class MultiplexingNodeState extends Abst
// child node operations
@Override
public boolean hasChildNode(String name) {
- String childPath = PathUtils.concat(path, name);
+ String childPath = simpleConcat(path, name);
MountedNodeStore mountedStore = ctx.getOwningStore(childPath);
return nodeStates.get(mountedStore).hasChildNode(name);
}
@Override
- public NodeState getChildNode(String name) {
- String childPath = PathUtils.concat(path, name);
- MountedNodeStore mountedStore = ctx.getOwningStore(childPath);
- if (!nodeStates.get(mountedStore).hasChildNode(name)) {
- return MISSING_NODE;
- }
-
- Map<MountedNodeStore, NodeState> newNodeStates = newHashMap();
- for (MountedNodeStore mns : ctx.getAllMountedNodeStores()) {
- newNodeStates.put(mns, nodeStates.get(mns).getChildNode(name));
- }
+ public NodeState getChildNode(final String name) {
+ String childPath = simpleConcat(path, name);
+ Map<MountedNodeStore, NodeState> newNodeStates =
Maps.transformValues(nodeStates, new Function<NodeState, NodeState>() {
+ @Override
+ public NodeState apply(NodeState input) {
+ return input.getChildNode(name);
+ }
+ });
return new MultiplexingNodeState(childPath, newNodeStates, ctx);
}
@@ -201,12 +197,12 @@ class MultiplexingNodeState extends Abst
// write operations
@Override
public NodeBuilder builder() {
- Map<MountedNodeStore, NodeBuilder> nodeBuilders =
copyOf(transformValues(nodeStates, new Function<NodeState, NodeBuilder>() {
+ Map<MountedNodeStore, NodeBuilder> nodeBuilders =
transformValues(nodeStates, new Function<NodeState, NodeBuilder>() {
@Override
public NodeBuilder apply(NodeState input) {
return input.builder();
}
- }));
+ });
return new MultiplexingNodeBuilder(path, nodeBuilders, ctx);
}
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStore.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStore.java?rev=1780391&r1=1780390&r2=1780391&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStore.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStore.java
Thu Jan 26 13:42:12 2017
@@ -21,6 +21,7 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.apache.jackrabbit.oak.api.Blob;
import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.spi.commit.CommitHook;
import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
@@ -118,6 +119,9 @@ public class MultiplexingNodeStore imple
public NodeState merge(NodeBuilder builder, CommitHook commitHook,
CommitInfo info) throws CommitFailedException {
checkArgument(builder instanceof MultiplexingNodeBuilder);
MultiplexingNodeBuilder nodeBuilder = (MultiplexingNodeBuilder)
builder;
+ if (!PathUtils.denotesRoot(nodeBuilder.getPath())) {
+ throw new IllegalArgumentException();
+ }
// run commit hooks and apply the changes to the builder instance
NodeState processed = commitHook.processCommit(getRoot(),
rebase(nodeBuilder), info);