Author: thomasm
Date: Wed Oct 2 10:06:44 2013
New Revision: 1528405
URL: http://svn.apache.org/r1528405
Log:
OAK-1059 Property index: faster unique indexes using new storage strategy (2nd
try)
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/UniqueEntryStoreStrategy.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java?rev=1528405&r1=1528404&r2=1528405&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java
Wed Oct 2 10:06:44 2013
@@ -45,6 +45,7 @@ import org.apache.jackrabbit.oak.plugins
import org.apache.jackrabbit.oak.plugins.index.IndexEditor;
import
org.apache.jackrabbit.oak.plugins.index.property.strategy.ContentMirrorStoreStrategy;
import
org.apache.jackrabbit.oak.plugins.index.property.strategy.IndexStoreStrategy;
+import
org.apache.jackrabbit.oak.plugins.index.property.strategy.UniqueEntryStoreStrategy;
import org.apache.jackrabbit.oak.spi.commit.Editor;
import org.apache.jackrabbit.oak.spi.query.PropertyValues;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
@@ -59,9 +60,13 @@ import org.apache.jackrabbit.oak.spi.sta
class PropertyIndexEditor implements IndexEditor {
/** Index storage strategy */
- private static final IndexStoreStrategy STORE =
+ private static final IndexStoreStrategy MIRROR =
new ContentMirrorStoreStrategy();
+ /** Index storage strategy */
+ private static final IndexStoreStrategy UNIQUE =
+ new UniqueEntryStoreStrategy();
+
/** Parent editor, or {@code null} if this is the root editor. */
private final PropertyIndexEditor parent;
@@ -79,6 +84,8 @@ class PropertyIndexEditor implements Ind
private final Set<String> primaryTypes;
private final Set<String> mixinTypes;
+
+ private final boolean unique;
private final Set<String> keysToCheckForUniqueness;
@@ -133,8 +140,10 @@ class PropertyIndexEditor implements Ind
// keep track of modified keys for uniqueness checks
if (definition.getBoolean(IndexConstants.UNIQUE_PROPERTY_NAME)) {
+ unique = true;
this.keysToCheckForUniqueness = newHashSet();
} else {
+ unique = false;
this.keysToCheckForUniqueness = null;
}
}
@@ -147,6 +156,7 @@ class PropertyIndexEditor implements Ind
this.propertyNames = parent.propertyNames;
this.primaryTypes = parent.primaryTypes;
this.mixinTypes = parent.mixinTypes;
+ this.unique = parent.unique;
this.keysToCheckForUniqueness = parent.keysToCheckForUniqueness;
}
@@ -205,6 +215,10 @@ class PropertyIndexEditor implements Ind
}
}
+ private static IndexStoreStrategy getStrategy(boolean unique) {
+ return unique ? UNIQUE : MIRROR;
+ }
+
@Override
public void leave(NodeState before, NodeState after)
throws CommitFailedException {
@@ -217,9 +231,9 @@ class PropertyIndexEditor implements Ind
if (!beforeKeys.isEmpty() || !afterKeys.isEmpty()) {
NodeBuilder index = definition.child(INDEX_CONTENT_NODE_NAME);
- STORE.update(index, getPath(), beforeKeys, afterKeys);
- if (keysToCheckForUniqueness != null) {
+ getStrategy(unique).update(index, getPath(), beforeKeys,
afterKeys);
+ if (unique) {
keysToCheckForUniqueness.addAll(afterKeys);
}
}
@@ -230,11 +244,11 @@ class PropertyIndexEditor implements Ind
definition.child(INDEX_CONTENT_NODE_NAME);
// check uniqueness constraints when leaving the root
- if (keysToCheckForUniqueness != null
- && !keysToCheckForUniqueness.isEmpty()) {
+ if (unique && !keysToCheckForUniqueness.isEmpty()) {
NodeState indexMeta = definition.getNodeState();
+ IndexStoreStrategy s = getStrategy(unique);
for (String key : keysToCheckForUniqueness) {
- if (STORE.count(indexMeta, singleton(key), 2) > 1) {
+ if (s.count(indexMeta, singleton(key), 2) > 1) {
throw new CommitFailedException(
CONSTRAINT, 30,
"Uniqueness constraint violated for key " +
key);
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java?rev=1528405&r1=1528404&r2=1528405&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java
Wed Oct 2 10:06:44 2013
@@ -34,8 +34,10 @@ import org.apache.jackrabbit.oak.api.Pro
import org.apache.jackrabbit.oak.api.PropertyValue;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
import
org.apache.jackrabbit.oak.plugins.index.property.strategy.ContentMirrorStoreStrategy;
import
org.apache.jackrabbit.oak.plugins.index.property.strategy.IndexStoreStrategy;
+import
org.apache.jackrabbit.oak.plugins.index.property.strategy.UniqueEntryStoreStrategy;
import org.apache.jackrabbit.oak.spi.query.Filter;
import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
import org.apache.jackrabbit.oak.spi.state.NodeState;
@@ -60,7 +62,13 @@ public class PropertyIndexLookup {
private static final int MAX_COST = 100;
- private final IndexStoreStrategy store = new ContentMirrorStoreStrategy();
+ /** Index storage strategy */
+ private static final IndexStoreStrategy MIRROR =
+ new ContentMirrorStoreStrategy();
+
+ /** Index storage strategy */
+ private static final IndexStoreStrategy UNIQUE =
+ new UniqueEntryStoreStrategy();
private final NodeState root;
@@ -99,7 +107,14 @@ public class PropertyIndexLookup {
if (indexMeta == null) {
throw new IllegalArgumentException("No index for " + propertyName);
}
- return store.query(filter, propertyName, indexMeta, encode(value));
+ return getStrategy(indexMeta).query(filter, propertyName, indexMeta,
encode(value));
+ }
+
+ private static IndexStoreStrategy getStrategy(NodeState indexMeta) {
+ if (indexMeta.getBoolean(IndexConstants.UNIQUE_PROPERTY_NAME)) {
+ return UNIQUE;
+ }
+ return MIRROR;
}
public double getCost(Filter filter, String propertyName, PropertyValue
value) {
@@ -107,7 +122,7 @@ public class PropertyIndexLookup {
if (indexMeta == null) {
return Double.POSITIVE_INFINITY;
}
- return store.count(indexMeta, encode(value), MAX_COST);
+ return getStrategy(indexMeta).count(indexMeta, encode(value),
MAX_COST);
}
/**
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/UniqueEntryStoreStrategy.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/UniqueEntryStoreStrategy.java?rev=1528405&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/UniqueEntryStoreStrategy.java
(added)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/UniqueEntryStoreStrategy.java
Wed Oct 2 10:06:44 2013
@@ -0,0 +1,169 @@
+/*
+ * 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.index.property.strategy;
+
+import static
org.apache.jackrabbit.oak.plugins.index.IndexConstants.ENTRY_COUNT_PROPERTY_NAME;
+import static
org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_CONTENT_NODE_NAME;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.memory.MultiStringPropertyState;
+import org.apache.jackrabbit.oak.spi.query.Filter;
+import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An IndexStoreStrategy implementation that saves the unique node in a single
property.<br>
+ * This should reduce the number of nodes in the repository, and speed up
access.<br>
+ * <br>
+ * For example for a node that is under {@code /test/node}, the index
+ * structure will be {@code /oak:index/index/@key}:
+ */
+public class UniqueEntryStoreStrategy implements IndexStoreStrategy {
+
+ static final Logger LOG =
LoggerFactory.getLogger(UniqueEntryStoreStrategy.class);
+
+ @Override
+ public void update(
+ NodeBuilder index, String path,
+ Set<String> beforeKeys, Set<String> afterKeys) {
+ for (String key : beforeKeys) {
+ remove(index, key, path);
+ }
+ for (String key : afterKeys) {
+ insert(index, key, path);
+ }
+ }
+
+ private static void remove(NodeBuilder index, String key, String value) {
+ NodeBuilder builder = index.getChildNode(key);
+ if (builder.exists()) {
+ // there could be (temporarily) multiple entries
+ // we need to remove the right one
+ PropertyState s = builder.getProperty("entry");
+ if (s.count() == 1) {
+ builder.remove();
+ } else {
+ ArrayList<String> list = new ArrayList<String>();
+ for (int i = 0; i < s.count(); i++) {
+ String r = s.getValue(Type.STRING, i);
+ if (!r.equals(value)) {
+ list.add(r);
+ }
+ }
+ PropertyState s2 =
MultiStringPropertyState.stringProperty("entry", list);
+ builder.setProperty(s2);
+ }
+ }
+ }
+
+ private static void insert(NodeBuilder index, String key, String value) {
+ NodeBuilder k = index.child(key);
+ ArrayList<String> list = new ArrayList<String>();
+ list.add(value);
+ if (k.hasProperty("entry")) {
+ // duplicate key (to detect duplicate entries)
+ // this is just set temporarily,
+ // while trying to add a duplicate entry
+ PropertyState s = k.getProperty("entry");
+ for (int i = 0; i < s.count(); i++) {
+ String r = s.getValue(Type.STRING, i);
+ list.add(r);
+ }
+ }
+ PropertyState s2 = MultiStringPropertyState.stringProperty("entry",
list);
+ k.setProperty(s2);
+ }
+
+ @Override
+ public Iterable<String> query(final Filter filter, final String indexName,
+ final NodeState indexMeta, final Iterable<String> values) {
+ final NodeState index =
indexMeta.getChildNode(INDEX_CONTENT_NODE_NAME);
+ return new Iterable<String>() {
+ @Override
+ public Iterator<String> iterator() {
+ if (values == null) {
+ return new Iterator<String>() {
+
+ Iterator<? extends ChildNodeEntry> it =
index.getChildNodeEntries().iterator();
+
+ @Override
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+
+ @Override
+ public String next() {
+ PropertyState s =
it.next().getNodeState().getProperty("entry");
+ return s.getValue(Type.STRING, 0);
+ }
+
+ @Override
+ public void remove() {
+ it.remove();
+ }
+
+ };
+ }
+ ArrayList<String> list = new ArrayList<String>();
+ for (String p : values) {
+ NodeState key = index.getChildNode(p);
+ if (key.exists()) {
+ // we have an entry for this value, so use it
+ PropertyState s = key.getProperty("entry");
+ String v = s.getValue(Type.STRING, 0);
+ list.add(v);
+ }
+ }
+ return list.iterator();
+ }
+ };
+ }
+
+ @Override
+ public long count(NodeState indexMeta, Set<String> values, int max) {
+ NodeState index = indexMeta.getChildNode(INDEX_CONTENT_NODE_NAME);
+ long count = 0;
+ if (values == null) {
+ PropertyState ec =
indexMeta.getProperty(ENTRY_COUNT_PROPERTY_NAME);
+ if (ec != null) {
+ return ec.getValue(Type.LONG);
+ }
+ count = index.getChildNodeCount(max);
+ // "is not null" queries typically read more data
+ count *= 10;
+ } else if (values.size() == 1) {
+ NodeState k = index.getChildNode(values.iterator().next());
+ if (k.exists()) {
+ count = k.getProperty("entry").count();
+ } else {
+ count = 0;
+ }
+ } else {
+ count = values.size();
+ }
+ return count;
+ }
+
+}