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(']')));

Reply via email to