Author: thomasm
Date: Tue Feb  7 14:28:53 2017
New Revision: 1782000

URL: http://svn.apache.org/viewvc?rev=1782000&view=rev
Log:
OAK-5587 Node counter index estimates must not be used before first async index 
cycle

Added:
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/counter/
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/counter/NodeCounterIndexTest.java
Modified:
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/counter/jmx/NodeCounter.java
    
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryPlanTest.java
    
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/counter/jmx/NodeCounter.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/counter/jmx/NodeCounter.java?rev=1782000&r1=1781999&r2=1782000&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/counter/jmx/NodeCounter.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/counter/jmx/NodeCounter.java
 Tue Feb  7 14:28:53 2017
@@ -123,6 +123,10 @@ public class NodeCounter implements Node
             return -1;
         }
         s = child(s, NodeCounterEditor.DATA_NODE_NAME);
+        if (!s.exists()) {
+            // no index data (not yet indexed, or very few nodes)
+            return -1;
+        }        
         s = child(s, PathUtils.elements(path));
         if (s == null || !s.exists()) {
             // we have an index, but no data
@@ -164,6 +168,10 @@ public class NodeCounter implements Node
             return -1;
         }
         s = child(s, NodeCounterEditor.DATA_NODE_NAME);
+        if (!s.exists()) {
+            // no index data (not yet indexed, or very few nodes)
+            return -1;
+        }
         s = child(s, PathUtils.elements(path));
         if (s != null && s.exists()) {
             value = getCombinedCountIfAvailable(s);

Added: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/counter/NodeCounterIndexTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/counter/NodeCounterIndexTest.java?rev=1782000&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/counter/NodeCounterIndexTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/counter/NodeCounterIndexTest.java
 Tue Feb  7 14:28:53 2017
@@ -0,0 +1,165 @@
+/*
+ * 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.counter;
+
+import static org.apache.jackrabbit.oak.api.QueryEngine.NO_MAPPINGS;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.text.ParseException;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nullable;
+
+import org.apache.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.api.ContentRepository;
+import org.apache.jackrabbit.oak.api.ContentSession;
+import org.apache.jackrabbit.oak.api.PropertyValue;
+import org.apache.jackrabbit.oak.api.QueryEngine;
+import org.apache.jackrabbit.oak.api.Result;
+import org.apache.jackrabbit.oak.api.ResultRow;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.commons.json.JsonObject;
+import org.apache.jackrabbit.oak.commons.json.JsopTokenizer;
+import org.apache.jackrabbit.oak.plugins.index.AsyncIndexUpdate;
+import 
org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent;
+import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
+import org.apache.jackrabbit.oak.spi.state.NodeStateUtils;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.base.Predicate;
+
+/**
+ * A test case for the node counter index.
+ */
+public class NodeCounterIndexTest {
+
+    Whiteboard wb;
+    NodeStore nodeStore;
+    Root root;
+    QueryEngine qe;
+    ContentSession session;
+    
+    @Before
+    public void before() throws Exception {
+        session = createRepository().login(null, null);
+        root = session.getLatestRoot();
+        qe = root.getQueryEngine();
+    }
+    
+    @Test
+    public void testNotUsedBeforeValid() throws Exception {
+        root.getTree("/oak:index/counter").setProperty("resolution", 100);
+        root.commit();
+        // no index data before indexing
+        assertFalse(nodeExists("oak:index/counter/:index"));
+        // so, cost for traversal is high
+        assertTrue(getCost("/jcr:root//*") >= 1.0E8);
+        
+        runAsyncIndex();
+        // sometimes, the :index node doesn't exist because there are very few
+        // nodes (randomly, because the seed value of the node counter is 
random
+        // by design) - so we create nodes until the index exists
+        // (we could use a fixed seed to ensure this is not the case,
+        // but creating nodes has the same effect)
+        for(int i=0; !nodeExists("oak:index/counter/:index"); i++) {
+            assertTrue("index not ready after 100 iterations", i < 100);
+            Tree t = root.getTree("/").addChild("test" + i);
+            for (int j = 0; j < 100; j++) {
+                t.addChild("n" + j);
+            }
+            root.commit();
+            runAsyncIndex();
+        }
+
+        // because we do have node counter data,
+        // cost for traversal is low
+        assertTrue(getCost("/jcr:root//*") < 1.0E8);
+
+        // remove the counter index
+        root.getTree("/oak:index/counter").remove();
+        root.commit();
+        assertFalse(nodeExists("oak:index/counter"));
+        // so, cost for traversal is high again
+        assertTrue(getCost("/jcr:root//*") >= 1.0E8);
+    }
+    
+    private double getCost(String xpath) throws ParseException {
+        String plan = executeXPathQuery("explain measure " + xpath);
+        String cost = plan.substring(plan.lastIndexOf('{'));
+        JsonObject json = parseJson(cost);
+        double c = Double.parseDouble(json.getProperties().get("a"));
+        return c;
+    }
+    
+    private static JsonObject parseJson(String json) {
+        JsopTokenizer t = new JsopTokenizer(json);
+        t.read('{');
+        return JsonObject.create(t);
+    }
+    
+    private boolean nodeExists(String path) {
+        return NodeStateUtils.getNode(nodeStore.getRoot(), path).exists();
+    }
+    
+    protected String executeXPathQuery(String statement) throws ParseException 
{
+        Result result = qe.executeQuery(statement, "xpath", null, NO_MAPPINGS);
+        StringBuilder buff = new StringBuilder();
+        for (ResultRow row : result.getRows()) {
+            for(PropertyValue v : row.getValues()) {
+                buff.append(v);
+            }
+        }
+        return buff.toString();
+    }
+    
+    protected ContentRepository createRepository() {
+        nodeStore = new MemoryNodeStore();
+        Oak oak = new Oak(nodeStore)
+                .with(new InitialContent())
+                .with(new OpenSecurityProvider())
+                .with(new PropertyIndexEditorProvider())
+                .with(new NodeCounterEditorProvider())
+                //Effectively disable async indexing auto run
+                //such that we can control run timing as per test requirement
+                .withAsyncIndexing("async", TimeUnit.DAYS.toSeconds(1));
+
+        wb = oak.getWhiteboard();
+        return oak.createContentRepository();
+    }
+    
+    private void runAsyncIndex() {
+        Runnable async = WhiteboardUtils.getService(wb, Runnable.class, new 
Predicate<Runnable>() {
+            @Override
+            public boolean apply(@Nullable Runnable input) {
+                return input instanceof AsyncIndexUpdate;
+            }
+        });
+        assertNotNull(async);
+        async.run();
+        root.refresh();
+    }
+
+}

Modified: 
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryPlanTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryPlanTest.java?rev=1782000&r1=1781999&r2=1782000&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryPlanTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryPlanTest.java
 Tue Feb  7 14:28:53 2017
@@ -215,7 +215,7 @@ public class QueryPlanTest extends Abstr
         Session session = getAdminSession();
         QueryManager qm = session.getWorkspace().getQueryManager();
         Node nodetype = 
session.getRootNode().getNode("oak:index").getNode("nodetype");
-        nodetype.setProperty("entryCount", 100000);
+        nodetype.setProperty("entryCount", 10000000);
         Node testRootNode = session.getRootNode().addNode("testroot");
         Node n1 = testRootNode.addNode("node1");
         Node n2 = n1.addNode("node2");

Modified: 
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java?rev=1782000&r1=1781999&r2=1782000&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java
 Tue Feb  7 14:28:53 2017
@@ -817,8 +817,26 @@ public class QueryTest extends AbstractR
     @Test
     public void approxCount() throws Exception {
         Session session = createAdminSession();
-        double c = getCost(session, "//*[@x=1]");
+        session.getNode("/oak:index/counter").setProperty("resolution", 100);
+        session.save();
         // *with* the counter index, the estimated cost to traverse is low
+        // but the counter index is not always up to date, so we need a loop
+        for (int i = 0; i < 100; i++) {
+            double c = getCost(session, "//*[@x=1]");
+            if (c > 0 && c < 100000) {
+                break;
+            }
+            // create a few nodes, in case there are not enough nodes
+            // for the node counter index to be available
+            Node testNode = session.getRootNode().addNode("test" + i);
+            for (int j = 0; j < 100; j++) {
+                testNode.addNode("n" + j);
+            }
+            session.save();
+            // wait for async indexing (the node counter index is async)
+            Thread.sleep(100);
+        }
+        double c = getCost(session, "//*[@x=1]");
         assertTrue("cost: " + c, c > 0 && c < 100000);
         
         // *without* the counter index, the estimated cost to traverse is high


Reply via email to