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 da...@apache.org on 2023/09/11 08:55:15 UTC
[jackrabbit-oak] 02/02: OAK-8646 : added metrics for unmergedbranch commits & improved junit coverage
This is an automated email from the ASF dual-hosted git repository.
daim pushed a commit to branch OAK-8646
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
commit e57ca06a5bcc6869f199a03b7866f0237405ffa8
Author: Rishabh Kumar <di...@adobe.com>
AuthorDate: Mon Sep 11 14:24:54 2023 +0530
OAK-8646 : added metrics for unmergedbranch commits & improved junit coverage
---
.gitignore | 1 +
.../document/DetailedRevisionGCStatsCollector.java | 31 +-
.../DetailedRevisionGCStatsCollectorImpl.java | 10 +-
.../plugins/document/VersionGarbageCollector.java | 192 +++++++------
.../oak/plugins/document/BranchCommitGCTest.java | 315 +++++++++++----------
.../oak/plugins/document/DetailGCHelper.java | 61 ++++
.../DetailedRevisionGCStatsCollectorImplTest.java | 12 +-
.../document/VersionGarbageCollectorIT.java | 145 ++++++++--
8 files changed, 490 insertions(+), 277 deletions(-)
diff --git a/.gitignore b/.gitignore
index d13284858c..60e0127c72 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@ atlassian-ide-plugin.xml
derby.log
.java-version
oak-shaded-guava/dependency-reduced-pom.xml
+.DS_Store
diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DetailedRevisionGCStatsCollector.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DetailedRevisionGCStatsCollector.java
index 151195f3bd..0a965f44be 100644
--- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DetailedRevisionGCStatsCollector.java
+++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DetailedRevisionGCStatsCollector.java
@@ -26,15 +26,44 @@ import org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector.Versio
*/
public interface DetailedRevisionGCStatsCollector {
+ /**
+ * Total No. of documents read during DetailedGC phase
+ */
void documentRead();
+ /**
+ * No. of properties deleted during DetailedGC
+ * @param numProps no. of properties deleted in current cycle
+ */
void propertiesDeleted(long numProps);
+ /**
+ * No. of unmerged (unique) branch commits deleted during DetailedGC
+ * @param numCommits no. of unmerged branch commits deleted in current cycle
+ */
+ void unmergedBranchCommitsDeleted(long numCommits);
+
+ /**
+ * No. of documents updated (i.e. have garbage removed) during DetailedGC
+ * @param numDocs no. of documents updated in current cycle
+ */
void documentsUpdated(long numDocs);
- void documentsSkippedUpdation(long numDocs);
+ /**
+ * No. of documents which had skipped update (i.e. have been updated between garbage collection & removal)
+ * during DetailedGC
+ * @param numDocs No. of documents which had skipped update in current cycle
+ */
+ void documentsUpdateSkipped(long numDocs);
+ /**
+ * No. of times the DetailedGC has started
+ */
void started();
+ /**
+ * Timer for different phases in DetailedGC
+ * @param stats {@link VersionGCStats} containing DetailedGC phases timer
+ */
void finished(VersionGCStats stats);
}
diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DetailedRevisionGCStatsCollectorImpl.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DetailedRevisionGCStatsCollectorImpl.java
index b400f7401b..01f196831f 100644
--- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DetailedRevisionGCStatsCollectorImpl.java
+++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DetailedRevisionGCStatsCollectorImpl.java
@@ -36,6 +36,7 @@ class DetailedRevisionGCStatsCollectorImpl implements DetailedRevisionGCStatsCol
static final String DETAILED_GC = "DetailedGC";
static final String READ_DOC = "READ_DOC";
static final String DELETED_PROPERTY = "DELETED_PROPERTY";
+ static final String DELETED_UNMERGED_BC = "DELETED_UNMERGED_BC";
static final String UPDATED_DOC = "UPDATED_DOC";
static final String SKIPPED_DOC = "SKIPPED_DOC";
static final String DETAILED_GC_ACTIVE_TIMER = "DETAILED_GC_ACTIVE_TIMER";
@@ -51,6 +52,7 @@ class DetailedRevisionGCStatsCollectorImpl implements DetailedRevisionGCStatsCol
private final MeterStats readDoc;
private final MeterStats deletedProperty;
+ private final MeterStats deletedUnmergedBC;
private final MeterStats updatedDoc;
private final MeterStats skippedDoc;
private final TimerStats detailedGCActiveTimer;
@@ -68,6 +70,7 @@ class DetailedRevisionGCStatsCollectorImpl implements DetailedRevisionGCStatsCol
readDoc = meter(provider, READ_DOC);
deletedProperty = meter(provider, DELETED_PROPERTY);
+ deletedUnmergedBC = meter(provider, DELETED_UNMERGED_BC);
updatedDoc = meter(provider, UPDATED_DOC);
skippedDoc = meter(provider, SKIPPED_DOC);
@@ -95,13 +98,18 @@ class DetailedRevisionGCStatsCollectorImpl implements DetailedRevisionGCStatsCol
deletedProperty.mark(numProps);
}
+ @Override
+ public void unmergedBranchCommitsDeleted(long numCommits) {
+ deletedUnmergedBC.mark(numCommits);
+ }
+
@Override
public void documentsUpdated(long numDocs) {
updatedDoc.mark(numDocs);
}
@Override
- public void documentsSkippedUpdation(long numDocs) {
+ public void documentsUpdateSkipped(long numDocs) {
skippedDoc.mark(numDocs);
}
diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollector.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollector.java
index 7cd1604ec8..9e69241a68 100644
--- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollector.java
+++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollector.java
@@ -26,12 +26,12 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@@ -301,7 +301,8 @@ public class VersionGarbageCollector {
long oldestModifiedDocTimeStamp;
String oldestModifiedDocId;
int updatedDetailedGCDocsCount;
- int deletedPropsGCCount;
+ int deletedPropsCount;
+ int deletedUnmergedBCCount;
final TimeDurationFormatter df = TimeDurationFormatter.forLogging();
final Stopwatch active = Stopwatch.createUnstarted();
final Stopwatch detailedGCActive = Stopwatch.createUnstarted();
@@ -378,7 +379,8 @@ public class VersionGarbageCollector {
", oldestModifiedDocId=" + oldestModifiedDocId +
", oldestModifiedDocTimeStamp=" + oldestModifiedDocTimeStamp +
", updatedDetailedGCDocsCount=" + updatedDetailedGCDocsCount +
- ", deletedPropsGCCount=" + deletedPropsGCCount +
+ ", deletedPropsCount=" + deletedPropsCount +
+ ", deletedUnmergedBCCount=" + deletedUnmergedBCCount +
", iterationCount=" + iterationCount +
", timeDetailedGCActive=" + df.format(detailedGCActiveElapsed, MICROSECONDS) +
", timeActive=" + df.format(activeElapsed, MICROSECONDS) +
@@ -401,7 +403,8 @@ public class VersionGarbageCollector {
this.oldestModifiedDocTimeStamp = run.oldestModifiedDocTimeStamp;
this.oldestModifiedDocId = run.oldestModifiedDocId;
this.updatedDetailedGCDocsCount += run.updatedDetailedGCDocsCount;
- this.deletedPropsGCCount += run.deletedPropsGCCount;
+ this.deletedPropsCount += run.deletedPropsCount;
+ this.deletedUnmergedBCCount += run.deletedUnmergedBCCount;
if (run.iterationCount > 0) {
// run is cumulative with times in elapsed fields
this.activeElapsed += run.activeElapsed;
@@ -860,6 +863,12 @@ public class VersionGarbageCollector {
* In order to calculate the correct no. of updated documents & deleted properties, we save them in a map
*/
private final Map<String, Integer> deletedPropsCountMap;
+
+ /**
+ * {@link Set} of unmergedBranchCommit Revisions to calculate the no. of unmergedBranchCommits that would be
+ * removed in this iteration of DetailedGC.
+ */
+ private final Set<Revision> deletedUnmergedBCSet;
private int garbageDocsCount;
private int totalGarbageDocsCount;
private final Revision revisionForModified;
@@ -871,6 +880,7 @@ public class VersionGarbageCollector {
this.cancel = cancel;
this.updateOpList = new ArrayList<>();
this.deletedPropsCountMap = new HashMap<>();
+ this.deletedUnmergedBCSet = new HashSet<>();
this.timer = createUnstarted();
// clusterId is not used
this.revisionForModified = Revision.newRevision(0);
@@ -885,7 +895,7 @@ public class VersionGarbageCollector {
op.equals(MODIFIED_IN_SECS, doc.getModified());
collectDeletedProperties(doc, phases, op);
- collectUnmergedBranchCommitDocument(doc, toModifiedMs, phases, op);
+ collectUnmergedBranchCommits(doc, phases, op, toModifiedMs);
collectOldRevisions(doc, phases, op);
// only add if there are changes for this doc
if (op.hasChanges()) {
@@ -894,6 +904,9 @@ public class VersionGarbageCollector {
monitor.info("Collected [{}] garbage for doc [{}]", op.getChanges().size(), doc.getId());
updateOpList.add(op);
}
+ if (log.isDebugEnabled()) {
+ log.debug("UpdateOp for {} is {}", doc.getId(), op);
+ }
}
private boolean hasGarbage() {
@@ -916,118 +929,116 @@ public class VersionGarbageCollector {
.map(p -> p.stream().map(Utils::escapePropertyName).collect(toSet()))
.orElse(emptySet());
- final int deletedPropsGCCount = properties.stream()
+ final int deletedPropsCount = properties.stream()
.filter(p -> !retainPropSet.contains(p))
.mapToInt(x -> {
updateOp.remove(x);
return 1;})
.sum();
- deletedPropsCountMap.put(doc.getId(), deletedPropsGCCount);
+ deletedPropsCountMap.put(doc.getId(), deletedPropsCount);
if (log.isDebugEnabled()) {
- log.debug("Collected {} deleted properties for document {}", deletedPropsGCCount, doc.getId());
+ log.debug("Collected {} deleted properties for document {}", deletedPropsCount, doc.getId());
}
phases.stop(GCPhase.DETAILED_GC_COLLECT_PROPS);
}
}
- private void collectUnmergedBranchCommitDocument(final NodeDocument doc,
- long toModifiedMillis, final GCPhases phases, final UpdateOp updateOp) {
+ private void collectUnmergedBranchCommits(final NodeDocument doc, final GCPhases phases, final UpdateOp updateOp,
+ final long toModifiedMs) {
if (!phases.start(GCPhase.DETAILED_GC_COLLECT_UNMERGED_BC)) {
// GC was cancelled, stop
return;
}
- // from
- // https://jackrabbit.apache.org/oak/docs/nodestore/documentmk.html#previous-documents
- // "branch commits are not moved to previous documents until the branch is
- // merged."
+ // from https://jackrabbit.apache.org/oak/docs/nodestore/documentmk.html#previous-documents
+ // "branch commits are not moved to previous documents until the branch is merged."
// i.e. if we're looking for unmerged branch commits, they cannot be in
// previous documents, they have to be in the main one - hence we have to use
// getLocalBranchCommits here
- final Set<Revision> localBranchCommits = doc.getLocalBranchCommits();
- if (localBranchCommits.isEmpty()) {
+ final Set<Revision> olderUnmergedBranchCommits = doc.getLocalBranchCommits().stream()
+ .filter(bcRevision -> isRevisionOlderThan(bcRevision, toModifiedMs))
+ .filter(bcRevision -> !isCommitted(nodeStore.getCommitValue(bcRevision, doc)))
+ .collect(toSet());
+
+ if (olderUnmergedBranchCommits.isEmpty()) {
// nothing to do then
phases.stop(GCPhase.DETAILED_GC_COLLECT_UNMERGED_BC);
return;
}
- // !Note, the _bc sub-document was introduced with Oak 1.8 and is not present
- // in older versions. The branch commit revision is added to _bc whenever a
- // change is done on a document with a branch commit. This helps the
- // DocumentNodeStore to more easily identify branch commit changes."
+ // !Note, the _bc sub-document was introduced with Oak 1.8 and is not present in older versions.
+ // The branch commit revision is added to _bc whenever a change is done on a document with a
+ // branch commit. This helps the DocumentNodeStore to more easily identify branch commit changes."
// The current implementation of "collectUnmergedBranchCommitDocument" only
// supports branch commits that are created after Oak 1.8
- for (Revision bcRevision : localBranchCommits) {
- if (!isRevisionOlderThan(bcRevision, toModifiedMillis)) {
- // only even consider revisions that are older than the provided
- // timestamp - otherwise skip this
- continue;
- }
- final String commitValue = nodeStore.getCommitValue(bcRevision, doc);
- if (isCommitted(commitValue)) {
- // obviously don't do anything with merged (committed) branch commits
- continue;
- }
- removeUnmergedBCRevision(bcRevision, doc, updateOp);
+
+ olderUnmergedBranchCommits.forEach(bcRevision -> removeUnmergedBCRevision(bcRevision, doc, updateOp));
+ deletedUnmergedBCSet.addAll(olderUnmergedBranchCommits);
+
+ if (log.isDebugEnabled()) {
+ log.debug("Collected {} unmerged branch commits for document {}", olderUnmergedBranchCommits.size(), doc.getId());
}
+
// now for any of the handled system properties (the normal properties would
// already be cleaned up by cleanupDeletedProperties), the resulting
- // subdocument could in theory become empty after removing all unmerged branch
+ // sub document could in theory become empty after removing all unmerged branch
// commit revisions is done later.
// so we need to go through all of them and check if we'd have removed
// the entirety - and then, instead of individually remove revisions, just
// delete the entire property.
if (updateOp.hasChanges()) {
- for (Entry<String, Integer> e : getSystemRemoveMapEntryCounts(updateOp)
- .entrySet()) {
- final String prop = e.getKey();
- final Object d = doc.data.get(prop);
- if (!(d instanceof Map)) {
- // unexpected and would likely indicate a bug, hence log.error
- log.error(
- "collectUnmergedBranchCommitDocument: property without subdocument as expected. id={}, prop={}",
- doc.getId(), prop);
- continue;
- }
- @SuppressWarnings("rawtypes")
- final Map m = (Map) d;
- if (m.size() != e.getValue()) {
- // then we're not removing all revisions - so cannot cleanup
- continue;
- }
- // then we're removing all revisions - so replace those REMOVE_MAP_ENTRY
- // with one whole remove(prop)
- final Iterator<Entry<Key, Operation>> it = updateOp.getChanges().entrySet()
- .iterator();
- while (it.hasNext()) {
- if (it.next().getKey().getName().equals(prop)) {
- it.remove();
- }
- }
- updateOp.remove(prop);
- }
+ final int deletedSystemPropsCount = getSystemRemoveMapEntryCounts(updateOp)
+ .entrySet().stream()
+ .filter(e -> filterEmptyProps(doc, e.getKey(), e.getValue()))
+ .mapToInt(e -> {
+ final String prop = e.getKey();
+ updateOp.getChanges().entrySet().removeIf(opEntry -> Objects.equals(prop, opEntry.getKey().getName()));
+ updateOp.remove(prop);
+ return 1;})
+ .sum();
+
+ // update the deleted properties count Map to calculate the total no. of deleted properties
+ deletedPropsCountMap.merge(doc.getId(), deletedSystemPropsCount, Integer::sum);
}
phases.stop(GCPhase.DETAILED_GC_COLLECT_UNMERGED_BC);
}
+ /**
+ * Filter all would be empty system properties (after cleanup operation).
+ * <p>
+ * It verifies this by comparing the size of sub-document with given <code>value</code>
+ *
+ * @param doc {@link NodeDocument} on whose properties needs to be checked
+ * @param prop Name of sub-document which needs to checked whether it would be empty after cleanup or not
+ * @param value expected no. of entries
+ * @return true if sub-document would eventually be empty or not
+ */
+ private boolean filterEmptyProps(final NodeDocument doc, final String prop, final int value) {
+ final Object d = doc.data.get(prop);
+ if (d instanceof Map) {
+ @SuppressWarnings("rawtypes") final Map m = (Map) d;
+ // then we're not removing all revisions - so cannot clean up
+ return m.size() == value;
+ } else {
+ // unexpected and would likely indicate a bug, hence log.error
+ log.error("collectUnmergedBranchCommitDocument: property without sub-document as expected. " +
+ "id={}, prop={}", doc.getId(), prop);
+ return false;
+ }
+ }
+
/** small helper to count number of REMOVE_MAP_ENTRY per system property */
private Map<String, Integer> getSystemRemoveMapEntryCounts(final UpdateOp updateOp) {
final Map<String, Integer> propMap = new HashMap<>();
- for (Entry<Key, Operation> e : updateOp.getChanges().entrySet()) {
- if (e.getValue().type != Type.REMOVE_MAP_ENTRY) {
- // only count REMOVE_MAP_ENTRY types, skip the rest
- continue;
- }
- final String propName = e.getKey().getName();
- if (!propName.startsWith("_")) {
- // only count system properties, skip the rest
- continue;
- }
- Integer count = propMap.getOrDefault(propName, 0);
- propMap.put(propName, count + 1);
- }
+
+ updateOp.getChanges().entrySet().stream()
+ .filter(e -> e.getValue().type == Type.REMOVE_MAP_ENTRY)
+ .map(e -> e.getKey().getName())
+ .filter(propName -> propName.startsWith("_"))
+ .forEach(propName -> propMap.merge(propName, 1, Integer::sum));
+
return propMap;
}
@@ -1070,10 +1081,9 @@ public class VersionGarbageCollector {
* @param doc the document from which the uncommittedBCRevision
* should be removed
* @param updateOp the resulting operations yet to be applied
- * @param propMap
*/
- private void removeUnmergedBCRevision(Revision unmergedBCRevision,
- NodeDocument doc, UpdateOp updateOp) {
+ private void removeUnmergedBCRevision(final Revision unmergedBCRevision, final NodeDocument doc,
+ final UpdateOp updateOp) {
// caller ensures the provided revision is an unmerged branch commit
NodeDocument.removeBranchCommit(updateOp, unmergedBCRevision);
@@ -1091,7 +1101,7 @@ public class VersionGarbageCollector {
// commit, but that was never merged. when we now remove that, it could be
// that it is then deleted.
- // to know whether or not the node is actually deleted, would potentially
+ // to know whether the node is actually deleted, would potentially
// require several commit value lookups.
// in order to keep the execution time of detailGC in this regard small,
// the code here stops with any further checks and just sets
@@ -1112,8 +1122,7 @@ public class VersionGarbageCollector {
if (doc.getLocalRevisions().containsKey(unmergedBCRevision)) {
NodeDocument.removeRevision(updateOp, unmergedBCRevision);
}
- if (doc.getLocalMap(NodeDocument.COLLISIONS)
- .containsKey(unmergedBCRevision)) {
+ if (doc.getLocalMap(NodeDocument.COLLISIONS).containsKey(unmergedBCRevision)) {
NodeDocument.removeCollision(updateOp, unmergedBCRevision);
}
// phase 4 : go through normal properties
@@ -1132,7 +1141,7 @@ public class VersionGarbageCollector {
}
}
- private void collectOldRevisions(NodeDocument doc, GCPhases phases, UpdateOp updateOp) {
+ private void collectOldRevisions(final NodeDocument doc, final GCPhases phases, final UpdateOp updateOp) {
if (phases.start(GCPhase.DETAILED_GC_COLLECT_OLD_REVS)){
// TODO add old rev collection logic
@@ -1179,18 +1188,25 @@ public class VersionGarbageCollector {
int deletedProps = oldDocs.stream().filter(Objects::nonNull).mapToInt(d -> deletedPropsCountMap.getOrDefault(d.getId(), 0)).sum();
updatedDocs = (int) oldDocs.stream().filter(Objects::nonNull).count();
stats.updatedDetailedGCDocsCount += updatedDocs;
- stats.deletedPropsGCCount += deletedProps;
- log.debug("Updated [{}] documents, deleted [{}] properties", updatedDocs, deletedProps);
- // now reset delete metadata
- updateOpList.clear();
- deletedPropsCountMap.clear();
- garbageDocsCount = 0;
+ stats.deletedPropsCount += deletedProps;
+ stats.deletedUnmergedBCCount += deletedUnmergedBCSet.size();
+
+ if (log.isDebugEnabled()) {
+ log.debug("Updated [{}] documents, deleted [{}] properties, deleted [{}] unmergedBranchCommits",
+ updatedDocs, deletedProps, deletedUnmergedBCSet.size());
+ }
// save stats
detailedGCStats.propertiesDeleted(deletedProps);
+ detailedGCStats.unmergedBranchCommitsDeleted(deletedUnmergedBCSet.size());
detailedGCStats.documentsUpdated(updatedDocs);
- detailedGCStats.documentsSkippedUpdation(oldDocs.size() - updatedDocs);
+ detailedGCStats.documentsUpdateSkipped(oldDocs.size() - updatedDocs);
} finally {
+ // now reset delete metadata
+ updateOpList.clear();
+ deletedPropsCountMap.clear();
+ deletedUnmergedBCSet.clear();
+ garbageDocsCount = 0;
delayOnModifications(timer.stop().elapsed(MILLISECONDS), cancel);
}
}
diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BranchCommitGCTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BranchCommitGCTest.java
index 79d7f9bbae..7597b71d32 100644
--- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BranchCommitGCTest.java
+++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BranchCommitGCTest.java
@@ -16,33 +16,37 @@
*/
package org.apache.jackrabbit.oak.plugins.document;
-import static java.util.concurrent.TimeUnit.HOURS;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.function.Consumer;
-
import org.apache.jackrabbit.oak.plugins.document.DocumentMK.Builder;
import org.apache.jackrabbit.oak.plugins.document.util.Utils;
import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
-import org.apache.jackrabbit.oak.spi.state.NodeState;
-import org.apache.jackrabbit.oak.spi.state.NodeStore;
import org.apache.jackrabbit.oak.stats.Clock;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+import static java.util.concurrent.TimeUnit.HOURS;
+import static org.apache.commons.lang3.reflect.FieldUtils.writeField;
+import static org.apache.jackrabbit.oak.plugins.document.DetailGCHelper.assertBranchRevisionRemovedFromAllDocuments;
+import static org.apache.jackrabbit.oak.plugins.document.DetailGCHelper.build;
+import static org.apache.jackrabbit.oak.plugins.document.DetailGCHelper.mergedBranchCommit;
+import static org.apache.jackrabbit.oak.plugins.document.DetailGCHelper.unmergedBranchCommit;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
public class BranchCommitGCTest {
@Rule
@@ -72,28 +76,6 @@ public class BranchCommitGCTest {
Revision.resetClockToDefault();
}
- private void assertNoEmptyProperties() {
- for (NodeDocument nd : Utils.getAllDocuments(store.getDocumentStore())) {
- for (Entry<String, Object> e : nd.data.entrySet()) {
- Object v = e.getValue();
- if (v instanceof Map) {
- @SuppressWarnings("rawtypes")
- Map m = (Map) v;
- if (m.isEmpty()
- && (e.getKey().equals("_commitRoot")
- || e.getKey().equals("_collisions"))
- && nd.getId().equals("0:/")) {
- // skip : root document apparently has an empty _commitRoot:{}
- continue;
- }
- assertFalse("document has empty property : id=" + nd.getId()
- + ", property=" + e.getKey() + ", document=" + nd.asString(),
- m.isEmpty());
- }
- }
- }
- }
-
@Test
public void unmergedAddChildren() throws Exception {
RevisionVector br = unmergedBranchCommit(b -> {
@@ -102,10 +84,8 @@ public class BranchCommitGCTest {
});
assertExists("1:/a");
assertExists("1:/b");
- assertFalse(
- store.getDocumentStore().find(Collection.NODES, "1:/a").wasDeletedOnce());
- assertFalse(
- store.getDocumentStore().find(Collection.NODES, "1:/b").wasDeletedOnce());
+ assertFalse(store.getDocumentStore().find(Collection.NODES, "1:/a").wasDeletedOnce());
+ assertFalse(store.getDocumentStore().find(Collection.NODES, "1:/b").wasDeletedOnce());
store.runBackgroundOperations();
@@ -115,20 +95,20 @@ public class BranchCommitGCTest {
VersionGarbageCollector.VersionGCStats stats = gc.gc(1, HOURS);
assertEquals(3, stats.updatedDetailedGCDocsCount);
+ assertEquals(1, stats.deletedUnmergedBCCount);
- assertTrue(
- store.getDocumentStore().find(Collection.NODES, "1:/a").wasDeletedOnce());
- assertTrue(
- store.getDocumentStore().find(Collection.NODES, "1:/b").wasDeletedOnce());
+ assertTrue(store.getDocumentStore().find(Collection.NODES, "1:/a").wasDeletedOnce());
+ assertTrue(store.getDocumentStore().find(Collection.NODES, "1:/b").wasDeletedOnce());
// now do another gc to get those documents actually deleted
clock.waitUntil(clock.getTime() + HOURS.toMillis(2));
stats = gc.gc(1, HOURS);
assertEquals(0, stats.updatedDetailedGCDocsCount);
assertEquals(2, stats.deletedDocGCCount);
+ assertEquals(0, stats.deletedUnmergedBCCount);
assertNotExists("1:/a");
assertNotExists("1:/b");
- assertBranchRevisionRemovedFromAllDocuments(br);
+ assertBranchRevisionRemovedFromAllDocuments(store, br);
}
@Test
@@ -139,10 +119,8 @@ public class BranchCommitGCTest {
});
assertExists("1:/a");
assertExists("1:/b");
- assertFalse(
- store.getDocumentStore().find(Collection.NODES, "1:/a").wasDeletedOnce());
- assertFalse(
- store.getDocumentStore().find(Collection.NODES, "1:/b").wasDeletedOnce());
+ assertFalse(store.getDocumentStore().find(Collection.NODES, "1:/a").wasDeletedOnce());
+ assertFalse(store.getDocumentStore().find(Collection.NODES, "1:/b").wasDeletedOnce());
store.runBackgroundOperations();
@@ -165,11 +143,10 @@ public class BranchCommitGCTest {
// clean everything older than one hour
VersionGarbageCollector.VersionGCStats stats = gc.gc(1, HOURS);
- assertTrue(
- "stats.updatedDetailedGCDocsCount expected 1 or less, was: "
- + stats.updatedDetailedGCDocsCount,
+ assertTrue("stats.updatedDetailedGCDocsCount expected 1 or less, was: " + stats.updatedDetailedGCDocsCount,
stats.updatedDetailedGCDocsCount <= 1);
assertEquals(2, stats.deletedDocGCCount);
+ assertEquals(1, stats.deletedUnmergedBCCount);
assertNotExists("1:/a");
assertNotExists("1:/b");
@@ -178,7 +155,50 @@ public class BranchCommitGCTest {
stats = gc.gc(1, HOURS);
assertEquals(0, stats.updatedDetailedGCDocsCount);
assertEquals(0, stats.deletedDocGCCount);
- assertBranchRevisionRemovedFromAllDocuments(br);
+ assertEquals(0, stats.deletedUnmergedBCCount);
+ assertBranchRevisionRemovedFromAllDocuments(store, br);
+ }
+
+ @Test
+ public void testDeletedPropsAndUnmergedBC() throws Exception {
+ // create a node with property.
+ NodeBuilder nb = store.getRoot().builder();
+ nb.child("bar").setProperty("prop", "value");
+ nb.child("bar").setProperty("x", "y");
+ store.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ store.runBackgroundOperations();
+
+ // remove the property
+ nb = store.getRoot().builder();
+ nb.child("bar").removeProperty("prop");
+ store.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ store.runBackgroundOperations();
+
+ // create branch commits
+ mergedBranchCommit(b -> b.child("foo").setProperty("p", "prop"));
+ RevisionVector br1 = unmergedBranchCommit(b -> b.child("foo").setProperty("a", "b"));
+ RevisionVector br2 = unmergedBranchCommit(b -> b.child("foo").setProperty("a", "c"));
+ RevisionVector br3 = unmergedBranchCommit(b -> b.child("foo").setProperty("a", "d"));
+ RevisionVector br4 = unmergedBranchCommit(b -> b.child("bar").setProperty("x", "z"));
+ mergedBranchCommit(b -> b.child("foo").removeProperty("p"));
+ store.runBackgroundOperations();
+
+ // enable the detailed gc flag
+ writeField(gc, "detailedGCEnabled", true, true);
+
+ // wait two hours
+ clock.waitUntil(clock.getTime() + HOURS.toMillis(2));
+ // clean everything older than one hour
+ VersionGarbageCollector.VersionGCStats stats = gc.gc(1, HOURS);
+
+ assertEquals(3, stats.updatedDetailedGCDocsCount);
+ // deleted properties are : 1:/foo -> prop, a, _collisions & p && 1:/bar -> _bc
+ assertEquals(5, stats.deletedPropsCount);
+ assertEquals(4, stats.deletedUnmergedBCCount);
+ assertBranchRevisionRemovedFromAllDocuments(store, br1);
+ assertBranchRevisionRemovedFromAllDocuments(store, br2);
+ assertBranchRevisionRemovedFromAllDocuments(store, br3);
+ assertBranchRevisionRemovedFromAllDocuments(store, br4);
}
@Test
@@ -193,27 +213,24 @@ public class BranchCommitGCTest {
});
assertExists("1:/a");
assertExists("1:/b");
- assertFalse(
- store.getDocumentStore().find(Collection.NODES, "1:/a").wasDeletedOnce());
- assertFalse(
- store.getDocumentStore().find(Collection.NODES, "1:/b").wasDeletedOnce());
+ assertFalse(store.getDocumentStore().find(Collection.NODES, "1:/a").wasDeletedOnce());
+ assertFalse(store.getDocumentStore().find(Collection.NODES, "1:/b").wasDeletedOnce());
store.runBackgroundOperations();
- Long originalModified = store.getDocumentStore().find(Collection.NODES, "1:/a")
- .getModified();
+ Long originalModified = store.getDocumentStore().find(Collection.NODES, "1:/a").getModified();
// wait two hours
clock.waitUntil(clock.getTime() + HOURS.toMillis(2));
// clean everything older than one hour
VersionGarbageCollector.VersionGCStats stats = gc.gc(1, HOURS);
- Long laterModified = store.getDocumentStore().find(Collection.NODES, "1:/a")
- .getModified();
+ Long laterModified = store.getDocumentStore().find(Collection.NODES, "1:/a").getModified();
assertNotEquals(originalModified, laterModified);
assertEquals(3, stats.updatedDetailedGCDocsCount);
assertEquals(0, stats.deletedDocGCCount);
+ assertEquals(2, stats.deletedUnmergedBCCount);
assertExists("1:/a");
assertExists("1:/b");
@@ -223,8 +240,9 @@ public class BranchCommitGCTest {
stats = gc.gc(1, HOURS);
assertEquals(0, stats.updatedDetailedGCDocsCount);
assertEquals(2, stats.deletedDocGCCount);
- assertBranchRevisionRemovedFromAllDocuments(br1);
- assertBranchRevisionRemovedFromAllDocuments(br2);
+ assertEquals(0, stats.deletedUnmergedBCCount);
+ assertBranchRevisionRemovedFromAllDocuments(store, br1);
+ assertBranchRevisionRemovedFromAllDocuments(store, br2);
}
@Test
@@ -239,10 +257,8 @@ public class BranchCommitGCTest {
});
assertExists("1:/a");
assertExists("1:/b");
- assertFalse(
- store.getDocumentStore().find(Collection.NODES, "1:/a").wasDeletedOnce());
- assertFalse(
- store.getDocumentStore().find(Collection.NODES, "1:/b").wasDeletedOnce());
+ assertFalse(store.getDocumentStore().find(Collection.NODES, "1:/a").wasDeletedOnce());
+ assertFalse(store.getDocumentStore().find(Collection.NODES, "1:/b").wasDeletedOnce());
store.runBackgroundOperations();
@@ -255,12 +271,13 @@ public class BranchCommitGCTest {
// wait two hours
clock.waitUntil(clock.getTime() + HOURS.toMillis(2));
- // clean everything older than one hours
+ // clean everything older than one hour
VersionGarbageCollector.VersionGCStats stats = gc.gc(1, HOURS);
assertTrue("should have been 2 or more, was: " + stats.updatedDetailedGCDocsCount,
stats.updatedDetailedGCDocsCount >= 2);
assertEquals(0, stats.deletedDocGCCount);
+ assertEquals(2, stats.deletedUnmergedBCCount);
assertExists("1:/a");
assertExists("1:/b");
@@ -270,8 +287,9 @@ public class BranchCommitGCTest {
stats = gc.gc(1, HOURS);
assertEquals(0, stats.updatedDetailedGCDocsCount);
assertEquals(0, stats.deletedDocGCCount);
- assertBranchRevisionRemovedFromAllDocuments(br1);
- assertBranchRevisionRemovedFromAllDocuments(br2);
+ assertEquals(0, stats.deletedUnmergedBCCount);
+ assertBranchRevisionRemovedFromAllDocuments(store, br1);
+ assertBranchRevisionRemovedFromAllDocuments(store, br2);
}
@Test
@@ -286,10 +304,8 @@ public class BranchCommitGCTest {
});
assertExists("1:/a");
assertExists("1:/b");
- assertFalse(
- store.getDocumentStore().find(Collection.NODES, "1:/a").wasDeletedOnce());
- assertFalse(
- store.getDocumentStore().find(Collection.NODES, "1:/b").wasDeletedOnce());
+ assertFalse(store.getDocumentStore().find(Collection.NODES, "1:/a").wasDeletedOnce());
+ assertFalse(store.getDocumentStore().find(Collection.NODES, "1:/b").wasDeletedOnce());
store.runBackgroundOperations();
@@ -318,6 +334,7 @@ public class BranchCommitGCTest {
assertEquals(3, stats.updatedDetailedGCDocsCount);
assertEquals(0, stats.deletedDocGCCount);
+ assertEquals(4, stats.deletedUnmergedBCCount);
assertExists("1:/a");
assertExists("1:/b");
@@ -327,20 +344,11 @@ public class BranchCommitGCTest {
stats = gc.gc(1, HOURS);
assertEquals(0, stats.updatedDetailedGCDocsCount);
assertEquals(0, stats.deletedDocGCCount);
- assertBranchRevisionRemovedFromAllDocuments(br1);
- assertBranchRevisionRemovedFromAllDocuments(br2);
- assertBranchRevisionRemovedFromAllDocuments(br3);
- assertBranchRevisionRemovedFromAllDocuments(br4);
- }
-
- private void assertNotExists(String id) {
- NodeDocument doc = store.getDocumentStore().find(Collection.NODES, id);
- assertNull("doc exists but was expected not to : id=" + id, doc);
- }
-
- private void assertExists(String id) {
- NodeDocument doc = store.getDocumentStore().find(Collection.NODES, id);
- assertNotNull("doc does not exist : id=" + id, doc);
+ assertEquals(0, stats.deletedUnmergedBCCount);
+ assertBranchRevisionRemovedFromAllDocuments(store, br1);
+ assertBranchRevisionRemovedFromAllDocuments(store, br2);
+ assertBranchRevisionRemovedFromAllDocuments(store, br3);
+ assertBranchRevisionRemovedFromAllDocuments(store, br4);
}
@Test
@@ -351,18 +359,19 @@ public class BranchCommitGCTest {
});
RevisionVector br = unmergedBranchCommit(b -> {
b.child("test").remove();
- b.getChildNode("foo").child("childfoo");
+ b.getChildNode("foo").child("childFoo");
});
store.runBackgroundOperations();
// wait two hours
clock.waitUntil(clock.getTime() + HOURS.toMillis(2));
- // clean everything older than one hours
+ // clean everything older than one hour
VersionGarbageCollector.VersionGCStats stats = gc.gc(1, HOURS);
// first gc round will only mark document for deleting by second round
assertEquals(0, stats.deletedDocGCCount);
assertEquals(4, stats.updatedDetailedGCDocsCount);
+ assertEquals(1, stats.deletedUnmergedBCCount);
// wait two hours
clock.waitUntil(clock.getTime() + HOURS.toMillis(2));
@@ -370,25 +379,8 @@ public class BranchCommitGCTest {
stats = gc.gc(1, HOURS);
assertEquals(0, stats.updatedDetailedGCDocsCount);
assertEquals(1, stats.deletedDocGCCount);
- assertBranchRevisionRemovedFromAllDocuments(br);
- }
-
- private void assertBranchRevisionRemovedFromAllDocuments(RevisionVector br) {
- assertTrue(br.isBranch());
- Revision br1 = br.getRevision(1);
- Revision r1 = br1.asTrunkRevision();
- for (NodeDocument nd : Utils.getAllDocuments(store.getDocumentStore())) {
- if (nd.getId().equals("0:/")) {
- NodeDocument target = new NodeDocument(store.getDocumentStore());
- nd.deepCopy(target);
- }
- System.out.println("nd=" + nd.asString());
- if (nd.asString().contains(r1.toString())) {
- System.out.println("break");
- }
- assertFalse("document not fully cleaned up: " + nd.asString(),
- nd.asString().contains(r1.toString()));
- }
+ assertEquals(0, stats.deletedUnmergedBCCount);
+ assertBranchRevisionRemovedFromAllDocuments(store, br);
}
@Test
@@ -406,27 +398,30 @@ public class BranchCommitGCTest {
// wait two hours
clock.waitUntil(clock.getTime() + HOURS.toMillis(2));
- // clean everything older than one hours
+ // clean everything older than one hour
stats = gc.gc(1, HOURS);
assertEquals(2, stats.updatedDetailedGCDocsCount);
- assertBranchRevisionRemovedFromAllDocuments(br);
+ assertEquals(0, stats.deletedPropsCount);
+ assertEquals(1, stats.deletedUnmergedBCCount);
+ assertBranchRevisionRemovedFromAllDocuments(store, br);
}
@Test
public void unmergedAddProperty() throws Exception {
mergedBranchCommit(b -> b.child("foo"));
- RevisionVector br = unmergedBranchCommit(
- b -> b.child("foo").setProperty("a", "b"));
+ RevisionVector br = unmergedBranchCommit(b -> b.child("foo").setProperty("a", "b"));
store.runBackgroundOperations();
// wait two hours
clock.waitUntil(clock.getTime() + HOURS.toMillis(2));
- // clean everything older than one hours
+ // clean everything older than one hour
stats = gc.gc(1, HOURS);
assertEquals(2, stats.updatedDetailedGCDocsCount);
- assertBranchRevisionRemovedFromAllDocuments(br);
+ assertEquals(1, stats.deletedPropsCount);
+ assertEquals(1, stats.deletedUnmergedBCCount);
+ assertBranchRevisionRemovedFromAllDocuments(store, br);
}
@Test
@@ -439,6 +434,7 @@ public class BranchCommitGCTest {
store.runBackgroundOperations();
stats = gc.gc(1, HOURS);
assertEquals(0, stats.updatedDetailedGCDocsCount);
+ assertEquals(0, stats.deletedUnmergedBCCount);
final List<RevisionVector> brs = new LinkedList<>();
for (int j = 0; j < 10; j++) {
@@ -451,10 +447,11 @@ public class BranchCommitGCTest {
// wait two hours
clock.waitUntil(clock.getTime() + HOURS.toMillis(2));
- // clean everything older than one hours
+ // clean everything older than one hour
stats = gc.gc(1, HOURS);
assertEquals(2, stats.updatedDetailedGCDocsCount);
+ assertEquals(10, stats.deletedUnmergedBCCount);
doc = store.getDocumentStore().find(Collection.NODES, "1:/foo");
Long finalModified = doc.getModified();
@@ -462,7 +459,7 @@ public class BranchCommitGCTest {
assertEquals(originalModified, finalModified);
for (RevisionVector br : brs) {
- assertBranchRevisionRemovedFromAllDocuments(br);
+ assertBranchRevisionRemovedFromAllDocuments(store, br);
}
}
@@ -476,6 +473,7 @@ public class BranchCommitGCTest {
store.runBackgroundOperations();
stats = gc.gc(1, HOURS);
assertEquals(0, stats.updatedDetailedGCDocsCount);
+ assertEquals(0, stats.deletedUnmergedBCCount);
for (int i = 0; i < 50; i++) {
mergedBranchCommit(b -> b.child("foo").remove());
@@ -489,12 +487,13 @@ public class BranchCommitGCTest {
// wait two hours
clock.waitUntil(clock.getTime() + HOURS.toMillis(2));
- // clean everything older than one hours
+ // clean everything older than one hour
stats = gc.gc(1, HOURS);
assertEquals(2, stats.updatedDetailedGCDocsCount);
+ assertEquals(10, stats.deletedUnmergedBCCount);
for (RevisionVector br : brs) {
- assertBranchRevisionRemovedFromAllDocuments(br);
+ assertBranchRevisionRemovedFromAllDocuments(store, br);
}
}
@@ -507,6 +506,8 @@ public class BranchCommitGCTest {
store.runBackgroundOperations();
stats = gc.gc(1, HOURS);
assertEquals(0, stats.updatedDetailedGCDocsCount);
+ assertEquals(0, stats.deletedUnmergedBCCount);
+ assertEquals(0, stats.deletedPropsCount);
RevisionVector br = unmergedBranchCommit(b -> {
b.setProperty("rootProp", "v");
@@ -514,20 +515,26 @@ public class BranchCommitGCTest {
});
store.runBackgroundOperations();
DocumentNodeStore store2 = newStore(2);
- mergedBranchCommit(store2, b -> b.child("foo").removeProperty("a"));
+ DetailGCHelper.mergedBranchCommit(store2, b -> b.child("foo").removeProperty("a"));
store2.runBackgroundOperations();
store2.dispose();
store.runBackgroundOperations();
// wait two hours
clock.waitUntil(clock.getTime() + HOURS.toMillis(2));
- // clean everything older than one hours
+ // clean everything older than one hour
stats = gc.gc(1, HOURS);
assertEquals(2, stats.updatedDetailedGCDocsCount);
- assertBranchRevisionRemovedFromAllDocuments(br);
+ assertEquals(1, stats.deletedUnmergedBCCount);
+ // deleted properties are 0:/ -> rootProp, _collisions & 1:/foo -> a
+ assertEquals(3, stats.deletedPropsCount);
+ assertBranchRevisionRemovedFromAllDocuments(store, br);
}
+
+ // helper methods
+
private DocumentNodeStore newStore(int clusterId) {
Builder builder = builderProvider.newBuilder().clock(clock)
.setLeaseCheckMode(LeaseCheckMode.DISABLED).setDetailedGCEnabled(true)
@@ -535,50 +542,46 @@ public class BranchCommitGCTest {
if (clusterId > 0) {
builder.setClusterId(clusterId);
}
- DocumentNodeStore store2 = builder.getNodeStore();
- return store2;
+ return builder.getNodeStore();
}
- private RevisionVector mergedBranchCommit(Consumer<NodeBuilder> buildFunction)
- throws Exception {
+ private RevisionVector mergedBranchCommit(Consumer<NodeBuilder> buildFunction) throws Exception {
return build(store, true, true, buildFunction);
}
- private RevisionVector mergedBranchCommit(NodeStore store,
- Consumer<NodeBuilder> buildFunction) throws Exception {
- return build(store, true, true, buildFunction);
- }
-
- private RevisionVector unmergedBranchCommit(Consumer<NodeBuilder> buildFunction)
- throws Exception {
+ private RevisionVector unmergedBranchCommit(Consumer<NodeBuilder> buildFunction) throws Exception {
RevisionVector result = build(store, true, false, buildFunction);
assertTrue(result.isBranch());
return result;
}
- private RevisionVector build(NodeStore store, boolean persistToBranch, boolean merge,
- Consumer<NodeBuilder> buildFunction) throws Exception {
- if (!persistToBranch && !merge) {
- throw new IllegalArgumentException("must either persistToBranch or merge");
- }
- NodeBuilder b = store.getRoot().builder();
- buildFunction.accept(b);
- RevisionVector result = null;
- if (persistToBranch) {
- DocumentRootBuilder drb = (DocumentRootBuilder) b;
- drb.persist();
- DocumentNodeState ns = (DocumentNodeState) drb.getNodeState();
- result = ns.getLastRevision();
- }
- if (merge) {
- DocumentNodeState dns = (DocumentNodeState) merge(b);
- result = dns.getLastRevision();
- }
- return result;
+ private void assertNotExists(String id) {
+ NodeDocument doc = store.getDocumentStore().find(Collection.NODES, id);
+ assertNull("doc exists but was expected not to : id=" + id, doc);
+ }
+
+ private void assertExists(String id) {
+ NodeDocument doc = store.getDocumentStore().find(Collection.NODES, id);
+ assertNotNull("doc does not exist : id=" + id, doc);
}
- private NodeState merge(NodeBuilder builder) throws Exception {
- return store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ private void assertNoEmptyProperties() {
+ for (NodeDocument nd : Utils.getAllDocuments(store.getDocumentStore())) {
+ for (Entry<String, Object> e : nd.data.entrySet()) {
+ Object v = e.getValue();
+ if (v instanceof Map) {
+ @SuppressWarnings("rawtypes")
+ Map m = (Map) v;
+ if (m.isEmpty() && (e.getKey().equals("_commitRoot") || e.getKey().equals("_collisions"))
+ && Objects.equals(nd.getId(), "0:/")) {
+ // skip : root document apparently has an empty _commitRoot:{}
+ continue;
+ }
+ assertFalse("document has empty property : id=" + nd.getId() +
+ ", property=" + e.getKey() + ", document=" + nd.asString(), m.isEmpty());
+ }
+ }
+ }
}
}
diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DetailGCHelper.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DetailGCHelper.java
index d52c5e33ae..61b8fa0ab5 100644
--- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DetailGCHelper.java
+++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DetailGCHelper.java
@@ -18,7 +18,19 @@
*/
package org.apache.jackrabbit.oak.plugins.document;
+import org.apache.jackrabbit.oak.plugins.document.util.Utils;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+
import static org.apache.commons.lang3.reflect.FieldUtils.writeField;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
public class DetailGCHelper {
@@ -33,4 +45,53 @@ public class DetailGCHelper {
writeField(vgc, "detailedGCEnabled", false, true);
}
}
+
+ public static RevisionVector mergedBranchCommit(final NodeStore store, final Consumer<NodeBuilder> buildFunction) throws Exception {
+ return build(store, true, true, buildFunction);
+ }
+
+ public static RevisionVector unmergedBranchCommit(final NodeStore store, final Consumer<NodeBuilder> buildFunction) throws Exception {
+ RevisionVector result = build(store, true, false, buildFunction);
+ assertTrue(result.isBranch());
+ return result;
+ }
+
+ public static void assertBranchRevisionRemovedFromAllDocuments(final DocumentNodeStore store, final RevisionVector br) {
+ assertTrue(br.isBranch());
+ Revision br1 = br.getRevision(1);
+ assert br1 != null;
+ Revision r1 = br1.asTrunkRevision();
+ for (NodeDocument nd : Utils.getAllDocuments(store.getDocumentStore())) {
+ if (Objects.equals(nd.getId(), "0:/")) {
+ NodeDocument target = new NodeDocument(store.getDocumentStore());
+ nd.deepCopy(target);
+ }
+ assertFalse("document not fully cleaned up: " + nd.asString(), nd.asString().contains(r1.toString()));
+ }
+ }
+
+ static RevisionVector build(final NodeStore store, final boolean persistToBranch, final boolean merge,
+ final Consumer<NodeBuilder> buildFunction) throws Exception {
+ if (!persistToBranch && !merge) {
+ throw new IllegalArgumentException("must either persistToBranch or merge");
+ }
+ NodeBuilder b = store.getRoot().builder();
+ buildFunction.accept(b);
+ RevisionVector result = null;
+ if (persistToBranch) {
+ DocumentRootBuilder drb = (DocumentRootBuilder) b;
+ drb.persist();
+ DocumentNodeState ns = (DocumentNodeState) drb.getNodeState();
+ result = ns.getLastRevision();
+ }
+ if (merge) {
+ DocumentNodeState dns = (DocumentNodeState) merge(store, b);
+ result = dns.getLastRevision();
+ }
+ return result;
+ }
+
+ private static NodeState merge(final NodeStore store, final NodeBuilder builder) throws Exception {
+ return store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ }
}
diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DetailedRevisionGCStatsCollectorImplTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DetailedRevisionGCStatsCollectorImplTest.java
index b84084d439..3af5165f5c 100644
--- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DetailedRevisionGCStatsCollectorImplTest.java
+++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DetailedRevisionGCStatsCollectorImplTest.java
@@ -40,6 +40,7 @@ import static org.apache.jackrabbit.oak.plugins.document.DetailedRevisionGCStats
import static org.apache.jackrabbit.oak.plugins.document.DetailedRevisionGCStatsCollectorImpl.COLLECT_UNMERGED_BC_TIMER;
import static org.apache.jackrabbit.oak.plugins.document.DetailedRevisionGCStatsCollectorImpl.COUNTER;
import static org.apache.jackrabbit.oak.plugins.document.DetailedRevisionGCStatsCollectorImpl.DELETED_PROPERTY;
+import static org.apache.jackrabbit.oak.plugins.document.DetailedRevisionGCStatsCollectorImpl.DELETED_UNMERGED_BC;
import static org.apache.jackrabbit.oak.plugins.document.DetailedRevisionGCStatsCollectorImpl.DELETE_DETAILED_GC_DOCS_TIMER;
import static org.apache.jackrabbit.oak.plugins.document.DetailedRevisionGCStatsCollectorImpl.DETAILED_GC;
import static org.apache.jackrabbit.oak.plugins.document.DetailedRevisionGCStatsCollectorImpl.DETAILED_GC_ACTIVE_TIMER;
@@ -78,7 +79,7 @@ public class DetailedRevisionGCStatsCollectorImplTest {
public void getDocumentsSkippedUpdationCount() throws IllegalAccessException {
Meter m = getMeter(SKIPPED_DOC);
long count = m.getCount();
- stats.documentsSkippedUpdation(17);
+ stats.documentsUpdateSkipped(17);
assertEquals(count + 17, m.getCount());
assertEquals(count + 17, ((MeterStats) readField(stats, "skippedDoc", true)).getCount());
}
@@ -92,6 +93,15 @@ public class DetailedRevisionGCStatsCollectorImplTest {
assertEquals(count + 10, ((MeterStats) readField(stats, "deletedProperty", true)).getCount());
}
+ @Test
+ public void getUnmergedBCDeletedCount() throws IllegalAccessException {
+ Meter m = getMeter(DELETED_UNMERGED_BC);
+ long count = m.getCount();
+ stats.unmergedBranchCommitsDeleted(10);
+ assertEquals(count + 10, m.getCount());
+ assertEquals(count + 10, ((MeterStats) readField(stats, "deletedUnmergedBC", true)).getCount());
+ }
+
@Test
public void getDocumentsUpdatedCount() throws IllegalAccessException {
Meter m = getMeter(UPDATED_DOC);
diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorIT.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorIT.java
index 71af29fe06..4c74f86b0e 100644
--- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorIT.java
+++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorIT.java
@@ -50,6 +50,9 @@ import static org.apache.jackrabbit.oak.api.Type.STRING;
import static org.apache.jackrabbit.oak.api.Type.STRINGS;
import static org.apache.jackrabbit.oak.plugins.document.Collection.NODES;
import static org.apache.jackrabbit.oak.plugins.document.Collection.SETTINGS;
+import static org.apache.jackrabbit.oak.plugins.document.DetailGCHelper.assertBranchRevisionRemovedFromAllDocuments;
+import static org.apache.jackrabbit.oak.plugins.document.DetailGCHelper.mergedBranchCommit;
+import static org.apache.jackrabbit.oak.plugins.document.DetailGCHelper.unmergedBranchCommit;
import static org.apache.jackrabbit.oak.plugins.document.NodeDocument.MIN_ID_VALUE;
import static org.apache.jackrabbit.oak.plugins.document.NodeDocument.NUM_REVS_THRESHOLD;
import static org.apache.jackrabbit.oak.plugins.document.NodeDocument.PREV_SPLIT_FACTOR;
@@ -320,7 +323,7 @@ public class VersionGarbageCollectorIT {
// 6. Now run gc after checkpoint and see removed properties gets collected
clock.waitUntil(clock.getTime() + delta*2);
VersionGCStats stats = gc.gc(delta, MILLISECONDS);
- assertEquals(1, stats.deletedPropsGCCount);
+ assertEquals(1, stats.deletedPropsCount);
assertEquals(1, stats.updatedDetailedGCDocsCount);
assertTrue(stats.ignoredGCDueToCheckPoint);
assertFalse(stats.ignoredDetailedGCDueToCheckPoint);
@@ -354,7 +357,7 @@ public class VersionGarbageCollectorIT {
//1. Go past GC age and check no GC done as nothing deleted
clock.waitUntil(getCurrentTimestamp() + maxAge);
VersionGCStats stats = gc.gc(maxAge, HOURS);
- assertEquals(0, stats.deletedPropsGCCount);
+ assertEquals(0, stats.deletedPropsCount);
assertEquals(0, stats.updatedDetailedGCDocsCount);
//Remove property
@@ -368,14 +371,14 @@ public class VersionGarbageCollectorIT {
//Clock cannot move back (it moved forward in #1) so double the maxAge
clock.waitUntil(clock.getTime() + delta);
stats = gc.gc(maxAge*2, HOURS);
- assertEquals(0, stats.deletedPropsGCCount);
+ assertEquals(0, stats.deletedPropsCount);
assertEquals(0, stats.updatedDetailedGCDocsCount);
//3. Check that deleted property does get collected post maxAge
clock.waitUntil(clock.getTime() + HOURS.toMillis(maxAge*2) + delta);
stats = gc.gc(maxAge*2, HOURS);
- assertEquals(1, stats.deletedPropsGCCount);
+ assertEquals(1, stats.deletedPropsCount);
assertEquals(1, stats.updatedDetailedGCDocsCount);
assertEquals(MIN_ID_VALUE, stats.oldestModifiedDocId);
@@ -390,7 +393,7 @@ public class VersionGarbageCollectorIT {
clock.waitUntil(clock.getTime() + HOURS.toMillis(maxAge*2) + delta);
stats = gc.gc(maxAge*2, HOURS);
- assertEquals(0, stats.deletedPropsGCCount);
+ assertEquals(0, stats.deletedPropsCount);
assertEquals(0, stats.updatedDetailedGCDocsCount);
assertEquals(MIN_ID_VALUE, stats.oldestModifiedDocId);
}
@@ -428,7 +431,7 @@ public class VersionGarbageCollectorIT {
clock.waitUntil(clock.getTime() + HOURS.toMillis(maxAge*2) + delta);
VersionGCStats stats = gc.gc(maxAge*2, HOURS);
- assertEquals(50_000, stats.deletedPropsGCCount);
+ assertEquals(50_000, stats.deletedPropsCount);
assertEquals(5_000, stats.updatedDetailedGCDocsCount);
assertEquals(MIN_ID_VALUE, stats.oldestModifiedDocId);
}
@@ -471,7 +474,7 @@ public class VersionGarbageCollectorIT {
clock.waitUntil(clock.getTime() + HOURS.toMillis(maxAge*2) + delta);
VersionGCStats stats = gc.gc(maxAge, HOURS);
- assertEquals(50_000, stats.deletedPropsGCCount);
+ assertEquals(50_000, stats.deletedPropsCount);
assertEquals(5_000, stats.updatedDetailedGCDocsCount);
assertEquals(MIN_ID_VALUE, stats.oldestModifiedDocId);
}
@@ -534,7 +537,7 @@ public class VersionGarbageCollectorIT {
//1. Go past GC age and check no GC done as nothing deleted
clock.waitUntil(getCurrentTimestamp() + maxAge);
VersionGCStats stats = gc.gc(maxAge, HOURS);
- assertEquals(0, stats.deletedPropsGCCount);
+ assertEquals(0, stats.deletedPropsCount);
//Remove property
NodeBuilder b2 = store.getRoot().builder();
@@ -548,7 +551,7 @@ public class VersionGarbageCollectorIT {
clock.waitUntil(clock.getTime() + HOURS.toMillis(maxAge*2) + delta);
stats = gc.gc(maxAge*2, HOURS);
- assertEquals(10, stats.deletedPropsGCCount);
+ assertEquals(10, stats.deletedPropsCount);
assertEquals(10, stats.updatedDetailedGCDocsCount);
assertEquals(MIN_ID_VALUE, stats.oldestModifiedDocId);
@@ -573,7 +576,7 @@ public class VersionGarbageCollectorIT {
// increment the clock again by more than 2 hours + delta
clock.waitUntil(clock.getTime() + HOURS.toMillis(maxAge*2) + delta);
stats = gc.gc(maxAge*2, HOURS);
- assertEquals(10, stats.deletedPropsGCCount);
+ assertEquals(10, stats.deletedPropsCount);
assertEquals(10, stats.updatedDetailedGCDocsCount);
assertEquals(MIN_ID_VALUE, stats.oldestModifiedDocId);
}
@@ -641,7 +644,7 @@ public class VersionGarbageCollectorIT {
// increment the clock again by more than 2 hours + delta
clock.waitUntil(clock.getTime() + HOURS.toMillis(maxAge*2) + delta);
VersionGCStats stats = gc.gc(maxAge*2, HOURS);
- assertEquals(10, stats.deletedPropsGCCount);
+ assertEquals(10, stats.deletedPropsCount);
assertEquals(10, stats.updatedDetailedGCDocsCount);
assertEquals(MIN_ID_VALUE, stats.oldestModifiedDocId);
}
@@ -664,7 +667,7 @@ public class VersionGarbageCollectorIT {
//1. Go past GC age and check no GC done as nothing deleted
clock.waitUntil(getCurrentTimestamp() + maxAge);
VersionGCStats stats = gc.gc(maxAge, HOURS);
- assertEquals(0, stats.deletedPropsGCCount);
+ assertEquals(0, stats.deletedPropsCount);
assertEquals(0, stats.updatedDetailedGCDocsCount);
//Remove property
@@ -680,14 +683,14 @@ public class VersionGarbageCollectorIT {
//Clock cannot move back (it moved forward in #1) so double the maxAge
clock.waitUntil(clock.getTime() + delta);
stats = gc.gc(maxAge*2, HOURS);
- assertEquals(0, stats.deletedPropsGCCount);
+ assertEquals(0, stats.deletedPropsCount);
assertEquals(0, stats.updatedDetailedGCDocsCount);
//3. Check that deleted property does get collected post maxAge
clock.waitUntil(clock.getTime() + HOURS.toMillis(maxAge*2) + delta);
stats = gc.gc(maxAge*2, HOURS);
- assertEquals(10, stats.deletedPropsGCCount);
+ assertEquals(10, stats.deletedPropsCount);
//4. Check that a revived property (deleted and created again) does not get gc
NodeBuilder b4 = store.getRoot().builder();
@@ -698,7 +701,7 @@ public class VersionGarbageCollectorIT {
clock.waitUntil(clock.getTime() + HOURS.toMillis(maxAge*2) + delta);
stats = gc.gc(maxAge*2, HOURS);
- assertEquals(0, stats.deletedPropsGCCount);
+ assertEquals(0, stats.deletedPropsCount);
assertEquals(0, stats.updatedDetailedGCDocsCount);
}
@@ -722,7 +725,7 @@ public class VersionGarbageCollectorIT {
//1. Go past GC age and check no GC done as nothing deleted
clock.waitUntil(getCurrentTimestamp() + maxAge);
VersionGCStats stats = gc.gc(maxAge, HOURS);
- assertEquals(0, stats.deletedPropsGCCount);
+ assertEquals(0, stats.deletedPropsCount);
assertEquals(0, stats.updatedDetailedGCDocsCount);
//Remove property
@@ -738,14 +741,14 @@ public class VersionGarbageCollectorIT {
//Clock cannot move back (it moved forward in #1) so double the maxAge
clock.waitUntil(clock.getTime() + delta);
stats = gc.gc(maxAge*2, HOURS);
- assertEquals(0, stats.deletedPropsGCCount);
+ assertEquals(0, stats.deletedPropsCount);
assertEquals(0, stats.updatedDetailedGCDocsCount);
//3. Check that deleted property does get collected post maxAge
clock.waitUntil(clock.getTime() + HOURS.toMillis(maxAge*2) + delta);
stats = gc.gc(maxAge*2, HOURS);
- assertEquals(10, stats.deletedPropsGCCount);
+ assertEquals(10, stats.deletedPropsCount);
//4. Check that a revived property (deleted and created again) does not get gc
NodeBuilder b4 = store.getRoot().builder();
@@ -756,7 +759,7 @@ public class VersionGarbageCollectorIT {
clock.waitUntil(clock.getTime() + HOURS.toMillis(maxAge*2) + delta);
stats = gc.gc(maxAge*2, HOURS);
- assertEquals(0, stats.deletedPropsGCCount);
+ assertEquals(0, stats.deletedPropsCount);
assertEquals(0, stats.updatedDetailedGCDocsCount);
}
@@ -789,7 +792,7 @@ public class VersionGarbageCollectorIT {
//1. Go past GC age and check no GC done as nothing deleted
clock.waitUntil(getCurrentTimestamp() + maxAge);
VersionGCStats stats = gc.gc(maxAge, HOURS);
- assertEquals(0, stats.deletedPropsGCCount);
+ assertEquals(0, stats.deletedPropsCount);
assertEquals(0, stats.updatedDetailedGCDocsCount);
//Remove property
@@ -805,14 +808,14 @@ public class VersionGarbageCollectorIT {
//Clock cannot move back (it moved forward in #1) so double the maxAge
clock.waitUntil(clock.getTime() + delta);
stats = gc.gc(maxAge*2, HOURS);
- assertEquals(0, stats.deletedPropsGCCount);
+ assertEquals(0, stats.deletedPropsCount);
assertEquals(0, stats.updatedDetailedGCDocsCount);
//3. Check that deleted property does get collected post maxAge
clock.waitUntil(clock.getTime() + HOURS.toMillis(maxAge*2) + delta);
stats = gc.gc(maxAge*2, HOURS);
- assertEquals(10, stats.deletedPropsGCCount);
+ assertEquals(10, stats.deletedPropsCount);
}
@Test
@@ -844,7 +847,7 @@ public class VersionGarbageCollectorIT {
//1. Go past GC age and check no GC done as nothing deleted
clock.waitUntil(getCurrentTimestamp() + maxAge);
VersionGCStats stats = gc.gc(maxAge, HOURS);
- assertEquals(0, stats.deletedPropsGCCount);
+ assertEquals(0, stats.deletedPropsCount);
assertEquals(0, stats.updatedDetailedGCDocsCount);
//Remove property
@@ -860,14 +863,14 @@ public class VersionGarbageCollectorIT {
//Clock cannot move back (it moved forward in #1) so double the maxAge
clock.waitUntil(clock.getTime() + delta);
stats = gc.gc(maxAge*2, HOURS);
- assertEquals(0, stats.deletedPropsGCCount);
+ assertEquals(0, stats.deletedPropsCount);
assertEquals(0, stats.updatedDetailedGCDocsCount);
//3. Check that deleted property does get collected post maxAge
clock.waitUntil(clock.getTime() + HOURS.toMillis(maxAge*2) + delta);
stats = gc.gc(maxAge*2, HOURS);
- assertEquals(10, stats.deletedPropsGCCount);
+ assertEquals(10, stats.deletedPropsCount);
}
@Test
@@ -888,7 +891,7 @@ public class VersionGarbageCollectorIT {
//1. Go past GC age and check no GC done as nothing deleted
clock.waitUntil(getCurrentTimestamp() + maxAge);
VersionGCStats stats = gc.gc(maxAge, HOURS);
- assertEquals(0, stats.deletedPropsGCCount);
+ assertEquals(0, stats.deletedPropsCount);
assertEquals(0, stats.updatedDetailedGCDocsCount);
//Remove property
@@ -903,7 +906,7 @@ public class VersionGarbageCollectorIT {
//Clock cannot move back (it moved forward in #1) so double the maxAge
clock.waitUntil(clock.getTime() + delta);
stats = gc.gc(maxAge*2, HOURS);
- assertEquals(0, stats.deletedPropsGCCount);
+ assertEquals(0, stats.deletedPropsCount);
assertEquals(0, stats.updatedDetailedGCDocsCount);
assertEquals(MIN_ID_VALUE, stats.oldestModifiedDocId); // as GC hadn't run
@@ -932,7 +935,7 @@ public class VersionGarbageCollectorIT {
VersionGarbageCollector gc = new VersionGarbageCollector(store, gcSupport, true);
stats = gc.gc(maxAge*2, HOURS);
assertEquals(0, stats.updatedDetailedGCDocsCount);
- assertEquals(0, stats.deletedPropsGCCount);
+ assertEquals(0, stats.deletedPropsCount);
assertEquals(MIN_ID_VALUE, stats.oldestModifiedDocId);
}
@@ -957,7 +960,7 @@ public class VersionGarbageCollectorIT {
//1. Go past GC age and check no GC done as nothing deleted
clock.waitUntil(getCurrentTimestamp() + maxAge);
VersionGCStats stats = gc.gc(maxAge, HOURS);
- assertEquals(0, stats.deletedPropsGCCount);
+ assertEquals(0, stats.deletedPropsCount);
assertEquals(0, stats.updatedDetailedGCDocsCount);
//Remove property
@@ -1002,11 +1005,93 @@ public class VersionGarbageCollectorIT {
stats = gcRef.get().gc(maxAge*2, HOURS);
assertTrue(stats.canceled);
assertEquals(0, stats.updatedDetailedGCDocsCount);
- assertEquals(0, stats.deletedPropsGCCount);
+ assertEquals(0, stats.deletedPropsCount);
assertEquals(MIN_ID_VALUE, stats.oldestModifiedDocId);
}
// OAK-10199 END
+
+ // OAK-8646
+ @Test
+ public void testDeletedPropsAndUnmergedBCWithoutCollision() throws Exception {
+ // create a node with property.
+ NodeBuilder nb = store.getRoot().builder();
+ nb.child("bar").setProperty("prop", "value");
+ nb.child("bar").setProperty("x", "y");
+ store.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ store.runBackgroundOperations();
+
+ // remove the property
+ nb = store.getRoot().builder();
+ nb.child("bar").removeProperty("prop");
+ store.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ store.runBackgroundOperations();
+
+ // create branch commits
+ mergedBranchCommit(store, b -> b.child("foo").setProperty("p", "prop"));
+ RevisionVector br1 = unmergedBranchCommit(store, b -> b.child("foo").setProperty("a", "b"));
+ RevisionVector br4 = unmergedBranchCommit(store, b -> b.child("bar").setProperty("x", "z"));
+ mergedBranchCommit(store, b -> b.child("foo").removeProperty("p"));
+ store.runBackgroundOperations();
+
+ // enable the detailed gc flag
+ writeField(gc, "detailedGCEnabled", true, true);
+
+ // wait two hours
+ clock.waitUntil(clock.getTime() + HOURS.toMillis(2));
+ // clean everything older than one hour
+ VersionGCStats stats = gc.gc(1, HOURS);
+
+ assertEquals(3, stats.updatedDetailedGCDocsCount);
+ // deleted properties are : 1:/foo -> prop, a & p && 1:/bar -> _bc
+ assertEquals(4, stats.deletedPropsCount);
+ assertEquals(2, stats.deletedUnmergedBCCount);
+ assertBranchRevisionRemovedFromAllDocuments(store, br1);
+ assertBranchRevisionRemovedFromAllDocuments(store, br4);
+ }
+
+ @Test
+ public void testDeletedPropsAndUnmergedBCWithCollision() throws Exception {
+ // create a node with property.
+ NodeBuilder nb = store.getRoot().builder();
+ nb.child("bar").setProperty("prop", "value");
+ nb.child("bar").setProperty("x", "y");
+ store.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ store.runBackgroundOperations();
+
+ // remove the property
+ nb = store.getRoot().builder();
+ nb.child("bar").removeProperty("prop");
+ store.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ store.runBackgroundOperations();
+
+ // create branch commits
+ mergedBranchCommit(store, b -> b.child("foo").setProperty("p", "prop"));
+ RevisionVector br1 = unmergedBranchCommit(store, b -> b.child("foo").setProperty("a", "b"));
+ RevisionVector br2 = unmergedBranchCommit(store, b -> b.child("foo").setProperty("a", "c"));
+ RevisionVector br3 = unmergedBranchCommit(store, b -> b.child("foo").setProperty("a", "d"));
+ RevisionVector br4 = unmergedBranchCommit(store, b -> b.child("bar").setProperty("x", "z"));
+ mergedBranchCommit(store, b -> b.child("foo").removeProperty("p"));
+ store.runBackgroundOperations();
+
+ // enable the detailed gc flag
+ writeField(gc, "detailedGCEnabled", true, true);
+
+ // wait two hours
+ clock.waitUntil(clock.getTime() + HOURS.toMillis(2));
+ // clean everything older than one hour
+ VersionGCStats stats = gc.gc(1, HOURS);
+
+ assertEquals(3, stats.updatedDetailedGCDocsCount);
+ // deleted properties are : 1:/foo -> prop, a, _collisions & p && 1:/bar -> _bc
+ assertEquals(5, stats.deletedPropsCount);
+ assertEquals(4, stats.deletedUnmergedBCCount);
+ assertBranchRevisionRemovedFromAllDocuments(store, br1);
+ assertBranchRevisionRemovedFromAllDocuments(store, br2);
+ assertBranchRevisionRemovedFromAllDocuments(store, br3);
+ assertBranchRevisionRemovedFromAllDocuments(store, br4);
+ }
+ // OAK-8646 END
private void gcSplitDocsInternal(String subNodeName) throws Exception {
long maxAge = 1; //hrs