This is an automated email from the ASF dual-hosted git repository.
fortino 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 57d124c6fa OAK-10811: (oak-search-elastic) reduce contention in
IndexTracker (#1466)
57d124c6fa is described below
commit 57d124c6fa9f4b3683a45ff96b65259ab21d09ec
Author: Fabrizio Fortino <[email protected]>
AuthorDate: Thu May 23 09:23:26 2024 +0200
OAK-10811: (oak-search-elastic) reduce contention in IndexTracker (#1466)
* OAK-10811: (oak-search-elastic) reduce contention in IndexTracker
* OAK-10811: ElasticIndexInfoProvider uses node api to get :status info
* OAK-10811: rolled back unneeded change
---
.../index/elastic/ElasticIndexInfoProvider.java | 17 +++++--
.../plugins/index/elastic/ElasticIndexNode.java | 5 ++
.../index/elastic/ElasticIndexProviderService.java | 2 +-
.../plugins/index/elastic/ElasticIndexTracker.java | 37 +++++++++++++-
.../index/elastic/index/ElasticIndexWriter.java | 6 +--
.../index/elastic/ElasticAbstractQueryTest.java | 3 +-
.../index/elastic/index/ElasticIndexTest.java | 58 ++++++++++++++++++++++
7 files changed, 116 insertions(+), 12 deletions(-)
diff --git
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexInfoProvider.java
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexInfoProvider.java
index 4df7a0bf07..f552962d7c 100644
---
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexInfoProvider.java
+++
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexInfoProvider.java
@@ -27,6 +27,8 @@ import
org.apache.jackrabbit.oak.plugins.index.IndexInfoProvider;
import org.apache.jackrabbit.oak.plugins.index.IndexUtils;
import org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition;
import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStateUtils;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
import org.apache.jackrabbit.util.ISO8601;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -38,12 +40,16 @@ import
co.elastic.clients.elasticsearch.cluster.HealthResponse;
class ElasticIndexInfoProvider implements IndexInfoProvider {
+ private final NodeStore nodeStore;
+
private final ElasticIndexTracker indexTracker;
private final AsyncIndexInfoService asyncIndexInfoService;
- ElasticIndexInfoProvider(@NotNull ElasticIndexTracker indexTracker,
- @NotNull AsyncIndexInfoService
asyncIndexInfoService) {
+ ElasticIndexInfoProvider(@NotNull NodeStore nodeStore,
+ @NotNull ElasticIndexTracker indexTracker,
+ @NotNull AsyncIndexInfoService
asyncIndexInfoService) {
+ this.nodeStore = nodeStore;
this.indexTracker = indexTracker;
this.asyncIndexInfoService = asyncIndexInfoService;
}
@@ -57,18 +63,19 @@ class ElasticIndexInfoProvider implements IndexInfoProvider
{
public @Nullable IndexInfo getInfo(String indexPath) {
ElasticIndexNode node = indexTracker.acquireIndexNode(indexPath);
try {
- String asyncName =
IndexUtils.getAsyncLaneName(node.getDefinition().getDefinitionNodeState(),
indexPath);
+ NodeState idxState = NodeStateUtils.getNode(nodeStore.getRoot(),
indexPath);
+ String asyncName = IndexUtils.getAsyncLaneName(idxState,
indexPath);
AsyncIndexInfo asyncInfo =
asyncIndexInfoService.getInfo(asyncName);
return new ElasticIndexInfo(
indexPath,
asyncName,
asyncInfo != null ? asyncInfo.getLastIndexedTo() : -1L,
-
getStatusTimestamp(node.getDefinition().getDefinitionNodeState(),
IndexDefinition.STATUS_LAST_UPDATED),
+ getStatusTimestamp(idxState,
IndexDefinition.STATUS_LAST_UPDATED),
node.getIndexStatistics().numDocs(),
node.getIndexStatistics().primaryStoreSize(),
node.getIndexStatistics().creationDate(),
-
getStatusTimestamp(node.getDefinition().getDefinitionNodeState(),
IndexDefinition.REINDEX_COMPLETION_TIMESTAMP)
+ getStatusTimestamp(idxState,
IndexDefinition.REINDEX_COMPLETION_TIMESTAMP)
);
} finally {
node.release();
diff --git
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexNode.java
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexNode.java
index c5179348f2..4bb3722876 100644
---
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexNode.java
+++
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexNode.java
@@ -40,6 +40,11 @@ public class ElasticIndexNode implements IndexNode {
// do nothing
}
+ /**
+ * Returns the index definition for this index node. This method ensures
that the index definition is always
+ * up-to-date with the latest changes in the repository, except for the
:status node.
+ * For latest changes in the :status node, the node api should be used.
+ */
@Override
public ElasticIndexDefinition getDefinition() {
return indexDefinition;
diff --git
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexProviderService.java
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexProviderService.java
index 58c62c0fae..f2aebf1670 100644
---
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexProviderService.java
+++
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexProviderService.java
@@ -181,7 +181,7 @@ public class ElasticIndexProviderService {
// register info provider for oak index stats
regs.add(bundleContext.registerService(IndexInfoProvider.class.getName(),
- new ElasticIndexInfoProvider(indexTracker,
asyncIndexInfoService), null));
+ new ElasticIndexInfoProvider(nodeStore, indexTracker,
asyncIndexInfoService), null));
// register mbean for detailed elastic stats and utility actions
ElasticIndexMBean mBean = new ElasticIndexMBean(indexTracker);
diff --git
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexTracker.java
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexTracker.java
index fe924b2675..079666b676 100644
---
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexTracker.java
+++
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexTracker.java
@@ -16,6 +16,7 @@
*/
package org.apache.jackrabbit.oak.plugins.index.elastic;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition;
import
org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndexTracker;
import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
import org.apache.jackrabbit.oak.spi.commit.Observer;
@@ -35,12 +36,12 @@ public class ElasticIndexTracker extends
FulltextIndexTracker<ElasticIndexNodeMa
@Override
public boolean isUpdateNeeded(NodeState before, NodeState after) {
- // for Elastic we are not interested in checking for updates on
:status, index definition is enough.
+ // for Elastic, we are not interested in checking for updates on
:status, index definition is enough.
// The :status gets updated every time the indexed content is changed
(with properties like last_update_ts),
// removing the check on :status reduces drastically the contention
between queries (that need to acquire the
// read lock) and updates (need to acquire the write lock).
// Moreover, we don't check diffs in stored index definitions since
are not created for elastic.
- return !EqualsDiff.equals(before, after);
+ return !IgnoreStatusDiff.equals(before, after);
}
@Override
@@ -60,4 +61,36 @@ public class ElasticIndexTracker extends
FulltextIndexTracker<ElasticIndexNodeMa
public ElasticMetricHandler getElasticMetricHandler() {
return elasticMetricHandler;
}
+
+ static class IgnoreStatusDiff extends EqualsDiff {
+
+ public static boolean equals(NodeState before, NodeState after) {
+ return before.exists() == after.exists()
+ && after.compareAgainstBaseState(before, new
IgnoreStatusDiff());
+ }
+
+ /**
+ * The behaviour of this method is not immediately obvious from the
signature.
+ * For this reason, the javadoc from NodeStateDiff is copied below.
+ * When false is returned, the comparison is aborted because something
has changed.
+ * If the changed node is the status node, the comparison is continued
otherwise we call the super method.
+ * The super method will return false if the node is not the status
node, and it has changed.
+ * <p>
+ * Called for all child nodes that may contain changes between the
before
+ * and after states. The comparison implementation is expected to make
an
+ * effort to avoid calling this method on child nodes under which
nothing
+ * has changed.
+ *
+ * @param name name of the changed child node
+ * @param before child node state before the change
+ * @param after child node state after the change
+ * @return {@code true} to continue the comparison, {@code false} to
abort.
+ * Abort will stop comparing completely, that means sibling
nodes
+ * and sibling nodes of all parents are not further compared.
+ */
+ @Override
+ public boolean childNodeChanged(String name, NodeState before,
NodeState after) {
+ return IndexDefinition.STATUS_NODE.equals(name) ||
super.childNodeChanged(name, before, after);
+ }
+ }
}
diff --git
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexWriter.java
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexWriter.java
index 3bbd7d3daf..85540b1f47 100644
---
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexWriter.java
+++
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexWriter.java
@@ -150,12 +150,12 @@ class ElasticIndexWriter implements
FulltextIndexWriter<ElasticDocument> {
private void saveMetrics() {
ElasticIndexNode indexNode =
indexTracker.acquireIndexNode(indexDefinition.getIndexPath());
if (indexNode != null) {
- ElasticIndexStatistics stats = indexNode.getIndexStatistics();
try {
-
indexTracker.getElasticMetricHandler().markDocuments(indexName,
indexNode.getIndexStatistics().numDocs());
+ ElasticIndexStatistics stats = indexNode.getIndexStatistics();
+
indexTracker.getElasticMetricHandler().markDocuments(indexName,
stats.numDocs());
indexTracker.getElasticMetricHandler().markSize(indexName,
stats.primaryStoreSize(), stats.storeSize());
} catch (Exception e) {
- LOG.warn("Unable to store metrics for {}",
indexNode.getDefinition().getIndexPath(), e);
+ LOG.warn("Unable to store metrics for {}",
indexDefinition.getIndexPath(), e);
} finally {
indexNode.release();
}
diff --git
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticAbstractQueryTest.java
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticAbstractQueryTest.java
index 976ae264dd..9ffb7aa292 100644
---
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticAbstractQueryTest.java
+++
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticAbstractQueryTest.java
@@ -70,6 +70,7 @@ public abstract class ElasticAbstractQueryTest extends
AbstractQueryTest {
private static final String elasticConnectionString =
System.getProperty("elasticConnectionString");
protected ElasticConnection esConnection;
+ protected ElasticIndexTracker indexTracker;
// This is instantiated during repo creation but not hooked up to the
async indexing lane
// This can be used by the extending classes to trigger the async index
update as per need (not having to wait for async indexing cycle)
protected AsyncIndexUpdate asyncIndexUpdate;
@@ -139,7 +140,7 @@ public abstract class ElasticAbstractQueryTest extends
AbstractQueryTest {
@Override
protected ContentRepository createRepository() {
esConnection = getElasticConnection();
- ElasticIndexTracker indexTracker = new
ElasticIndexTracker(esConnection, getMetricHandler());
+ indexTracker = new ElasticIndexTracker(esConnection,
getMetricHandler());
ElasticIndexEditorProvider editorProvider = new
ElasticIndexEditorProvider(indexTracker, esConnection,
new ExtractedTextCache(10 * FileUtils.ONE_MB, 100));
ElasticIndexProvider indexProvider = new
ElasticIndexProvider(indexTracker);
diff --git
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexTest.java
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexTest.java
index 92309e7dd6..db137bdbfc 100644
---
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexTest.java
+++
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexTest.java
@@ -18,12 +18,17 @@ package
org.apache.jackrabbit.oak.plugins.index.elastic.index;
import org.apache.jackrabbit.oak.api.Tree;
import
org.apache.jackrabbit.oak.plugins.index.elastic.ElasticAbstractQueryTest;
+import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticIndexDefinition;
+import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticIndexNode;
+import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants;
import
org.apache.jackrabbit.oak.plugins.index.search.util.IndexDefinitionBuilder;
import org.junit.Test;
import java.util.UUID;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
public class ElasticIndexTest extends ElasticAbstractQueryTest {
@@ -37,4 +42,57 @@ public class ElasticIndexTest extends
ElasticAbstractQueryTest {
assertEventually(() -> assertEquals(ElasticIndexHelper.MAPPING_VERSION,
getElasticIndexDefinition(index).getMappingVersion()));
}
+
+ @Test
+ public void indexTrackerHandlesIndexDefinitionChanges() throws Exception {
+ IndexDefinitionBuilder builder = createIndex("a");
+ builder.indexRule("nt:base").property("a").propertyIndex();
+ Tree index = setIndex(UUID.randomUUID().toString(), builder);
+ root.commit();
+
+ ElasticIndexDefinition elasticIndexDefinitionT0;
+ ElasticIndexNode indexNodeT0 =
indexTracker.acquireIndexNode(index.getPath());
+ try {
+ elasticIndexDefinitionT0 = indexNodeT0.getDefinition();
+ assertNotNull(elasticIndexDefinitionT0);
+ } finally {
+ indexNodeT0.release();
+ }
+
+ Tree test = root.getTree("/").addChild("test");
+ test.addChild("t").setProperty("a", "foo");
+ root.commit();
+
+ ElasticIndexDefinition elasticIndexDefinitionT1;
+ ElasticIndexNode indexNodeT1 =
indexTracker.acquireIndexNode(index.getPath());
+ try {
+ elasticIndexDefinitionT1 = indexNodeT1.getDefinition();
+ assertNotNull(elasticIndexDefinitionT1);
+ } finally {
+ indexNodeT1.release();
+ }
+
+ // no changes in the index node and definition when the index content
gets updated
+ assertEquals(indexNodeT0, indexNodeT1);
+ assertEquals(elasticIndexDefinitionT0, elasticIndexDefinitionT1);
+
+ Tree b =
index.getChild("indexRules").getChild("nt:base").getChild("properties").addChild("b");
+ b.setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true);
+ b.setProperty(FulltextIndexConstants.PROP_ANALYZED, true);
+ root.commit();
+
+ ElasticIndexDefinition elasticIndexDefinitionT2;
+ ElasticIndexNode indexNodeT2 =
indexTracker.acquireIndexNode(index.getPath());
+ try {
+ elasticIndexDefinitionT2 = indexNodeT2.getDefinition();
+ assertNotNull(elasticIndexDefinitionT2);
+ } finally {
+ indexNodeT2.release();
+ }
+
+ // index node and definition are different after the index definition
change
+ assertNotEquals(indexNodeT1, indexNodeT2);
+ assertNotEquals(elasticIndexDefinitionT1, elasticIndexDefinitionT2);
+ assertNotNull(elasticIndexDefinitionT2.getPropertiesByName().get("b"));
+ }
}