This is an automated email from the ASF dual-hosted git repository. mkataria pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
The following commit(s) were added to refs/heads/trunk by this push: new 07809da OAK-9457: Lucene index is always used over ES index when the cost is minimal (#298) 07809da is described below commit 07809da9e4171f92189591c6bb1f1157687e30d9 Author: Mohit Kataria <mkata...@apache.org> AuthorDate: Fri Jun 25 12:00:34 2021 +0530 OAK-9457: Lucene index is always used over ES index when the cost is minimal (#298) --- .../org/apache/jackrabbit/oak/query/QueryImpl.java | 10 +- .../jackrabbit/oak/IndexCostEvaluationTest.java | 192 +++++++++++++++++++++ .../plugins/index/lucene/LucenePropertyIndex.java | 6 - .../plugins/index/elastic/query/ElasticIndex.java | 8 - .../index/search/spi/query/FulltextIndex.java | 7 + 5 files changed, 207 insertions(+), 16 deletions(-) diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java index f91cd3b..fc21ecb 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java @@ -1041,8 +1041,14 @@ public class QueryImpl implements Query { QueryIndex index = queryIndexes.get(i); double minCost = index.getMinimumCost(); if (minCost > bestCost) { - // Stop looking if the minimum cost is higher than the current best cost - break; + if (Math.abs(minCost - bestIndex.getMinimumCost()) < .00001) { + // Continue with cost evaluation if minimum cost of both indexes are same i.e both indexes are on par. + LOG.debug("minCost: {} of index :{} > best Cost: {} from index: {}, but both indexes have same minimum cost - cost evaluation will continue", minCost, index.getIndexName(), bestCost, bestIndex.getIndexName()); + } else { + // Stop looking if the minimum cost is higher than the current best cost + LOG.debug("minCost: {} of index :{} < best Cost: {} from index: {}. Further index evaluation will be skipped", minCost, index.getIndexName(), bestCost, bestIndex.getIndexName()); + break; + } } double cost; diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/IndexCostEvaluationTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/IndexCostEvaluationTest.java new file mode 100644 index 0000000..b5cf426 --- /dev/null +++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/IndexCostEvaluationTest.java @@ -0,0 +1,192 @@ +/* + * 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; + +import ch.qos.logback.classic.Level; +import com.google.common.collect.ImmutableList; +import org.apache.jackrabbit.api.JackrabbitRepository; +import org.apache.jackrabbit.api.JackrabbitSession; +import org.apache.jackrabbit.oak.commons.junit.LogCustomizer; +import org.apache.jackrabbit.oak.jcr.Jcr; +import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexPlan; +import org.apache.jackrabbit.oak.query.QueryEngineSettings; +import org.apache.jackrabbit.oak.query.index.FilterImpl; +import org.apache.jackrabbit.oak.spi.query.Cursor; +import org.apache.jackrabbit.oak.spi.query.Filter; +import org.apache.jackrabbit.oak.spi.query.QueryIndex; +import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import javax.jcr.Node; +import javax.jcr.Repository; +import javax.jcr.SimpleCredentials; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertTrue; + +public class IndexCostEvaluationTest { + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(new File("target")); + + private static final String TEST_USER_NAME = "testUserName"; + + private Repository repository = null; + private JackrabbitSession session = null; + private Node root = null; + private LogCustomizer custom; + + @Before + public void before() throws Exception { + custom = LogCustomizer + .forLogger( + "org.apache.jackrabbit.oak.query.QueryImpl") + .enable(Level.DEBUG).create(); + custom.starting(); + + + TestIndexProvider testProvider = new TestIndexProvider(); + TestIndexProvider2 testProvider2 = new TestIndexProvider2(); + TestIndexProvider3 testProvider3 = new TestIndexProvider3(); + + Jcr jcr = new Jcr() + .with((QueryIndexProvider) testProvider) + .with((QueryIndexProvider) testProvider2) + .with((QueryIndexProvider) testProvider3); + + repository = jcr.createRepository(); + session = (JackrabbitSession) repository.login(new SimpleCredentials("admin", "admin".toCharArray())); + root = session.getRootNode(); + } + + + @After + public void after() { + custom.finished(); + session.logout(); + if (repository instanceof JackrabbitRepository) { + ((JackrabbitRepository) repository).shutdown(); + } + } + + // In cases where two indexes have same min cost i.e. both indexes are on par, we don't skip cost evaluation + // even of cost from previous index is less than min cost of new index. + @Test + public void costEvaluationTest() throws Exception { + boolean evaluationContinueLogPresent = false; + boolean evaluationSkipLogPresent = false; + for (String log : custom.getLogs()) { + if (log.equals("minCost: 2.1 of index :test-index2 > best Cost: 2.0 from index: test-index, but both indexes have same minimum cost - cost evaluation will continue")) { + evaluationContinueLogPresent = true; + } + if (log.equals("minCost: 2.11 of index :test-index3 < best Cost: 2.0 from index: test-index. Further index evaluation will be skipped")) { + evaluationSkipLogPresent = true; + } + } + assertTrue(evaluationContinueLogPresent); + assertTrue(evaluationSkipLogPresent); + } + + private class TestIndexProvider implements QueryIndexProvider { + TestIndex index = new TestIndex(); + + @Override + public @NotNull List<? extends QueryIndex> getQueryIndexes(NodeState nodeState) { + return ImmutableList.<QueryIndex>of(index); + } + } + + private class TestIndexProvider2 extends TestIndexProvider { + public TestIndexProvider2() { + this.index = new TestIndex() { + public String getIndexName() { + return "test-index2"; + } + }; + } + } + + private class TestIndexProvider3 extends TestIndexProvider { + public TestIndexProvider3() { + this.index = new TestIndex() { + public String getIndexName() { + return "test-index3"; + } + + public double getMinimumCost() { + return PropertyIndexPlan.COST_OVERHEAD + 0.11; + } + }; + } + } + + private class TestIndex implements QueryIndex, QueryIndex.AdvancedQueryIndex { + + @Override + public double getMinimumCost() { + return PropertyIndexPlan.COST_OVERHEAD + 0.1; + } + + @Override + public double getCost(Filter filter, NodeState rootState) { + return Double.POSITIVE_INFINITY; + } + + @Override + public Cursor query(Filter filter, NodeState rootState) { + return null; + } + + @Override + public String getPlan(Filter filter, NodeState rootState) { + return null; + } + + @Override + public String getIndexName() { + return "test-index"; + } + + @Override + public List<IndexPlan> getPlans(Filter filter, List<OrderEntry> sortOrder, NodeState rootState) { + IndexPlan.Builder b = new IndexPlan.Builder(); + Filter f = new FilterImpl(null, "SELECT * FROM [nt:file]", new QueryEngineSettings()); + IndexPlan plan1 = b.setEstimatedEntryCount(10).setPlanName("testIndexPlan1").setFilter(f).build(); + List<IndexPlan> indexList = new ArrayList<IndexPlan>(); + + indexList.add(plan1); + return indexList; + } + + @Override + public String getPlanDescription(IndexPlan plan, NodeState root) { + return null; + } + + @Override + public Cursor query(IndexPlan plan, NodeState rootState) { + return null; + } + } +} diff --git a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java index 2a31b0b..811675c 100644 --- a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java +++ b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java @@ -201,7 +201,6 @@ public class LucenePropertyIndex extends FulltextIndex { private final boolean CACHE_FACET_RESULTS = Boolean.parseBoolean(System.getProperty(CACHE_FACET_RESULTS_NAME, "true")); - private static double MIN_COST = 2.1; private static boolean FLAG_CACHE_FACET_RESULTS_CHANGE = true; private static final Logger LOG = LoggerFactory @@ -242,11 +241,6 @@ public class LucenePropertyIndex extends FulltextIndex { } @Override - public double getMinimumCost() { - return MIN_COST; - } - - @Override public String getIndexName() { return "lucene-property"; } diff --git a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticIndex.java b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticIndex.java index 2779bf0..a7cac11 100644 --- a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticIndex.java +++ b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticIndex.java @@ -46,9 +46,6 @@ class ElasticIndex extends FulltextIndex { // no concept of rewound in ES (even if it might be doing it internally, we can't do much about it private static final IteratorRewoundStateProvider REWOUND_STATE_PROVIDER_NOOP = () -> 0; - // # OAK-9419 - private static final double MIN_COST = 2.2; - private final ElasticIndexTracker elasticIndexTracker; ElasticIndex(ElasticIndexTracker elasticIndexTracker) { @@ -76,11 +73,6 @@ class ElasticIndex extends FulltextIndex { } @Override - public double getMinimumCost() { - return MIN_COST; - } - - @Override public String getIndexName() { return "elasticsearch"; } diff --git a/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndex.java b/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndex.java index ea63ff0..aebcd1b 100644 --- a/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndex.java +++ b/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndex.java @@ -74,6 +74,8 @@ public abstract class FulltextIndex implements AdvancedQueryIndex, QueryIndex, N public static final String ATTR_PLAN_RESULT = "oak.fulltext.planResult"; + private static final double MIN_COST = 2.1; + protected abstract IndexNode acquireIndexNode(String indexPath); protected abstract String getType(); @@ -100,6 +102,11 @@ public abstract class FulltextIndex implements AdvancedQueryIndex, QueryIndex, N } @Override + public double getMinimumCost() { + return MIN_COST; + } + + @Override public List<IndexPlan> getPlans(Filter filter, List<OrderEntry> sortOrder, NodeState rootState) { Collection<String> indexPaths = new IndexLookup(rootState, getIndexDefinitionPredicate()) .collectIndexNodePaths(filter);