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);


Reply via email to