You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by st...@apache.org on 2022/09/28 09:21:07 UTC
[jackrabbit-oak] branch trunk updated: OAK-9880 : simplify rgc query (#651)
This is an automated email from the ASF dual-hosted git repository.
stefanegli 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 9e93c3a865 OAK-9880 : simplify rgc query (#651)
9e93c3a865 is described below
commit 9e93c3a8659c5394693bd3d7ab1b09638d93fe05
Author: stefan-egli <st...@apache.org>
AuthorDate: Wed Sep 28 11:21:00 2022 +0200
OAK-9880 : simplify rgc query (#651)
* OAK-9880 : simplify rgc query
* OAK-9880 : method renamed to reflect new return type
* OAK-9880 : test case added
* OAK-9880 : added lastDefaultNoBranchDeletionRevs to MongoVersionGCSupport - plus more tests. This will now avoid doing queries for idle clusterNodeIds , something which is more relevant now that we split one of the queries into several, one per clusterNodeId
* OAK-9880 : more asserts to count number of deleteMany calls
* OAK-9880 : remove unused/orphaned method
* OAK-9880 : moved deleteMany counters to CountingMongoCollection
---
.../document/mongo/MongoVersionGCSupport.java | 51 ++-
.../MongoVersionGCSupportDefaultNoBranchTest.java | 408 +++++++++++++++++++++
.../document/mongo/MongoTestCollection.java | 2 +-
.../plugins/document/mongo/MongoTestDatabase.java | 4 +-
.../document/prefetch/CountingMongoCollection.java | 33 ++
5 files changed, 483 insertions(+), 15 deletions(-)
diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoVersionGCSupport.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoVersionGCSupport.java
index 910cf8d4cd..1f8e95a929 100644
--- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoVersionGCSupport.java
+++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoVersionGCSupport.java
@@ -35,6 +35,7 @@ import static org.apache.jackrabbit.oak.plugins.document.NodeDocument.SplitDocTy
import static org.apache.jackrabbit.oak.plugins.document.mongo.MongoUtils.hasIndex;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -82,6 +83,10 @@ public class MongoVersionGCSupport extends VersionGCSupport {
private final MongoDocumentStore store;
private final BasicDBObject hint;
+
+ /** keeps track of the sweepRev of the last (successful) deletion */
+ private RevisionVector lastDefaultNoBranchDeletionRevs;
+
/**
* The batch size for the query of possibly deleted docs.
*/
@@ -197,7 +202,7 @@ public class MongoVersionGCSupport extends VersionGCSupport {
if (DEFAULT_NO_BRANCH != type) {
orClauses.add(Filters.eq(SD_TYPE, type.typeCode()));
} else {
- result.add(queryForDefaultNoBranch(sweepRevs, getModifiedInSecs(oldestRevTimeStamp)));
+ result.addAll(queriesForDefaultNoBranch(sweepRevs, getModifiedInSecs(oldestRevTimeStamp)));
}
}
// OAK-8351: this (last) query only contains SD_TYPE and SD_MAX_REV_TIME_IN_SECS
@@ -211,11 +216,23 @@ public class MongoVersionGCSupport extends VersionGCSupport {
}
@NotNull
- private Bson queryForDefaultNoBranch(RevisionVector sweepRevs, long maxRevTimeInSecs) {
+ private List<Bson> queriesForDefaultNoBranch(RevisionVector sweepRevs, long maxRevTimeInSecs) {
// default_no_branch split type is special because we can
// only remove those older than sweep rev
- List<Bson> orClauses = Lists.newArrayList();
+ List<Bson> result = Lists.newArrayList();
for (Revision r : sweepRevs) {
+ if (lastDefaultNoBranchDeletionRevs != null) {
+ Revision dr = lastDefaultNoBranchDeletionRevs.getRevision(r.getClusterId());
+ if (dr != null) {
+ if (dr.getTimestamp() == r.getTimestamp()) {
+ // implies for this clusterNodeId the sweepRev is in control wrt RGC
+ // and we've already deleted up to that point.
+ // and meanwhile the sweepRev for that clusterNodeId hasn't changed.
+ // so we can skip it
+ continue;
+ }
+ }
+ }
String idSuffix = Utils.getPreviousIdFor(Path.ROOT, r, 0);
idSuffix = idSuffix.substring(idSuffix.lastIndexOf('-'));
@@ -229,16 +246,14 @@ public class MongoVersionGCSupport extends VersionGCSupport {
)
);
- orClauses.add(Filters.and(
- idPathClause,
- Filters.lt(SD_MAX_REV_TIME_IN_SECS, getModifiedInSecs(r.getTimestamp()))
- ));
+ long minMaxRevTimeInSecs = Math.min(maxRevTimeInSecs, getModifiedInSecs(r.getTimestamp()));
+ result.add(Filters.and(
+ Filters.eq(SD_TYPE, DEFAULT_NO_BRANCH.typeCode()),
+ Filters.lt(SD_MAX_REV_TIME_IN_SECS, minMaxRevTimeInSecs),
+ idPathClause
+ ));
}
- return Filters.and(
- Filters.eq(SD_TYPE, DEFAULT_NO_BRANCH.typeCode()),
- Filters.lt(SD_MAX_REV_TIME_IN_SECS, maxRevTimeInSecs),
- Filters.or(orClauses)
- );
+ return result;
}
private void logSplitDocIdsTobeDeleted(Bson query) {
@@ -301,6 +316,18 @@ public class MongoVersionGCSupport extends VersionGCSupport {
for (Bson query : queries) {
cnt += getNodeCollection().deleteMany(query).getDeletedCount();
}
+ // remember the revisions up to which deletion happened
+ Set<Revision> deletionRevs = new HashSet<>();
+ for (Revision r : sweepRevs) {
+ if (r.getTimestamp() <= oldestRevTimeStamp) {
+ // then sweepRev of this clusterNodeId was taking effect: we could only delete up to sweepRev
+ deletionRevs.add(r);
+ } else {
+ // then sweepRev is newer than oldestRevTimeStamp, so we could delete up to oldestRevTimeStamp
+ deletionRevs.add(new Revision(oldestRevTimeStamp, 0, r.getClusterId()));
+ }
+ }
+ MongoVersionGCSupport.this.lastDefaultNoBranchDeletionRevs = new RevisionVector(deletionRevs);
return cnt;
}
}
diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MongoVersionGCSupportDefaultNoBranchTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MongoVersionGCSupportDefaultNoBranchTest.java
new file mode 100644
index 0000000000..234718f18a
--- /dev/null
+++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MongoVersionGCSupportDefaultNoBranchTest.java
@@ -0,0 +1,408 @@
+/*
+ * 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.document;
+
+import static org.apache.jackrabbit.oak.plugins.document.Collection.NODES;
+import static org.apache.jackrabbit.oak.plugins.document.DocumentStoreFixture.MONGO;
+import static org.apache.jackrabbit.oak.plugins.document.NodeDocument.SplitDocType.COMMIT_ROOT_ONLY;
+import static org.apache.jackrabbit.oak.plugins.document.NodeDocument.SplitDocType.DEFAULT_LEAF;
+import static org.apache.jackrabbit.oak.plugins.document.NodeDocument.SplitDocType.DEFAULT_NO_BRANCH;
+import static org.apache.jackrabbit.oak.plugins.document.SplitOperations.forDocument;
+import static org.apache.jackrabbit.oak.plugins.document.util.Utils.getIdFromPath;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.document.DocumentMK.Builder;
+import org.apache.jackrabbit.oak.plugins.document.DocumentStoreFixture.MongoFixture;
+import org.apache.jackrabbit.oak.plugins.document.NodeDocument.SplitDocType;
+import org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector.VersionGCStats;
+import org.apache.jackrabbit.oak.plugins.document.mongo.MongoDocumentStore;
+import org.apache.jackrabbit.oak.plugins.document.mongo.MongoDocumentStoreTestHelper;
+import org.apache.jackrabbit.oak.plugins.document.mongo.MongoTestUtils;
+import org.apache.jackrabbit.oak.plugins.document.mongo.MongoVersionGCSupport;
+import org.apache.jackrabbit.oak.plugins.document.prefetch.CountingMongoDatabase;
+import org.apache.jackrabbit.oak.plugins.document.util.MongoConnection;
+import org.apache.jackrabbit.oak.plugins.document.util.Utils;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.stats.Clock;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Lists;
+import com.mongodb.ReadPreference;
+
+@RunWith(Parameterized.class)
+public class MongoVersionGCSupportDefaultNoBranchTest {
+
+ private static class Stats {
+ private final VersionGCStats versionGCStats;
+ private final int nodesDeleteMany;
+ Stats(VersionGCStats stats, int nodesDeleteMany) {
+ if (stats == null) {
+ throw new IllegalArgumentException("stats must not be null");
+ }
+ if (nodesDeleteMany < 0) {
+ throw new IllegalArgumentException("nodesDeleteMany must be positive");
+ }
+ this.versionGCStats = stats;
+ this.nodesDeleteMany = nodesDeleteMany;
+ }
+ }
+
+ private static Predicate<NodeDocument> splitDocsWithClusterId(final int clusterId) {
+ return new Predicate<NodeDocument>() {
+ @Override
+ public boolean apply(@Nullable NodeDocument doc) {
+ if (!Utils.isPreviousDocId(doc.getId())) {
+ return false;
+ }
+ Path p = doc.getPath();
+ p = p.getAncestor(1);
+ Revision rev = Revision.fromString(p.getName());
+ return rev.getClusterId() == clusterId;
+ }
+ };
+ }
+
+ private static final Set<NodeDocument.SplitDocType> GC_TYPES = EnumSet.of(
+ DEFAULT_LEAF, COMMIT_ROOT_ONLY, DEFAULT_NO_BRANCH);
+
+ class MongoVersionGCSupportAccessor extends MongoVersionGCSupport {
+
+ public MongoVersionGCSupportAccessor(MongoDocumentStore store) {
+ super(store);
+ }
+
+ protected Iterable<NodeDocument> identifyGarbage(Set<SplitDocType> gcTypes,
+ RevisionVector sweepRevs, long oldestRevTimeStamp) {
+ return super.identifyGarbage(gcTypes, sweepRevs, oldestRevTimeStamp);
+ }
+ }
+ // using AbstractTwoNodeTest as a helper rather than subclassing to simplify things
+ private AbstractTwoNodeTest helper;
+ private DocumentStoreFixture fixture;
+ private DocumentStore store1;
+ private DocumentStore store2;
+ protected DocumentNodeStore ds1;
+ protected DocumentNodeStore ds2;
+ private VersionGCSupport gcSupport1;
+ private CountingMongoDatabase db;
+ private List<String> ids = Lists.newArrayList();
+
+ private Clock clock;
+ private AtomicInteger offset = new AtomicInteger(0);
+
+ @Parameterized.Parameters(name="{0}")
+ public static java.util.Collection<DocumentStoreFixture> fixtures() {
+ List<DocumentStoreFixture> fixtures = Lists.newArrayList();
+ if (MONGO.isAvailable()) {
+ fixtures.add(new MongoFixture() {
+ @Override
+ public DocumentStore createDocumentStore(Builder builder) {
+ try {
+ MongoConnection connection = MongoUtils.getConnection();
+ CountingMongoDatabase db = new CountingMongoDatabase(connection.getDatabase());
+ return new MongoDocumentStore(connection.getMongoClient(),db, builder);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ }
+ return fixtures;
+ }
+
+ public MongoVersionGCSupportDefaultNoBranchTest(final DocumentStoreFixture fixture) throws InterruptedException {
+ this.fixture = fixture;
+ helper = new AbstractTwoNodeTest(fixture);
+ helper.setUp();
+ this.store1 = helper.store1;
+ this.store2 = helper.store2;
+ this.clock = helper.clock;
+ this.ds1 = helper.ds1;
+ this.ds2 = helper.ds2;
+
+ // Enforce primary read preference, otherwise tests may fail on a
+ // replica set with a read preference configured to secondary.
+ // Revision GC usually runs with a modified range way in the past,
+ // which means changes made it to the secondary, but not in this
+ // test using a virtual clock
+ MongoTestUtils.setReadPreference(store1, ReadPreference.primary());
+ MongoTestUtils.setReadPreference(store2, ReadPreference.primary());
+
+ if (store1 instanceof MongoDocumentStore) {
+ MongoDocumentStore mds = (MongoDocumentStore) store1;
+ this.gcSupport1 = new MongoVersionGCSupportAccessor(mds);
+ this.db = (CountingMongoDatabase) MongoDocumentStoreTestHelper.getDB(mds);
+ } else {
+ this.gcSupport1 = new VersionGCSupport(store1);
+ }
+
+ resetNodesDeleteMany();
+ }
+
+ private int resetNodesDeleteMany() {
+ if (this.db != null) {
+ return this.db.getCachedCountingCollection("nodes").resetNodesDeleteMany();
+ } else {
+ return 0;
+ }
+ }
+
+ private int getNodesDeleteMany() {
+ if (this.db != null) {
+ return this.db.getCachedCountingCollection("nodes").getNodesDeleteMany();
+ } else {
+ return 0;
+ }
+ }
+
+ @After
+ public void after() throws Exception {
+ store1.remove(Collection.NODES, ids);
+ helper.tearDown();
+ fixture.dispose();
+ }
+
+ public Stats deleteSplitDocuments(VersionGCSupport gcSupport, RevisionVector sweepRevs, long oldestRevTimeStamp) {
+ int a = getNodesDeleteMany();
+ VersionGCStats stats = new VersionGCStats();
+ ((VersionGCSupport)gcSupport).deleteSplitDocuments(GC_TYPES, sweepRevs, oldestRevTimeStamp, stats);;
+ int b = getNodesDeleteMany() - a;
+ return new Stats(stats, b);
+ }
+
+ @Test
+ public void testBothInstancesSplit_none_none() throws Exception {
+ doTestBothInstancesSplit(0, 0);
+ }
+
+ @Test
+ public void testBothInstancesSplit_none_one() throws Exception {
+ doTestBothInstancesSplit(0, 1);
+ }
+
+ @Test
+ public void testBothInstancesSplit_one_none() throws Exception {
+ doTestBothInstancesSplit(1, 0);
+ }
+
+ @Test
+ public void testBothInstancesSplit_two_three() throws Exception {
+ doTestBothInstancesSplit(2, 3);
+ assertEquals(4, getNodesDeleteMany());
+ }
+
+ @Test
+ public void testBothInstancesSplit_three_two() throws Exception {
+ doTestBothInstancesSplit(3, 2);
+ assertEquals(4, getNodesDeleteMany());
+ }
+
+ @Test
+ public void testBothInstancesSplit_many_many() throws Exception {
+ doTestBothInstancesSplit(7, 9);
+ assertEquals(4, getNodesDeleteMany());
+ }
+
+ public void doTestBothInstancesSplit(int numSplit1, int numSplit2) throws Exception {
+ final int totalSplits = numSplit1 + numSplit2;
+
+ waitALittle();
+
+ modify(ds1, (b) -> b.child("foo"));
+
+ waitALittle();
+
+ assertNumSplitDocs(store1, "/foo", 0);
+ assertNumSplitDocs(store2, "/foo", 0);
+
+ ds1.runBackgroundOperations();
+ ds2.runBackgroundOperations();
+
+ for(int i = 0; i < numSplit1; i++) {
+ splitPathNoBranch(ds1, "/foo", ds2);
+ }
+ for(int i = 0; i < numSplit2; i++) {
+ splitPathNoBranch(ds2, "/foo", ds1);
+ }
+
+ ds1.runBackgroundOperations();
+ ds2.runBackgroundOperations();
+
+ waitALittle();
+ if (numSplit1 > 0) {
+ modify(ds1, (b) -> b.setProperty("sweeptrigger", offset.getAndIncrement()));
+ }
+ ds1.runBackgroundOperations();
+ ds2.runBackgroundOperations();
+ if (numSplit2 > 0) {
+ modify(ds2, (b) -> b.setProperty("sweeptrigger", offset.getAndIncrement()));
+ }
+ ds2.runBackgroundOperations();
+ ds1.runBackgroundOperations();
+ long oldestRevTimeStamp = clock.getTime();
+ ds1.runBackgroundOperations();
+ ds2.runBackgroundOperations();
+
+ assertNumSplitDocs(store1, "/foo", totalSplits);
+ assertNumSplitDocs(store2, "/foo", totalSplits);
+
+ RevisionVector sweepRevs = ds1.getSweepRevisions();
+ Iterable<NodeDocument> garbage = gcSupport1.identifyGarbage(GC_TYPES, sweepRevs, oldestRevTimeStamp);
+ assertNotNull(garbage);
+ assertEquals(totalSplits, Iterables.size(garbage));
+ assertEquals(numSplit1, Iterables.size(Iterables.filter(garbage, splitDocsWithClusterId(1))));
+ assertEquals(numSplit2, Iterables.size(Iterables.filter(garbage, splitDocsWithClusterId(2))));
+
+ Stats stats = deleteSplitDocuments(gcSupport1, sweepRevs, oldestRevTimeStamp);
+ assertNotNull(stats);
+ assertEquals(totalSplits, stats.versionGCStats.splitDocGCCount);
+ ds1.runBackgroundOperations();
+ ds2.runBackgroundOperations();
+ ds1.runBackgroundOperations();
+ ds2.runBackgroundOperations();
+ ds1.runBackgroundOperations();
+ ds2.runBackgroundOperations();
+ assertNumSplitDocs(store1, "/foo", 0);
+ assertNumSplitDocs(store2, "/foo", 0);
+
+ waitALittle();
+ oldestRevTimeStamp = clock.getTime();
+ sweepRevs = ds1.getSweepRevisions();
+ stats = deleteSplitDocuments(gcSupport1, sweepRevs, oldestRevTimeStamp);
+ assertNotNull(stats);
+ assertEquals(0, stats.versionGCStats.splitDocGCCount);
+ assertEquals(1, stats.nodesDeleteMany);
+ }
+
+ public void modify(DocumentNodeStore ds, Function<NodeBuilder,NodeBuilder> builderFunction) throws CommitFailedException {
+ NodeBuilder builder = ds.getRoot().builder();
+ builderFunction.apply(builder);
+ TestUtils.merge(ds, builder);
+ }
+
+ @Test
+ public void testLastDefaultNoBranchDeletionRevs() throws Exception {
+ final int NUM_BOTH_SPLITS = 10;
+ for(int i = 0; i < NUM_BOTH_SPLITS; i++) {
+ doTestBothInstancesSplit(1,2);
+ }
+ ds1.runBackgroundOperations();
+ ds2.runBackgroundOperations();
+ int removesCount = resetNodesDeleteMany();
+ // there should be 4 deleteMany calls : first call results in 3, second in 1 => 4
+ assertEquals(4 * NUM_BOTH_SPLITS, removesCount);
+ for(int i = 0; i < 10; i++) {
+ doTestBothInstancesSplit(1,0);
+ }
+ ds1.runBackgroundOperations();
+ ds2.runBackgroundOperations();
+ removesCount = resetNodesDeleteMany();
+ // there should be between 3 and 4 deleteMany calls due to avoiding calls if nothing changed
+ // but seems every other (more or less) iteration there is still a change in _lastRev
+ // so the condition is strict >3 and <4
+ assertTrue(3 * NUM_BOTH_SPLITS < removesCount);
+ assertTrue(4 * NUM_BOTH_SPLITS > removesCount);
+ for(int i = 0; i < 10; i++) {
+ doTestBothInstancesSplit(0, 1);
+ }
+ ds1.runBackgroundOperations();
+ ds2.runBackgroundOperations();
+ removesCount = resetNodesDeleteMany();
+ // there should be between 3 and 4 deleteMany calls due to avoiding calls if nothing changed
+ // but seems every other (more or less) iteration there is still a change in _lastRev
+ // so the condition is strict >3 and <4
+ assertTrue(3 * NUM_BOTH_SPLITS < removesCount);
+ assertTrue(4 * NUM_BOTH_SPLITS > removesCount);
+
+ assertNumSplitDocs(store1, "/foo", 0);
+ assertNumSplitDocs(store2, "/foo", 0);
+
+ waitALittle();
+ long oldestRevTimeStamp = clock.getTime();
+ @NotNull
+ RevisionVector sweepRevs = ds1.getSweepRevisions();
+ Stats stats = deleteSplitDocuments(gcSupport1, sweepRevs, oldestRevTimeStamp);
+ assertNotNull(stats);
+ assertEquals(0, stats.versionGCStats.splitDocGCCount);
+ assertEquals(1, stats.nodesDeleteMany);
+ }
+
+ private void waitALittle() throws InterruptedException {
+ clock.waitUntil(clock.getTime() + TimeUnit.SECONDS.toMillis(NodeDocument.MODIFIED_IN_SECS_RESOLUTION * 2));
+ }
+
+ private void assertNumSplitDocs(DocumentStore ds, String path, int expected) throws Exception {
+ assertEquals(expected, getNumSplitDocuments(ds, path));
+ }
+
+ private void splitPathNoBranch(DocumentNodeStore ns, String path, DocumentNodeStore... otherStores)
+ throws Exception {
+ NodeBuilder builder = ns.getRoot().builder();
+ NodeBuilder nb = builder;
+ for (String name : PathUtils.elements(path)) {
+ nb = nb.child(name);
+ }
+ nb.child("noBranchChild"); // DEFAULT_NO_BRANCH requires a child, otherwise it could be DEFAULT_LEAF
+ nb.setProperty("o", offset.getAndIncrement());
+ TestUtils.merge(ns, builder);
+ nb.setProperty("o", offset.getAndIncrement());
+ TestUtils.merge(ns, builder);
+ nb.setProperty("o", offset.getAndIncrement());
+ TestUtils.merge(ns, builder);
+
+ DocumentStore store = ns.getDocumentStore();
+ NodeDocument doc = store.find(NODES, getIdFromPath(path));
+ List<UpdateOp> ops = forDocument(doc, ns, ns.getHeadRevision(),
+ TestUtils.NO_BINARY, 2);
+ assertFalse(ops.isEmpty());
+ store.createOrUpdate(NODES, ops);
+
+ ns.runBackgroundOperations();
+ for (DocumentNodeStore dns : otherStores) {
+ dns.runBackgroundOperations();
+ }
+ }
+
+ private static int getNumSplitDocuments(DocumentStore store, String path)
+ throws Exception {
+ NodeDocument doc = store.find(NODES, getIdFromPath(path), -1);
+ assertNotNull(doc);
+ return Iterators.size(doc.getAllPreviousDocs());
+ }
+}
diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoTestCollection.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoTestCollection.java
index 64940da187..051f99d49d 100644
--- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoTestCollection.java
+++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoTestCollection.java
@@ -67,7 +67,7 @@ public class MongoTestCollection<TDocument> implements MongoCollection<TDocument
private final AtomicReference<String> beforeUpdateException;
private final AtomicReference<String> afterUpdateException;
- protected MongoTestCollection(MongoCollection<TDocument> collection,
+ public MongoTestCollection(MongoCollection<TDocument> collection,
AtomicReference<String> beforeQueryException,
AtomicReference<String> beforeUpdateException,
AtomicReference<String> afterUpdateException) {
diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoTestDatabase.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoTestDatabase.java
index b48f6e93de..27d89cffc9 100644
--- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoTestDatabase.java
+++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoTestDatabase.java
@@ -37,7 +37,7 @@ import org.bson.codecs.configuration.CodecRegistry;
import org.bson.conversions.Bson;
import org.jetbrains.annotations.NotNull;
-class MongoTestDatabase implements MongoDatabase {
+public class MongoTestDatabase implements MongoDatabase {
private final MongoDatabase db;
@@ -45,7 +45,7 @@ class MongoTestDatabase implements MongoDatabase {
private final AtomicReference<String> beforeUpdateException;
private final AtomicReference<String> afterUpdateException;
- MongoTestDatabase(MongoDatabase db,
+ public MongoTestDatabase(MongoDatabase db,
AtomicReference<String> beforeQueryException,
AtomicReference<String> beforeUpdateException,
AtomicReference<String> afterUpdateException) {
diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/prefetch/CountingMongoCollection.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/prefetch/CountingMongoCollection.java
index b79a7b211a..55bdc0b267 100644
--- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/prefetch/CountingMongoCollection.java
+++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/prefetch/CountingMongoCollection.java
@@ -33,6 +33,7 @@ public class CountingMongoCollection<TDocument> extends MongoTestCollection<TDoc
private final MongoCollection<TDocument> countingCollectionDelegate;
private AtomicInteger findCounter = new AtomicInteger(0);
+ private AtomicInteger nodesDeleteMany = new AtomicInteger(0);
CountingMongoCollection(MongoCollection<TDocument> collection,
AtomicReference<String> beforeQueryException,
@@ -62,6 +63,38 @@ public class CountingMongoCollection<TDocument> extends MongoTestCollection<TDoc
findCounter.incrementAndGet();
}
+ public int getNodesDeleteMany() {
+ return nodesDeleteMany.get();
+ }
+
+ public void incNodesDeleteMany() {
+ nodesDeleteMany.incrementAndGet();
+ }
+
+ public int resetNodesDeleteMany() {
+ return nodesDeleteMany.getAndSet(0);
+ }
+
+ public com.mongodb.client.result.DeleteResult deleteMany(org.bson.conversions.Bson filter) {
+ incNodesDeleteMany();
+ return countingCollectionDelegate.deleteMany(filter);
+ };
+
+ public com.mongodb.client.result.DeleteResult deleteMany(org.bson.conversions.Bson filter, com.mongodb.client.model.DeleteOptions options) {
+ incNodesDeleteMany();
+ return countingCollectionDelegate.deleteMany(filter, options);
+ };
+
+ public com.mongodb.client.result.DeleteResult deleteMany(com.mongodb.client.ClientSession clientSession, org.bson.conversions.Bson filter) {
+ incNodesDeleteMany();
+ return countingCollectionDelegate.deleteMany(clientSession, filter);
+ };
+
+ public com.mongodb.client.result.DeleteResult deleteMany(com.mongodb.client.ClientSession clientSession, org.bson.conversions.Bson filter, com.mongodb.client.model.DeleteOptions options) {
+ incNodesDeleteMany();
+ return countingCollectionDelegate.deleteMany(clientSession, filter, options);
+ };
+
@NotNull
@Override
public FindIterable<TDocument> find() {