Author: mkataria Date: Tue Mar 31 15:11:08 2020 New Revision: 1875945 URL: http://svn.apache.org/viewvc?rev=1875945&view=rev Log: OAK-8978: Cache facet results
Added: jackrabbit/oak/branches/1.22/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/hybrid/FacetCacheTest.java (with props) Modified: jackrabbit/oak/branches/1.22/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java Modified: jackrabbit/oak/branches/1.22/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.22/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java?rev=1875945&r1=1875944&r2=1875945&view=diff ============================================================================== --- jackrabbit/oak/branches/1.22/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java (original) +++ jackrabbit/oak/branches/1.22/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java Tue Mar 31 15:11:08 2020 @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Deque; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -196,8 +197,12 @@ public class LucenePropertyIndex extends private static boolean NON_LAZY = Boolean.parseBoolean(System.getProperty("oak.lucene.nonLazyIndex","true")); public final static String OLD_FACET_PROVIDER_CONFIG_NAME = "oak.lucene.oldFacetProvider"; private final static boolean OLD_FACET_PROVIDER = Boolean.getBoolean(OLD_FACET_PROVIDER_CONFIG_NAME); + public final static String CACHE_FACET_RESULTS_NAME = "oak.lucene.cacheFacetResults"; + 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 .getLogger(LucenePropertyIndex.class); @@ -232,6 +237,15 @@ public class LucenePropertyIndex extends this.tracker = tracker; this.scorerProviderFactory = factory; this.augmentorFactory = augmentorFactory; + logConfigsOnce(); + } + + private void logConfigsOnce() { + if (FLAG_CACHE_FACET_RESULTS_CHANGE) { + LOG.info(OLD_FACET_PROVIDER_CONFIG_NAME + " = " + OLD_FACET_PROVIDER); + LOG.info(CACHE_FACET_RESULTS_NAME + " = " + CACHE_FACET_RESULTS); + FLAG_CACHE_FACET_RESULTS_CHANGE = false; + } } @Override @@ -730,7 +744,7 @@ public class LucenePropertyIndex extends protected String getType() { return TYPE_LUCENE; } - + @Override protected boolean filterReplacedIndexes() { return tracker.getMountInfoProvider().hasNonDefaultMounts(); @@ -1574,11 +1588,12 @@ public class LucenePropertyIndex extends return Iterators.concat(propIndex.iterator(), itr); } - static class DelayedLuceneFacetProvider implements FacetProvider { + class DelayedLuceneFacetProvider implements FacetProvider { private final LucenePropertyIndex index; private final Query query; private final IndexPlan plan; private final SecureFacetConfiguration config; + private final Map<String, List<Facet>> cachedResults = new HashMap<>(); DelayedLuceneFacetProvider(LucenePropertyIndex index, Query query, IndexPlan plan, SecureFacetConfiguration config) { this.index = index; @@ -1589,6 +1604,22 @@ public class LucenePropertyIndex extends @Override public List<Facet> getFacets(int numberOfFacets, String columnName) throws IOException { + if (!CACHE_FACET_RESULTS) { + LOG.trace(CACHE_FACET_RESULTS_NAME + " = " + CACHE_FACET_RESULTS + " getting uncached results for columnName = " + columnName); + return getFacetsUncached(numberOfFacets, columnName); + } + String cacheKey = columnName + "/" + numberOfFacets; + if (cachedResults.containsKey(cacheKey)) { + LOG.trace("columnName = " + columnName + " returning Facet Data from cache."); + return cachedResults.get(cacheKey); + } + LOG.trace("columnName = " + columnName + " facet Data not present in cache..."); + List<Facet> result = getFacetsUncached(numberOfFacets, columnName); + cachedResults.put(cacheKey, result); + return result; + } + + private List<Facet> getFacetsUncached(int numberOfFacets, String columnName) throws IOException { LuceneIndexNode indexNode = index.acquireIndexNode(plan); try { IndexSearcher searcher = indexNode.getSearcher(); @@ -1600,7 +1631,7 @@ public class LucenePropertyIndex extends if (topChildren != null) { for (LabelAndValue lav : topChildren.labelValues) { res.add(new Facet( - lav.label, lav.value.intValue() + lav.label, lav.value.intValue() )); } return res.build(); Added: jackrabbit/oak/branches/1.22/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/hybrid/FacetCacheTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.22/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/hybrid/FacetCacheTest.java?rev=1875945&view=auto ============================================================================== --- jackrabbit/oak/branches/1.22/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/hybrid/FacetCacheTest.java (added) +++ jackrabbit/oak/branches/1.22/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/hybrid/FacetCacheTest.java Tue Mar 31 15:11:08 2020 @@ -0,0 +1,318 @@ +/* + * 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.lucene.hybrid; + +import ch.qos.logback.classic.Level; +import org.apache.jackrabbit.oak.InitialContent; +import org.apache.jackrabbit.oak.Oak; +import org.apache.jackrabbit.oak.api.ContentRepository; +import org.apache.jackrabbit.oak.api.Tree; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.commons.concurrent.ExecutorCloser; +import org.apache.jackrabbit.oak.commons.junit.LogCustomizer; +import org.apache.jackrabbit.oak.jcr.Jcr; +import org.apache.jackrabbit.oak.plugins.index.AsyncIndexUpdate; +import org.apache.jackrabbit.oak.plugins.index.counter.NodeCounterEditorProvider; +import org.apache.jackrabbit.oak.plugins.index.lucene.IndexCopier; +import org.apache.jackrabbit.oak.plugins.index.lucene.IndexTracker; +import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexEditorProvider; +import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexNodeManager; +import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexProvider; +import org.apache.jackrabbit.oak.plugins.index.lucene.LucenePropertyIndex; +import org.apache.jackrabbit.oak.plugins.index.lucene.TestUtil; +import org.apache.jackrabbit.oak.plugins.index.lucene.TestUtil.OptionalEditorProvider; +import org.apache.jackrabbit.oak.plugins.index.lucene.reader.DefaultIndexReaderFactory; +import org.apache.jackrabbit.oak.plugins.index.lucene.reader.LuceneIndexReaderFactory; +import org.apache.jackrabbit.oak.plugins.index.lucene.util.IndexDefinitionBuilder; +import org.apache.jackrabbit.oak.plugins.index.nodetype.NodeTypeIndexProvider; +import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider; +import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants; +import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore; +import org.apache.jackrabbit.oak.query.AbstractQueryTest; +import org.apache.jackrabbit.oak.spi.commit.Observer; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; +import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider; +import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider; +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.apache.jackrabbit.oak.stats.Clock; +import org.apache.jackrabbit.oak.stats.StatisticsProvider; +import org.jetbrains.annotations.Nullable; +import org.junit.After; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import javax.jcr.GuestCredentials; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; +import javax.jcr.query.Query; +import javax.jcr.query.QueryManager; +import javax.jcr.query.QueryResult; +import javax.jcr.query.RowIterator; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Properties; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; + +import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_FACETS; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.STATISTICAL_FACET_SAMPLE_SIZE_DEFAULT; +import static org.apache.jackrabbit.oak.spi.mount.Mounts.defaultMountInfoProvider; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +public class FacetCacheTest extends AbstractQueryTest { + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(new File("target")); + + private static final int NUM_LABELS = 4; + private static final int NUM_LEAF_NODES = STATISTICAL_FACET_SAMPLE_SIZE_DEFAULT; + private static final String FACET_PROP = "facets"; + private static final long REFRESH_DELTA = TimeUnit.SECONDS.toMillis(1); + + private ExecutorService executorService = Executors.newFixedThreadPool(2); + private OptionalEditorProvider optionalEditorProvider = new OptionalEditorProvider(); + private NRTIndexFactory nrtIndexFactory; + private LuceneIndexProvider luceneIndexProvider; + private NodeStore nodeStore; + private DocumentQueue queue; + private Clock clock = new Clock.Virtual(); + private Whiteboard wb; + private QueryManager qm; + private Repository jcrRepo; + private Jcr jcr; + private Oak oak; + private Thread thread; + // backup original system properties i.e. before test started + private final Properties backupProperties = (Properties) System.getProperties().clone(); + + @After + public void tearDown() throws IOException { + luceneIndexProvider.close(); + new ExecutorCloser(executorService).close(); + nrtIndexFactory.close(); + // restore original system properties i.e. before test started + System.setProperties(backupProperties); + } + + @Override + protected ContentRepository createRepository() { + IndexCopier copier; + try { + copier = new IndexCopier(executorService, temporaryFolder.getRoot()); + } catch (IOException e) { + throw new RuntimeException(e); + } + MountInfoProvider mip = defaultMountInfoProvider(); + nrtIndexFactory = new NRTIndexFactory(copier, clock, TimeUnit.MILLISECONDS.toSeconds(REFRESH_DELTA), StatisticsProvider.NOOP); + nrtIndexFactory.setAssertAllResourcesClosed(true); + LuceneIndexReaderFactory indexReaderFactory = new DefaultIndexReaderFactory(mip, copier); + IndexTracker tracker = new IndexTracker(indexReaderFactory, nrtIndexFactory); + luceneIndexProvider = new LuceneIndexProvider(tracker); + queue = new DocumentQueue(100, tracker, sameThreadExecutor()); + LuceneIndexEditorProvider editorProvider = new LuceneIndexEditorProvider(copier, + tracker, + null, + null, + mip); + editorProvider.setIndexingQueue(queue); + LocalIndexObserver localIndexObserver = new LocalIndexObserver(queue, StatisticsProvider.NOOP); + nodeStore = new MemoryNodeStore(); + oak = new Oak(nodeStore) + .with(new InitialContent()) + .with(new OpenSecurityProvider()) + .with((QueryIndexProvider) luceneIndexProvider) + .with((Observer) luceneIndexProvider) + .with(localIndexObserver) + .with(editorProvider) + .with(new PropertyIndexEditorProvider()) + .with(new NodeTypeIndexProvider()) + .with(optionalEditorProvider) + .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(); + ContentRepository repo = oak.createContentRepository(); + return repo; + } + + private void createSmallDataset(int k) throws RepositoryException { + Random rGen = new Random(42); + Tree par = createPath("/parent" + k); + par.setProperty("foo", "bar"); + for (int i = 0; i < NUM_LABELS * 2; i++) { + Tree subPar = par.addChild("par" + i); + for (int j = 0; j < NUM_LEAF_NODES / (2 * NUM_LABELS); j++) { + Tree child = subPar.addChild("c" + j); + child.setProperty("cons", "val"); + int labelNum = rGen.nextInt(NUM_LABELS); + child.setProperty("foo", "l" + labelNum); + } + } + } + + @Test + public void cachedFacetTest() throws Exception { + LogCustomizer custom = LogCustomizer + .forLogger( + LucenePropertyIndex.class.getName()) + .enable(Level.TRACE).create(); + System.setProperty(LucenePropertyIndex.CACHE_FACET_RESULTS_NAME, "true"); + System.setProperty(LuceneIndexNodeManager.OLD_FACET_PROVIDER_TEST_FAILURE_SLEEP_INSTRUMENT_NAME, "40"); + String idxName = "hybridtest"; + Tree idx = createIndex(root.getTree("/"), idxName); + TestUtil.enableIndexingMode(idx, FulltextIndexConstants.IndexingMode.NRT); + setTraversalEnabled(false); + root.commit(); + jcr = new Jcr(oak); + jcrRepo = jcr.createRepository(); + createSmallDataset(0); + root.commit(); + runAsyncIndex(); + Session anonSession = jcrRepo.login(new GuestCredentials()); + qm = anonSession.getWorkspace().getQueryManager(); + + try { + custom.starting(); + Query q = qm.createQuery("SELECT [rep:facet(foo)] FROM [nt:base] WHERE [cons] = 'val'", SQL2); + QueryResult qr = q.execute(); + + RowIterator it; + try { + it = qr.getRows(); + } catch (Exception e) { + throw e; + } + List<String> logs = custom.getLogs(); + assertThat("Log should contain ", logs.toString(), + containsString("facet Data not present in cache...")); + + String firstColumnName = qr.getColumnNames()[0]; + if (it.hasNext()) { + Value v = it.nextRow().getValue(firstColumnName); + } + assertThat("Log should contain ", logs.toString(), + containsString("returning Facet Data from cache")); + } finally { + custom.finished(); + } + } + + + @Test + public void unCachedFacetTest() throws Exception { + LogCustomizer custom = LogCustomizer + .forLogger( + LucenePropertyIndex.class.getName()) + .enable(Level.TRACE).create(); + System.setProperty(LucenePropertyIndex.CACHE_FACET_RESULTS_NAME, "false"); + System.setProperty(LuceneIndexNodeManager.OLD_FACET_PROVIDER_TEST_FAILURE_SLEEP_INSTRUMENT_NAME, "40"); + String idxName = "hybridtest"; + Tree idx = createIndex(root.getTree("/"), idxName); + TestUtil.enableIndexingMode(idx, FulltextIndexConstants.IndexingMode.NRT); + setTraversalEnabled(false); + root.commit(); + jcr = new Jcr(oak); + jcrRepo = jcr.createRepository(); + createSmallDataset(0); + root.commit(); + runAsyncIndex(); + Session anonSession = jcrRepo.login(new GuestCredentials()); + qm = anonSession.getWorkspace().getQueryManager(); + + try { + custom.starting(); + Query q = qm.createQuery("SELECT [rep:facet(foo)] FROM [nt:base] WHERE [cons] = 'val'", SQL2); + QueryResult qr = q.execute(); + + RowIterator it; + try { + it = qr.getRows(); + } catch (Exception e) { + throw e; + } + List<String> logs = custom.getLogs(); + String firstColumnName = qr.getColumnNames()[0]; + if (it.hasNext()) { + Value v = it.nextRow().getValue(firstColumnName); + } + assertThat("Log should contain ", logs.toString(), + containsString(LucenePropertyIndex.CACHE_FACET_RESULTS_NAME + " = " + false + " getting uncached results for columnName = ")); + } finally { + custom.finished(); + } + } + + private void runAsyncIndex() { + AsyncIndexUpdate async = (AsyncIndexUpdate) WhiteboardUtils.getService(wb, Runnable.class, new Predicate<Runnable>() { + @Override + public boolean test(@Nullable Runnable input) { + return input instanceof AsyncIndexUpdate; + } + }); + assertNotNull(async); + async.run(); + if (async.isFailing()) { + fail("AsyncIndexUpdate failed"); + } + root.refresh(); + } + + private Tree createPath(String path) { + Tree base = root.getTree("/"); + for (String e : PathUtils.elements(path)) { + base = base.addChild(e); + } + return base; + } + + private Tree createIndex(Tree index, String name) throws RepositoryException { + IndexDefinitionBuilder idxBuilder = new IndexDefinitionBuilder(); + idxBuilder.noAsync() + .indexRule("nt:base") + .property("cons").propertyIndex() + .property("foo").propertyIndex() + .getBuilderTree().setProperty(PROP_FACETS, true); + Tree facetConfig = idxBuilder.getBuilderTree().addChild(FACET_PROP); + facetConfig.setProperty("jcr:primaryType", "nt:unstructured", Type.NAME); + facetConfig.setProperty("secure", "statistical"); + facetConfig.setProperty("topChildren", "100"); + Tree idxTree = index.getChild("oak:index").addChild(name); + idxBuilder.build(idxTree); + return idxTree; + } + +} Propchange: jackrabbit/oak/branches/1.22/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/hybrid/FacetCacheTest.java ------------------------------------------------------------------------------ svn:eol-style = native