Author: thomasm
Date: Tue Oct 1 13:52:58 2013
New Revision: 1528094
URL: http://svn.apache.org/r1528094
Log:
OAK-1059 Property index: faster unique indexes using new storage strategy
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=1528094&r1=1528093&r2=1528094&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
Tue Oct 1 13:52:58 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 isUnique;
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)) {
+ isUnique = true;
this.keysToCheckForUniqueness = newHashSet();
} else {
+ isUnique = false;
this.keysToCheckForUniqueness = null;
}
}
@@ -148,6 +157,7 @@ class PropertyIndexEditor implements Ind
this.primaryTypes = parent.primaryTypes;
this.mixinTypes = parent.mixinTypes;
this.keysToCheckForUniqueness = parent.keysToCheckForUniqueness;
+ this.isUnique = parent.isUnique;
}
/**
@@ -217,9 +227,11 @@ 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) {
+
+ if (keysToCheckForUniqueness == null) {
+ MIRROR.update(index, getPath(), beforeKeys, afterKeys);
+ } else {
+ UNIQUE.update(index, getPath(), beforeKeys, afterKeys);
keysToCheckForUniqueness.addAll(afterKeys);
}
}
@@ -234,7 +246,7 @@ class PropertyIndexEditor implements Ind
&& !keysToCheckForUniqueness.isEmpty()) {
NodeState indexMeta = definition.getNodeState();
for (String key : keysToCheckForUniqueness) {
- if (STORE.count(indexMeta, singleton(key), 2) > 1) {
+ if (UNIQUE.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=1528094&r1=1528093&r2=1528094&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
Tue Oct 1 13:52:58 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=1528094&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
Tue Oct 1 13:52:58 2013
@@ -0,0 +1,143 @@
+/*
+ * 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.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()) {
+ builder.remove();
+ }
+ }
+
+ private static void insert(NodeBuilder index, String key, String value) {
+ NodeBuilder k = index.child(key);
+ if (k.hasProperty("entry")) {
+ // duplicate key (only temporarily set, to detect duplicate
entries)
+ k.setProperty("entry2", value);
+ } else {
+ k.setProperty("entry", value);
+ }
+ }
+
+ @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() {
+ return
it.next().getNodeState().getProperty("entry").getValue(Type.STRING);
+ }
+
+ @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
+ String v =
key.getProperty("entry").getValue(Type.STRING);
+ 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.hasProperty("entry2") ? 2 : 1;
+ } else {
+ count = 0;
+ }
+ } else {
+ count = values.size();
+ }
+ return count;
+ }
+
+}