This is an automated email from the ASF dual-hosted git repository.
stefanegli pushed a commit to branch DetailedGC/OAK-10199
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
The following commit(s) were added to refs/heads/DetailedGC/OAK-10199 by this
push:
new cc9d565c2e OAK-10570 : add detailedGC capability to oak-run revisions
collect/re… (#1238)
cc9d565c2e is described below
commit cc9d565c2e0d58227d4b695787193b08bfec3931
Author: stefan-egli <[email protected]>
AuthorDate: Thu Nov 30 17:07:14 2023 +0100
OAK-10570 : add detailedGC capability to oak-run revisions collect/re…
(#1238)
* OAK-10570 : add detailedGC capability to oak-run revisions
collect/reset/info
* OAK-10570 : fix sonar warning
* OAK-10570 : fix sonar warning about low coverage
* OAK-10570 : fix test regression
* OAK-10570 : fix sonar warning about low coverage - part 2
---
.../jackrabbit/oak/run/RevisionsCommand.java | 57 +++++++++++++++++-----
.../jackrabbit/oak/run/RevisionsCommandTest.java | 35 ++++++++++++-
.../plugins/document/VersionGCRecommendations.java | 20 ++++++--
.../plugins/document/VersionGarbageCollector.java | 19 ++++++--
.../document/VersionGarbageCollectorIT.java | 2 +-
.../document/VersionGarbageCollectorLogTest.java | 22 +++++++++
6 files changed, 133 insertions(+), 22 deletions(-)
diff --git
a/oak-run/src/main/java/org/apache/jackrabbit/oak/run/RevisionsCommand.java
b/oak-run/src/main/java/org/apache/jackrabbit/oak/run/RevisionsCommand.java
index b995910c57..c4822da0e8 100644
--- a/oak-run/src/main/java/org/apache/jackrabbit/oak/run/RevisionsCommand.java
+++ b/oak-run/src/main/java/org/apache/jackrabbit/oak/run/RevisionsCommand.java
@@ -100,6 +100,8 @@ public class RevisionsCommand implements Command {
final OptionSpec<Long> olderThan;
final OptionSpec<Double> delay;
final OptionSpec<?> continuous;
+ final OptionSpec<?> detailedGC;
+ final OptionSpec<?> detailedGCOnly;
final OptionSpec<?> verbose;
RevisionsOptions(String usage) {
@@ -119,6 +121,10 @@ public class RevisionsCommand implements Command {
.ofType(Long.class).defaultsTo(-1L);
continuous = parser
.accepts("continuous", "run continuously (collect only)");
+ detailedGC = parser
+ .accepts("detailedGC", "enable detailedGC (collect only)");
+ detailedGCOnly = parser
+ .accepts("detailedGCOnly", "apply only to detailedGC
(reset only)");
verbose = parser
.accepts("verbose", "print INFO messages to the console");
}
@@ -160,6 +166,14 @@ public class RevisionsCommand implements Command {
return options.has(continuous);
}
+ boolean isDetailedGCEnabled() {
+ return options.has(detailedGC);
+ }
+
+ boolean isResetDetailedGCOnly() {
+ return options.has(detailedGCOnly);
+ }
+
boolean isVerbose() {
return options.has(verbose);
}
@@ -220,6 +234,9 @@ public class RevisionsCommand implements Command {
version);
System.exit(1);
}
+ if (options.isDetailedGCEnabled()) {
+ builder.setDetailedGCEnabled(true);
+ }
// set it read-only before the DocumentNodeStore is created
// this prevents the DocumentNodeStore from writing a new
// clusterId to the clusterNodes and nodes collections
@@ -245,6 +262,11 @@ public class RevisionsCommand implements Command {
throws IOException {
VersionGarbageCollector gc = bootstrapVGC(options, closer);
System.out.println("retrieving gc info");
+ printInfo(gc, options);
+ }
+
+ private void printInfo(VersionGarbageCollector gc, RevisionsOptions
options)
+ throws IOException {
VersionGCInfo info = gc.getInfo(options.getOlderThan(), SECONDS);
System.out.printf(Locale.US, "%21s %s%n", "Last Successful Run:",
@@ -261,6 +283,8 @@ public class RevisionsCommand implements Command {
fmtTimestamp(info.recommendedCleanupTimestamp));
System.out.printf(Locale.US, "%21s %d%n", "Iterations Estimate:",
info.estimatedIterations);
+ System.out.printf(Locale.US, "%21s %s%n", "Oldest DetailedGC:",
+ fmtTimestamp(info.oldestDetailedGCRevisionEstimate));
}
private void collect(final RevisionsOptions options, Closer closer)
@@ -269,19 +293,20 @@ public class RevisionsCommand implements Command {
ExecutorService executor = Executors.newSingleThreadExecutor();
final Semaphore finished = new Semaphore(0);
try {
+ // collect until shutdown hook is called
+ final AtomicBoolean running = new AtomicBoolean(true);
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override
+ public void run() {
+ System.out.println("Detected QUIT signal.");
+ System.out.println("Stopping Revision GC...");
+ running.set(false);
+ gc.cancel();
+ finished.acquireUninterruptibly();
+ System.out.println("Stopped Revision GC.");
+ }
+ }));
if (options.isContinuous()) {
- // collect until shutdown hook is called
- final AtomicBoolean running = new AtomicBoolean(true);
- Runtime.getRuntime().addShutdownHook(new Thread(new Runnable()
{
- @Override
- public void run() {
- System.out.println("Detected QUIT signal.");
- System.out.println("Stopping Revision GC...");
- running.set(false);
- finished.acquireUninterruptibly();
- System.out.println("Stopped Revision GC.");
- }
- }));
while (running.get()) {
long lastRun = System.currentTimeMillis();
collectOnce(gc, options, executor);
@@ -290,6 +315,8 @@ public class RevisionsCommand implements Command {
} else {
collectOnce(gc, options, executor);
}
+ System.out.println("retrieving gc info");
+ printInfo(gc, options);
} finally {
finished.release();
executor.shutdownNow();
@@ -352,7 +379,11 @@ public class RevisionsCommand implements Command {
throws IOException {
VersionGarbageCollector gc = bootstrapVGC(options, closer);
System.out.println("resetting recommendations and statistics");
- gc.reset();
+ if (options.isResetDetailedGCOnly()) {
+ gc.resetDetailedGC();
+ } else {
+ gc.reset();
+ }
}
private void sweep(RevisionsOptions options, Closer closer)
diff --git
a/oak-run/src/test/java/org/apache/jackrabbit/oak/run/RevisionsCommandTest.java
b/oak-run/src/test/java/org/apache/jackrabbit/oak/run/RevisionsCommandTest.java
index ebd09b7a5e..98dad7d89a 100644
---
a/oak-run/src/test/java/org/apache/jackrabbit/oak/run/RevisionsCommandTest.java
+++
b/oak-run/src/test/java/org/apache/jackrabbit/oak/run/RevisionsCommandTest.java
@@ -34,6 +34,7 @@ import
org.apache.jackrabbit.oak.plugins.document.MongoConnectionFactory;
import org.apache.jackrabbit.oak.plugins.document.MongoUtils;
import org.apache.jackrabbit.oak.plugins.document.Revision;
import org.apache.jackrabbit.oak.plugins.document.UpdateOp;
+import org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector;
import org.apache.jackrabbit.oak.plugins.document.util.MongoConnection;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -97,6 +98,34 @@ public class RevisionsCommandTest {
assertNull(doc);
}
+ @Test
+ public void resetDetailedGC() throws Exception {
+ // need to set detailedGCEnabled to true, so let's bounce the default
one
+ ns.dispose();
+ // and create it fresh with detailedGCEnabled==true
+ ns = createDocumentNodeStore(true);
+ ns.getVersionGarbageCollector().gc(1, TimeUnit.HOURS);
+
+ Document doc = ns.getDocumentStore().find(Collection.SETTINGS,
"versionGC");
+ assertNotNull(doc);
+ assertNotNull(doc.get("detailedGCTimeStamp"));
+ assertNotNull(doc.get("detailedGCId"));
+
+ ns.dispose();
+
+ String output = captureSystemOut(new RevisionsCmd("reset",
"--detailedGCOnly"));
+ assertTrue(output.contains("resetting recommendations and
statistics"));
+
+ MongoConnection c = connectionFactory.getConnection();
+ assertNotNull(c);
+ ns = builderProvider.newBuilder()
+ .setMongoDB(c.getMongoClient(), c.getDBName()).getNodeStore();
+ doc = ns.getDocumentStore().find(Collection.SETTINGS, "versionGC");
+ assertNotNull(doc);
+ assertNull(doc.get("detailedGCTimeStamp"));
+ assertNull(doc.get("detailedGCId"));
+ }
+
@Test
public void collect() throws Exception {
ns.dispose();
@@ -136,10 +165,14 @@ public class RevisionsCommandTest {
}
private DocumentNodeStore createDocumentNodeStore() {
+ return createDocumentNodeStore(false);
+ }
+
+ private DocumentNodeStore createDocumentNodeStore(boolean
detailedGCEnabled) {
MongoConnection c = connectionFactory.getConnection();
assertNotNull(c);
MongoUtils.dropCollections(c.getDatabase());
- return builderProvider.newBuilder()
+ return
builderProvider.newBuilder().setDetailedGCEnabled(detailedGCEnabled)
.setMongoDB(c.getMongoClient(), c.getDBName()).getNodeStore();
}
diff --git
a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGCRecommendations.java
b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGCRecommendations.java
index 6e2b9eaf1d..768786431d 100644
---
a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGCRecommendations.java
+++
b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGCRecommendations.java
@@ -50,6 +50,9 @@ public class VersionGCRecommendations {
private static final Logger log =
LoggerFactory.getLogger(VersionGCRecommendations.class);
+ private static final long IGNORED_GC_WARNING_INTERVAL_MS =
TimeUnit.MINUTES.toMillis(5); // 5min
+ private static long lastIgnoreWarning = 0;
+
private final VersionGCSupport vgc;
private final GCMonitor gcmon;
@@ -66,6 +69,7 @@ public class VersionGCRecommendations {
private final long precisionMs;
final long suggestedIntervalMs;
private final boolean scopeIsComplete;
+ private final boolean detailedGCScopeIsComplete;
private final boolean detailedGCEnabled;
/**
@@ -176,11 +180,11 @@ public class VersionGCRecommendations {
//Check for any registered checkpoint which prevent the GC from running
Revision checkpoint = checkpoints.getOldestRevisionToKeep();
- final GCResult gcResult = getResult(options, checkpoint, scope);
+ final GCResult gcResult = getResult(options, checkpoint, scope, clock);
scope = gcResult.gcScope;
ignoreDueToCheckPoint = gcResult.ignoreGC;
- final GCResult detailGCResult = getResult(options, checkpoint,
scopeDetailedGC);
+ final GCResult detailGCResult = getResult(options, checkpoint,
scopeDetailedGC, clock);
scopeDetailedGC = detailGCResult.gcScope;
ignoreDetailedGCDueToCheckPoint = detailGCResult.ignoreGC;
@@ -198,6 +202,7 @@ public class VersionGCRecommendations {
this.scopeDetailedGC = scopeDetailedGC;
this.detailedGCId = oldestModifiedDocId;
this.scopeIsComplete = scope.toMs >= keep.fromMs;
+ this.detailedGCScopeIsComplete = scopeDetailedGC.toMs >= keep.fromMs;
this.maxCollect = collectLimit;
this.suggestedIntervalMs = suggestedIntervalMs;
this.deleteCandidateCount = deletedOnceCount;
@@ -255,6 +260,7 @@ public class VersionGCRecommendations {
// success, we would not expect to encounter revisions older than
this in the future
setLongSetting(SETTINGS_COLLECTION_DETAILED_GC_TIMESTAMP_PROP,
stats.oldestModifiedDocTimeStamp);
setStringSetting(SETTINGS_COLLECTION_DETAILED_GC_DOCUMENT_ID_PROP,
stats.oldestModifiedDocId);
+ stats.needRepeat |= !detailedGCScopeIsComplete;
}
}
@@ -297,12 +303,18 @@ public class VersionGCRecommendations {
}
@NotNull
- private static GCResult getResult(final VersionGCOptions options, final
Revision checkpoint, TimeInterval gcScope) {
+ private static GCResult getResult(final VersionGCOptions options,
+ final Revision checkpoint, TimeInterval gcScope, Clock clock) {
boolean ignoreGC = false;
if (checkpoint != null &&
gcScope.endsAfter(checkpoint.getTimestamp())) {
TimeInterval minimalScope =
gcScope.startAndDuration(options.precisionMs);
if (minimalScope.endsAfter(checkpoint.getTimestamp())) {
- log.warn("Ignoring GC run because a valid checkpoint [{}]
exists inside minimal scope {}.", checkpoint.toReadableString(), minimalScope);
+ final long now = clock.getTime();
+ if (now - lastIgnoreWarning > IGNORED_GC_WARNING_INTERVAL_MS) {
+ log.warn("Ignoring GC run because a valid checkpoint [{}]
exists inside minimal scope {}.",
+ checkpoint.toReadableString(), minimalScope);
+ lastIgnoreWarning = now;
+ }
ignoreGC = true;
} else {
gcScope = gcScope.notLaterThan(checkpoint.getTimestamp() - 1);
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 cd0de7c88e..6ca5c468c1 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
@@ -242,6 +242,13 @@ public class VersionGarbageCollector {
ds.remove(SETTINGS, SETTINGS_COLLECTION_ID);
}
+ public void resetDetailedGC() {
+ UpdateOp op = new UpdateOp(SETTINGS_COLLECTION_ID, false);
+ op.remove(SETTINGS_COLLECTION_DETAILED_GC_DOCUMENT_ID_PROP);
+ op.remove(SETTINGS_COLLECTION_DETAILED_GC_TIMESTAMP_PROP);
+ ds.findAndUpdate(SETTINGS, op);
+ }
+
public VersionGCInfo getInfo(long maxRevisionAge, TimeUnit unit)
throws IOException {
long maxRevisionAgeInMillis = unit.toMillis(maxRevisionAge);
@@ -255,7 +262,8 @@ public class VersionGarbageCollector {
}
return new VersionGCInfo(rec.lastOldestTimestamp, rec.scope.fromMs,
rec.deleteCandidateCount, rec.maxCollect,
- rec.suggestedIntervalMs, rec.scope.toMs, estimatedIterations);
+ rec.suggestedIntervalMs, rec.scope.toMs, estimatedIterations,
+ rec.scopeDetailedGC.fromMs);
}
public static class VersionGCInfo {
@@ -266,6 +274,7 @@ public class VersionGarbageCollector {
public final long recommendedCleanupInterval;
public final long recommendedCleanupTimestamp;
public final int estimatedIterations;
+ public final long oldestDetailedGCRevisionEstimate;
VersionGCInfo(long lastSuccess,
long oldestRevisionEstimate,
@@ -273,7 +282,8 @@ public class VersionGarbageCollector {
long collectLimit,
long recommendedCleanupInterval,
long recommendedCleanupTimestamp,
- int estimatedIterations) {
+ int estimatedIterations,
+ long oldestDetailedGCRevisionEstimate) {
this.lastSuccess = lastSuccess;
this.oldestRevisionEstimate = oldestRevisionEstimate;
this.revisionsCandidateCount = revisionsCandidateCount;
@@ -281,6 +291,7 @@ public class VersionGarbageCollector {
this.recommendedCleanupInterval = recommendedCleanupInterval;
this.recommendedCleanupTimestamp = recommendedCleanupTimestamp;
this.estimatedIterations = estimatedIterations;
+ this.oldestDetailedGCRevisionEstimate =
oldestDetailedGCRevisionEstimate;
}
}
@@ -625,7 +636,9 @@ public class VersionGarbageCollector {
}
}
- if ((detailedGCEnabled && rec.ignoreDetailedGCDueToCheckPoint)
|| rec.ignoreDueToCheckPoint) {
+ if ((!detailedGCEnabled ||
rec.ignoreDetailedGCDueToCheckPoint) && rec.ignoreDueToCheckPoint) {
+ // cancel if both are stopped by checkpoint
+ // otherwise we need to continue
cancel.set(true);
}
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 d8ff6acf88..65f2553872 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
@@ -329,7 +329,7 @@ public class VersionGarbageCollectorIT {
assertEquals(1, stats.deletedPropsCount);
assertEquals(1, stats.updatedDetailedGCDocsCount);
assertTrue(stats.ignoredGCDueToCheckPoint);
- assertFalse(stats.ignoredDetailedGCDueToCheckPoint);
+ assertTrue(stats.ignoredDetailedGCDueToCheckPoint);
assertTrue(stats.canceled);
}
diff --git
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorLogTest.java
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorLogTest.java
index e78f9df886..c3329ef248 100644
---
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorLogTest.java
+++
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorLogTest.java
@@ -25,8 +25,10 @@ import org.apache.jackrabbit.guava.common.collect.Lists;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.commons.junit.LogCustomizer;
+import
org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector.VersionGCStats;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.jackrabbit.oak.stats.Clock;
+import org.jetbrains.annotations.NotNull;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
@@ -38,6 +40,7 @@ import ch.qos.logback.classic.Level;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
public class VersionGarbageCollectorLogTest {
@@ -75,6 +78,7 @@ public class VersionGarbageCollectorLogTest {
@After
public void after() {
logCustomizer.finished();
+ ClusterNodeInfo.resetClockToDefault();
}
@AfterClass
@@ -97,6 +101,24 @@ public class VersionGarbageCollectorLogTest {
}
}
+ @Test
+ public void gcWithCheckpoint() throws Exception {
+ ClusterNodeInfo.setClock(clock);
+ createGarbage();
+ for( int i = 0; i < 60; i++ ) {
+ clock.waitUntil(clock.getTime() + TimeUnit.MINUTES.toMillis(1));
+ ns.renewClusterIdLease();
+ }
+ ns.runBackgroundOperations();
+ addNode("/unrelated");
+ String checkpoint = ns.checkpoint(Long.MAX_VALUE);
+ clock.waitUntil(clock.getTime() + TimeUnit.MINUTES.toMillis(1));
+ ns.renewClusterIdLease();
+ VersionGarbageCollector gc = ns.getVersionGarbageCollector();
+ VersionGCStats stats = gc.gc(10, TimeUnit.SECONDS);
+ assertTrue(stats.ignoredGCDueToCheckPoint);
+ }
+
private int getNumDeleted(String msg) {
int idx = msg.indexOf('[');
return Integer.parseInt(msg.substring(idx + 1, msg.indexOf(']')));