Author: catholicon
Date: Mon Feb 29 18:03:14 2016
New Revision: 1732923
URL: http://svn.apache.org/viewvc?rev=1732923&view=rev
Log:
OAK-4066: Suggestion dictionary don't update after
suggestUpdateFrequencyMinutes unless something else causes index update
Modified:
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorContext.java
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/SuggestionIntervalTest.java
Modified:
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorContext.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorContext.java?rev=1732923&r1=1732922&r2=1732923&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorContext.java
(original)
+++
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorContext.java
Mon Feb 29 18:03:14 2016
@@ -245,6 +245,12 @@ public class LuceneIndexEditorContext {
getWriter();
}
+ boolean updateSuggestions = shouldUpdateSuggestions();
+ if (writer == null && updateSuggestions) {
+ log.debug("Would update suggester dictionary although no index
changes were detected in current cycle");
+ getWriter();
+ }
+
if (writer != null) {
if (log.isTraceEnabled()) {
trackIndexSizeInfo(writer, definition, directory);
@@ -252,8 +258,14 @@ public class LuceneIndexEditorContext {
final long start = PERF_LOGGER.start();
- updateSuggester(writer.getAnalyzer());
- PERF_LOGGER.end(start, -1, "Completed suggester for directory {}",
definition);
+ Calendar lastUpdated = null;
+ if (updateSuggestions) {
+ lastUpdated = updateSuggester(writer.getAnalyzer());
+ PERF_LOGGER.end(start, -1, "Completed suggester for directory
{}", definition);
+ }
+ if (lastUpdated == null) {
+ lastUpdated = getCalendar();
+ }
writer.close();
PERF_LOGGER.end(start, -1, "Closed writer for directory {}",
definition);
@@ -265,8 +277,9 @@ public class LuceneIndexEditorContext {
//as to make IndexTracker detect changes when index
//is stored in file system
NodeBuilder status = definitionBuilder.child(":status");
- status.setProperty("lastUpdated", ISO8601.format(getCalendar()),
Type.DATE);
- status.setProperty("indexedNodes",indexedNodes);
+ status.setProperty("lastUpdated", ISO8601.format(lastUpdated),
Type.DATE);
+ status.setProperty("indexedNodes", indexedNodes);
+
PERF_LOGGER.end(start, -1, "Overall Closed IndexWriter for
directory {}", definition);
textExtractionStats.log(reindex);
@@ -278,38 +291,69 @@ public class LuceneIndexEditorContext {
* eventually update suggest dictionary
* @throws IOException if suggest dictionary update fails
* @param analyzer the analyzer used to update the suggester
+ * @return {@link Calendar} object representing the lastUpdated value
written by suggestions
*/
- private void updateSuggester(Analyzer analyzer) throws IOException {
+ private Calendar updateSuggester(Analyzer analyzer) throws IOException {
+ Calendar ret = null;
+ NodeBuilder suggesterStatus =
definitionBuilder.child(":suggesterStatus");
+ DirectoryReader reader = DirectoryReader.open(writer, false);
+ final OakDirectory suggestDirectory = new
OakDirectory(definitionBuilder, ":suggest-data", definition, false);
+ try {
+ SuggestHelper.updateSuggester(suggestDirectory, analyzer, reader);
+ ret = getCalendar();
+ suggesterStatus.setProperty("lastUpdated", ISO8601.format(ret),
Type.DATE);
+ } catch (Throwable e) {
+ log.warn("could not update suggester", e);
+ } finally {
+ suggestDirectory.close();
+ reader.close();
+ }
- if (definition.isSuggestEnabled()) {
+ return ret;
+ }
+
+ /**
+ * Checks if last suggestion build time was done sufficiently in the past
AND that there were non-zero indexedNodes
+ * stored in the last run. Note, if index is updated only to rebuild
suggestions, even then we update indexedNodes,
+ * which would be zero in case it was a forced update of suggestions.
+ * @return is suggest dict should be updated
+ */
+ private boolean shouldUpdateSuggestions() {
+ boolean updateSuggestions = false;
- boolean updateSuggester = false;
+ if (definition.isSuggestEnabled()) {
NodeBuilder suggesterStatus =
definitionBuilder.child(":suggesterStatus");
- if (suggesterStatus.hasProperty("lastUpdated")) {
- PropertyState suggesterLastUpdatedValue =
suggesterStatus.getProperty("lastUpdated");
+
+ PropertyState suggesterLastUpdatedValue =
suggesterStatus.getProperty("lastUpdated");
+
+ if (suggesterLastUpdatedValue != null) {
Calendar suggesterLastUpdatedTime =
ISO8601.parse(suggesterLastUpdatedValue.getValue(Type.DATE));
+
int updateFrequency =
definition.getSuggesterUpdateFrequencyMinutes();
- suggesterLastUpdatedTime.add(Calendar.MINUTE, updateFrequency);
- if (getCalendar().after(suggesterLastUpdatedTime)) {
- updateSuggester = true;
+ Calendar nextSuggestUpdateTime =
(Calendar)suggesterLastUpdatedTime.clone();
+ nextSuggestUpdateTime.add(Calendar.MINUTE, updateFrequency);
+ if (getCalendar().after(nextSuggestUpdateTime)) {
+ updateSuggestions = (writer != null ||
isIndexUpdatedAfter(suggesterLastUpdatedTime));
}
} else {
- updateSuggester = true;
+ updateSuggestions = true;
}
+ }
- if (updateSuggester) {
- DirectoryReader reader = DirectoryReader.open(writer, false);
- final OakDirectory suggestDirectory = new
OakDirectory(definitionBuilder, ":suggest-data", definition, false);
- try {
- SuggestHelper.updateSuggester(suggestDirectory, analyzer,
reader);
- suggesterStatus.setProperty("lastUpdated",
ISO8601.format(getCalendar()), Type.DATE);
- } catch (Throwable e) {
- log.warn("could not update suggester", e);
- } finally {
- suggestDirectory.close();
- reader.close();
- }
- }
+ return updateSuggestions;
+ }
+
+ /**
+ * @return {@code false} if persisted lastUpdated time for index is after
{@code calendar}. {@code true} otherwise
+ */
+ private boolean isIndexUpdatedAfter(Calendar calendar) {
+ NodeBuilder indexStats = definitionBuilder.child(":status");
+ PropertyState indexLastUpdatedValue =
indexStats.getProperty("lastUpdated");
+ if (indexLastUpdatedValue != null) {
+ Calendar indexLastUpdatedTime =
ISO8601.parse(indexLastUpdatedValue.getValue(Type.DATE));
+ return indexLastUpdatedTime.after(calendar);
+ } else {
+ return true;
}
}
Modified:
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/SuggestionIntervalTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/SuggestionIntervalTest.java?rev=1732923&r1=1732922&r2=1732923&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/SuggestionIntervalTest.java
(original)
+++
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/SuggestionIntervalTest.java
Mon Feb 29 18:03:14 2016
@@ -18,25 +18,22 @@ package org.apache.jackrabbit.oak.plugin
import com.google.common.collect.Sets;
import org.apache.jackrabbit.JcrConstants;
-import org.apache.jackrabbit.api.JackrabbitSession;
-import org.apache.jackrabbit.oak.jcr.Jcr;
+import org.apache.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.api.ContentRepository;
+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.Tree;
+import org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent;
+import org.apache.jackrabbit.oak.query.AbstractQueryTest;
import org.apache.jackrabbit.oak.spi.commit.Observer;
import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;
+import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
import org.apache.jackrabbit.oak.stats.Clock;
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 javax.jcr.query.Query;
-import javax.jcr.query.QueryManager;
-import javax.jcr.query.QueryResult;
-import javax.jcr.query.Row;
-import javax.jcr.query.RowIterator;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -45,76 +42,73 @@ import static org.apache.jackrabbit.oak.
import static
org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_PROPERTY_NAME;
import static
org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
import static
org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.INDEX_RULES;
-import static org.apache.jackrabbit.oak.plugins.index.lucene.TestUtil.shutdown;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
-public class SuggestionIntervalTest {
- @Rule
- public TemporaryFolder temporaryFolder = new TemporaryFolder();
-
- private Repository repository = null;
- private JackrabbitSession session = null;
- private Node root = null;
+public class SuggestionIntervalTest extends AbstractQueryTest {
private Clock clock = null;
- @Before
- public void before() throws Exception {
+ @Override
+ protected ContentRepository createRepository() {
LuceneIndexProvider provider = new LuceneIndexProvider();
- Jcr jcr = new Jcr()
- .with(((QueryIndexProvider) provider))
+ ContentRepository repository = new Oak()
+ .with(new InitialContent())
+ .with(new OpenSecurityProvider())
+ .with((QueryIndexProvider) provider)
.with((Observer) provider)
- .with(new LuceneIndexEditorProvider());
-
- repository = jcr.createRepository();
- session = (JackrabbitSession) repository.login(new
SimpleCredentials("admin", "admin".toCharArray()));
- root = session.getRootNode();
+ .with(new LuceneIndexEditorProvider())
+ .createContentRepository();
clock = new Clock.Virtual();
- clock.waitUntil(System.currentTimeMillis());
+ try {
+ clock.waitUntil(System.currentTimeMillis());
+ } catch (Exception e) {
+ //eat exception if clock couldn't wait... that was just courteous
anyway!
+ }
LuceneIndexEditorContext.setClock(clock);
+
+ return repository;
}
@After
- public void after() {
+ public void after() throws Exception {
LuceneIndexEditorContext.setClock(Clock.SIMPLE);
-
- session.logout();
- shutdown(repository);
}
- private void createSuggestIndex(String indexedNodeType)
+ private Tree createSuggestIndex(String indexedNodeType)
throws Exception {
String indexName = "lucene-suggest";
- Node def = root.getNode(INDEX_DEFINITIONS_NAME)
- .addNode(indexName, INDEX_DEFINITIONS_NODE_TYPE);
+ Tree def = root.getTree("/" + INDEX_DEFINITIONS_NAME)
+ .addChild(indexName);
+ def.setProperty(JcrConstants.JCR_PRIMARYTYPE,
INDEX_DEFINITIONS_NODE_TYPE);
def.setProperty(TYPE_PROPERTY_NAME, LuceneIndexConstants.TYPE_LUCENE);
def.setProperty(REINDEX_PROPERTY_NAME, true);
def.setProperty("name", indexName);
def.setProperty(LuceneIndexConstants.COMPAT_MODE,
IndexFormatVersion.V2.getVersion());
- Node propertyIdxDef = def.addNode(INDEX_RULES,
JcrConstants.NT_UNSTRUCTURED)
- .addNode(indexedNodeType, JcrConstants.NT_UNSTRUCTURED)
- .addNode(LuceneIndexConstants.PROP_NODE,
JcrConstants.NT_UNSTRUCTURED)
- .addNode("indexedProperty", JcrConstants.NT_UNSTRUCTURED);
+ Tree propertyIdxDef = def.addChild(INDEX_RULES)
+ .addChild(indexedNodeType)
+ .addChild(LuceneIndexConstants.PROP_NODE)
+ .addChild("indexedProperty");
propertyIdxDef.setProperty("propertyIndex", true);
propertyIdxDef.setProperty("analyzed", true);
propertyIdxDef.setProperty("useInSuggest", true);
propertyIdxDef.setProperty("name",
LuceneIndexConstants.PROPDEF_PROP_NODE_NAME);
+
+ return def;
}
Set<String> getSuggestions(String nodeType, String suggestFor) throws
Exception {
Set<String> ret = Sets.newHashSet();
String suggQuery = createSuggestQuery(nodeType, suggestFor);
- QueryManager queryManager = session.getWorkspace().getQueryManager();
- QueryResult result = queryManager.createQuery(suggQuery,
Query.JCR_SQL2).execute();
- RowIterator rows = result.getRows();
-
- while (rows.hasNext()) {
- Row firstRow = rows.nextRow();
- ret.add(firstRow.getValue("suggestion").getString());
+ QueryEngine qe = root.getQueryEngine();
+ Result result = qe.executeQuery(suggQuery, Query.JCR_SQL2, null, null);
+
+ for (ResultRow row : result.getRows()) {
+ ret.add(row.getValue("suggestion").toString());
}
return ret;
@@ -129,19 +123,94 @@ public class SuggestionIntervalTest {
public void defaultSuggestInterval() throws Exception {
final String nodeType = "nt:unstructured";
+ //initial data
createSuggestIndex(nodeType);
- session.save();
+ root.commit();
//wait for documented time before suggestions are refreshed
clock.waitUntil(clock.getTime() + TimeUnit.MINUTES.toMillis(10));
clock.getTime();//get one more tick
- root.addNode("indexedNode", nodeType);
- session.save();
+ //add a node... this should kick in a suggestions udpate too as enough
time has passed
+ root.getTree("/").addChild("indexedNode")
+ .setProperty(JcrConstants.JCR_PRIMARYTYPE, nodeType);
+ root.commit();
Set<String> suggestions = getSuggestions(nodeType, "indexedn");
assertEquals(1, suggestions.size());
assertEquals("indexedNode", suggestions.iterator().next());
}
+
+ //OAK-4066
+ @Test
+ public void suggestionUpdateWithoutIndexChange() throws Exception {
+ final String nodeType = "nt:unstructured";
+
+ createSuggestIndex(nodeType);
+ root.commit();
+
+ long currTime = clock.getTime();
+ long toTime = currTime +
TimeUnit.MINUTES.toMillis(IndexDefinition.DEFAULT_SUGGESTER_UPDATE_FREQUENCY_MINUTES);
+
+ //add a node that get part in the index
+ root.getTree("/").addChild("indexedNode")
+ .setProperty(JcrConstants.JCR_PRIMARYTYPE, nodeType);
+ root.commit();
+
+ //wait for suggestions refresh time
+ clock.waitUntil(toTime);
+ clock.getTime();//get one more tick
+
+ //push a change which should not make any change in the index but yet
should help update suggestions
+ root.getTree("/").addChild("some-non-index-change")
+ .setProperty(JcrConstants.JCR_PRIMARYTYPE, "oak:Unstructured");
+ root.commit();
+
+ Set<String> suggestions = getSuggestions(nodeType, "indexedn");
+
+ assertEquals(1, suggestions.size());
+ assertEquals("indexedNode", suggestions.iterator().next());
+ }
+
+ //OAK-4066
+ @Test
+ public void avoidRedundantSuggestionBuildOnNonIndexUpdate() throws
Exception {
+ final String nodeType = "nt:unstructured";
+
+ //initial content which also updates suggestions with "indexedNode"
+ Tree indexDef = createSuggestIndex(nodeType);
+ root.getTree("/").addChild("indexedNode")
+ .setProperty(JcrConstants.JCR_PRIMARYTYPE, nodeType);
+ root.commit();
+ String suggUpdateTime1 = getSuggestionLastUpdated(indexDef);
+
+ assertNotNull("Suggestions update timestamp couldn't be read",
suggUpdateTime1);
+
+ long currTime = clock.getTime();
+ long toTime = currTime +
TimeUnit.MINUTES.toMillis(IndexDefinition.DEFAULT_SUGGESTER_UPDATE_FREQUENCY_MINUTES);
+
+ //wait for suggestions refresh time
+ clock.waitUntil(toTime);
+ clock.getTime();//get one more tick
+
+ //push a change which should not make any change in the index but
would kick in suggestion logic
+ //YET, suggestion logic shouldn't do any change
+ root.getTree("/").addChild("some-non-index-change")
+ .setProperty(JcrConstants.JCR_PRIMARYTYPE, "oak:Unstructured");
+ root.commit();
+
+ String suggUpdateTime2 = getSuggestionLastUpdated(indexDef);
+
+ assertEquals("Suggestions shouldn't rebuild un-necessarily. Update
times are different",
+ suggUpdateTime1, suggUpdateTime2);
+ }
+
+ private String getSuggestionLastUpdated(Tree indexDef) {
+ Tree suggStat = root.getTree(indexDef.getPath() + "/" +
":suggesterStatus");
+ if (!suggStat.hasProperty("lastUpdated")) {
+ return null;
+ }
+ return suggStat.getProperty("lastUpdated").toString();
+ }
}