This is an automated email from the ASF dual-hosted git repository.

daim pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git


The following commit(s) were added to refs/heads/trunk by this push:
     new e433fe28f2 OAK-11634 : provided support for generations in FullGC 
(#2206)
e433fe28f2 is described below

commit e433fe28f2f19f07ec1287137fd71799269890c1
Author: Rishabh Kumar <[email protected]>
AuthorDate: Mon Apr 14 13:26:35 2025 +0530

    OAK-11634 : provided support for generations in FullGC (#2206)
    
    * OAK-11634 : provided support for generations in FullGC
    
    * OAK-11634 : avoid reset operation if full gc is not enabled
    
    * OAK-11634 : changed log level to info
    
    ---------
    
    Co-authored-by: Rishabh Kumar <[email protected]>
---
 .../plugins/document/DocumentNodeStoreHelper.java  |   3 +-
 .../oak/plugins/document/Configuration.java        |  12 ++
 .../oak/plugins/document/DocumentNodeStore.java    |   3 +-
 .../plugins/document/DocumentNodeStoreBuilder.java |  10 ++
 .../plugins/document/DocumentNodeStoreService.java |   2 +
 .../plugins/document/VersionGarbageCollector.java  |  89 ++++++++++-
 .../document/rdb/RDBDocumentNodeStoreBuilder.java  |  13 ++
 .../DocumentNodeStoreServiceConfigurationTest.java |  10 ++
 .../oak/plugins/document/VersionGCInitTest.java    |  56 +++++++
 .../oak/plugins/document/VersionGCTest.java        | 137 +++++++++++++++--
 .../document/VersionGarbageCollectorIT.java        |   2 +-
 .../document/VersionGarbageCollectorTest.java      | 162 +++++++++++++++++++++
 .../mongo/MongoDocumentNodeStoreBuilderTest.java   |  17 ++-
 .../rdb/RDBDocumentNodeStoreBuilderTest.java       |   7 +
 .../oak/plugins/document/util/UtilsTest.java       |  31 +++-
 15 files changed, 533 insertions(+), 21 deletions(-)

diff --git 
a/oak-run-commons/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreHelper.java
 
b/oak-run-commons/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreHelper.java
index b46b3880dd..1ef9956b6b 100644
--- 
a/oak-run-commons/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreHelper.java
+++ 
b/oak-run-commons/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreHelper.java
@@ -74,7 +74,8 @@ public class DocumentNodeStoreHelper {
                                                           boolean 
isFullGCDryRun, final DocumentNodeStoreBuilder<?> builder) {
         return new VersionGarbageCollector(nodeStore, gcSupport, 
isFullGCEnabled(builder), isFullGCDryRun,
                 isEmbeddedVerificationEnabled(builder), 
builder.getFullGCMode(), builder.getFullGCDelayFactor(),
-                builder.getFullGCBatchSize(), builder.getFullGCProgressSize(), 
builder.getFullGcMaxAgeMillis());
+                builder.getFullGCBatchSize(), builder.getFullGCProgressSize(), 
builder.getFullGcMaxAgeMillis(),
+                builder.getFullGCGeneration());
     }
 
     public static DocumentNodeState readNode(DocumentNodeStore 
documentNodeStore, Path path, RevisionVector rootRevision) {
diff --git 
a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Configuration.java
 
b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Configuration.java
index eb5e9e1f51..48be6afe0e 100644
--- 
a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Configuration.java
+++ 
b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Configuration.java
@@ -36,6 +36,7 @@ import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreBuilde
 import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreBuilder.DEFAULT_UPDATE_LIMIT;
 import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreService.DEFAULT_FULL_GC_ENABLED;
 import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreService.DEFAULT_EMBEDDED_VERIFICATION_ENABLED;
+import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreService.DEFAULT_FULL_GC_GENERATION;
 import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreService.DEFAULT_PERFLOGGER_INFO_MILLIS;
 import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreService.DEFAULT_THROTTLING_ENABLED;
 import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreService.DEFAULT_FULL_GC_MODE;
@@ -376,6 +377,17 @@ import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreServic
                     "is set to true, the fullGCMode will be ignored.")
     int fullGCMode() default DEFAULT_FULL_GC_MODE;
 
+    @AttributeDefinition(
+            name = "Document Node Store Full GC Generation",
+            description = "Long value indicating which Full GC generation is 
currently running on " +
+                    "document node store. The Default value is " + 
DEFAULT_FULL_GC_GENERATION +
+                    ". Note that this value can be overridden via framework " +
+                    "property 'oak.documentstore.fullGCGeneration'. " +
+                    "FullGC can be reset to run from beginning after 
incrementing this value. " +
+                    "Any value change must be a increment from previous value 
to reset the FullGC, " +
+                    "in case we set to a value smaller or equal to exiting 
generation, it would simply be ignored.")
+    long fullGCGeneration() default DEFAULT_FULL_GC_GENERATION;
+
     @AttributeDefinition(
             name = "Delay factor for a Full GC run",
             description = "A Full GC run has a gap of this delay factor to 
reduce continuous load on system." +
diff --git 
a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
 
b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
index 0f7109ada8..4ec7871024 100644
--- 
a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
+++ 
b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
@@ -653,7 +653,8 @@ public final class DocumentNodeStore
         this.versionGarbageCollector = new VersionGarbageCollector(
                 this, builder.createVersionGCSupport(), 
isFullGCEnabled(builder), false,
                 isEmbeddedVerificationEnabled(builder), 
builder.getFullGCMode(), builder.getFullGCDelayFactor(),
-                builder.getFullGCBatchSize(), builder.getFullGCProgressSize(), 
builder.getFullGcMaxAgeMillis());
+                builder.getFullGCBatchSize(), builder.getFullGCProgressSize(), 
builder.getFullGcMaxAgeMillis(),
+                builder.getFullGCGeneration());
         
this.versionGarbageCollector.setStatisticsProvider(builder.getStatisticsProvider());
         this.versionGarbageCollector.setGCMonitor(builder.getGCMonitor());
         
this.versionGarbageCollector.setFullGCPaths(builder.getFullGCIncludePaths(), 
builder.getFullGCExcludePaths());
diff --git 
a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBuilder.java
 
b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBuilder.java
index 702cdca0bc..05b5e721f3 100644
--- 
a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBuilder.java
+++ 
b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBuilder.java
@@ -181,6 +181,7 @@ public class DocumentNodeStoreBuilder<T extends 
DocumentNodeStoreBuilder<T>> {
     private Set<String> fullGCExcludePaths = Set.of();
     private boolean embeddedVerificationEnabled = 
DocumentNodeStoreService.DEFAULT_EMBEDDED_VERIFICATION_ENABLED;
     private int fullGCMode = DocumentNodeStoreService.DEFAULT_FULL_GC_MODE;
+    private long fullGCGeneration = 
DocumentNodeStoreService.DEFAULT_FULL_GC_GENERATION;
     private long fullGcMaxAgeMillis = 
TimeUnit.SECONDS.toMillis(DocumentNodeStoreService.DEFAULT_FULL_GC_MAX_AGE);
     private int fullGCBatchSize = 
DocumentNodeStoreService.DEFAULT_FGC_BATCH_SIZE;
     private int fullGCProgressSize = 
DocumentNodeStoreService.DEFAULT_FGC_PROGRESS_SIZE;
@@ -371,6 +372,15 @@ public class DocumentNodeStoreBuilder<T extends 
DocumentNodeStoreBuilder<T>> {
         return this.fullGCMode;
     }
 
+    public T setFullGCGeneration(long v) {
+        this.fullGCGeneration = v;
+        return thisBuilder();
+    }
+
+    public long getFullGCGeneration() {
+        return this.fullGCGeneration;
+    }
+
     /**
      * The maximum age for nodes in milliseconds. Older entries are candidates 
for full gc
      * @param v max age in millis
diff --git 
a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java
 
b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java
index 9790267abd..27ce481cb7 100644
--- 
a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java
+++ 
b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java
@@ -141,6 +141,7 @@ public class DocumentNodeStoreService {
     static final boolean DEFAULT_FULL_GC_ENABLED = false;
     static final boolean DEFAULT_EMBEDDED_VERIFICATION_ENABLED = true;
     static final int DEFAULT_FULL_GC_MODE = 0;
+    static final int DEFAULT_FULL_GC_GENERATION = 0;
     static final int DEFAULT_MONGO_LEASE_SO_TIMEOUT_MILLIS = 30000;
     static final String DEFAULT_PERSISTENT_CACHE = "cache";
     static final String DEFAULT_JOURNAL_CACHE = "diff-cache";
@@ -530,6 +531,7 @@ public class DocumentNodeStoreService {
                 setFullGCExcludePaths(config.fullGCExcludePaths()).
                 
setEmbeddedVerificationEnabled(config.embeddedVerificationEnabled()).
                 setFullGCMode(config.fullGCMode()).
+                setFullGCGeneration(config.fullGCGeneration()).
                 
setFullGcMaxAgeMillis(TimeUnit.SECONDS.toMillis(config.fullGcMaxAgeInSecs())).
                 setFullGCBatchSize(config.fullGCBatchSize()).
                 setFullGCProgressSize(config.fullGCProgressSize()).
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 386f623da5..86291d8def 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
@@ -83,11 +83,13 @@ import static 
org.apache.jackrabbit.oak.plugins.document.Collection.SETTINGS;
 import static org.apache.jackrabbit.oak.plugins.document.Document.ID;
 import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreService.DEFAULT_FGC_BATCH_SIZE;
 import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreService.DEFAULT_FGC_PROGRESS_SIZE;
+import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreService.DEFAULT_FULL_GC_GENERATION;
 import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreService.DEFAULT_FULL_GC_MAX_AGE;
 import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreService.DEFAULT_FULL_GC_MODE;
 import static 
org.apache.jackrabbit.oak.plugins.document.NodeDocument.BRANCH_COMMITS;
 import static 
org.apache.jackrabbit.oak.plugins.document.NodeDocument.COLLISIONS;
 import static 
org.apache.jackrabbit.oak.plugins.document.NodeDocument.COMMIT_ROOT;
+import static org.apache.jackrabbit.oak.plugins.document.NodeDocument.LOG;
 import static 
org.apache.jackrabbit.oak.plugins.document.NodeDocument.MIN_ID_VALUE;
 import static 
org.apache.jackrabbit.oak.plugins.document.NodeDocument.MODIFIED_IN_SECS;
 import static 
org.apache.jackrabbit.oak.plugins.document.NodeDocument.REVISIONS;
@@ -153,6 +155,11 @@ public class VersionGarbageCollector {
      */
     static final String SETTINGS_COLLECTION_FULL_GC_DRY_RUN_TIMESTAMP_PROP = 
"fullGCDryRunTimeStamp";
 
+    /**
+     * Property name to fullGcGeneration which is currently running
+     */
+    static final String SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP = 
"fullGCGeneration";
+
     /**
      * Property name to _id till when last full-GC run happened in dryRun mode 
only
      */
@@ -174,6 +181,78 @@ public class VersionGarbageCollector {
         VersionGarbageCollector.fullGcMode = FullGCMode.getMode(fullGcMode);
     }
 
+    /**
+     * Sets the full GC generation for this document store and performs a 
reset if needed.
+     * <p>
+     * This method checks the existing full GC generation stored in the 
settings document:
+     * <ul>
+     *   <li>If no document exists, the new generation value is persisted</li>
+     *   <li>If the previous generation isn't a number or null, resets full GC 
and persists the new value</li>
+     *   <li>If the new generation is higher than the previous one, resets 
full GC and updates the value</li>
+     *   <li>If the new generation is less than or equal to the previous one, 
only logs the information</li>
+     * </ul>
+     *
+     * @param fullGcGen The new full GC generation value to set
+     * @return the generation with which the full GC has started
+     */
+    long resetFullGcIfGenChange(final long fullGcGen) {
+
+        if (fullGcGen == DEFAULT_FULL_GC_GENERATION) {
+            // generation hasn't been set yet, no need to make any change to 
make this backward compatible
+            LOG.info("Full GC generation is set to default value {}. No action 
needed.", fullGcGen);
+            return fullGcGen;
+        }
+
+        final Document doc = ds.find(SETTINGS, SETTINGS_COLLECTION_ID);
+
+        if (doc == null) {
+            // No version gc document exists, must be a new environment
+            persistFullGcGen(fullGcGen);
+            return fullGcGen;
+        }
+
+        final Object prevFullGcGenObj = 
doc.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP);
+
+        // If no previous generation or not a Number, just persist the new 
value
+        if (!(prevFullGcGenObj instanceof Number)) {
+            // this could happen if the previous value was set to a 
non-numeric value i.e. (not present)
+            LOG.info("Full GC generation {} is not a valid number or null, 
resetting to {}.", prevFullGcGenObj, fullGcGen);
+            resetFullGC();
+            persistFullGcGen(fullGcGen);
+            return fullGcGen;
+        }
+
+        // Compare with the previous generation
+        long prevFullGcGen = ((Number) prevFullGcGenObj).longValue();
+        if (prevFullGcGen >= fullGcGen) {
+            LOG.info("Full GC generation {} is less than or equal to the 
previously saved value {}.", fullGcGen, prevFullGcGen);
+            return prevFullGcGen;
+        } else {
+            LOG.info("Found a new generation of FullGC {}, resetting the Old 
gen {} values.", fullGcGen, prevFullGcGen);
+            resetFullGC();
+            persistFullGcGen(fullGcGen);
+            return fullGcGen;
+        }
+    }
+
+    /**
+     * Persists the full garbage collection generation value to the settings 
document.
+     * <p>
+     * This method creates or updates a document in the settings collection 
with the
+     * specified full GC generation number. The generation value is used to 
track major
+     * changes in the garbage collection process across restarts or different 
cluster nodes.
+     * <p>
+     * When the system detects a higher generation number than previously 
stored, it will
+     * reset the full GC state before persisting the new generation value.
+     *
+     * @param fullGcGeneration The full garbage collection generation value to 
persist
+     */
+    private void persistFullGcGen(long fullGcGeneration) {
+        UpdateOp op = new UpdateOp(SETTINGS_COLLECTION_ID, true);
+        op.set(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP, fullGcGeneration);
+        ds.createOrUpdate(SETTINGS, op);
+    }
+
     private final DocumentNodeStore nodeStore;
     private final DocumentStore ds;
     private final boolean fullGCEnabled;
@@ -198,7 +277,7 @@ public class VersionGarbageCollector {
                             final boolean isFullGCDryRun,
                             final boolean embeddedVerification) {
         this(nodeStore, gcSupport, fullGCEnabled, isFullGCDryRun, 
embeddedVerification, DEFAULT_FULL_GC_MODE,
-                0, DEFAULT_FGC_BATCH_SIZE, DEFAULT_FGC_PROGRESS_SIZE, 
SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE));
+                0, DEFAULT_FGC_BATCH_SIZE, DEFAULT_FGC_PROGRESS_SIZE, 
SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE), 0);
     }
 
     VersionGarbageCollector(DocumentNodeStore nodeStore,
@@ -210,7 +289,8 @@ public class VersionGarbageCollector {
                             final double fullGCDelayFactor,
                             final int fullGCBatchSize,
                             final int fullGCProgressSize,
-                            final long fullGcMaxAgeInMillis) {
+                            final long fullGcMaxAgeInMillis,
+                            final long fullGcGeneration) {
         this.nodeStore = nodeStore;
         this.versionStore = gcSupport;
         this.ds = gcSupport.getDocumentStore();
@@ -224,8 +304,9 @@ public class VersionGarbageCollector {
         this.options = new VersionGCOptions();
 
         setFullGcMode(fullGCMode);
-        AUDIT_LOG.info("<init> VersionGarbageCollector created with 
fullGcMode: {}, maxFullGcAgeInMillis: {}, batchSize: {}, progressSize: {}, 
delayFactor: {}",
-                fullGcMode, fullGcMaxAgeInMillis, fullGCBatchSize, 
fullGCProgressSize, fullGCDelayFactor);
+        long fullGcGen = fullGCEnabled ? 
resetFullGcIfGenChange(fullGcGeneration) : fullGcGeneration;
+        AUDIT_LOG.info("<init> VersionGarbageCollector created with 
fullGcMode: {}, maxFullGcAgeInMillis: {}, batchSize: {}, progressSize: {}, 
delayFactor: {}, fullGcGeneration: {}",
+                fullGcMode, fullGcMaxAgeInMillis, fullGCBatchSize, 
fullGCProgressSize, fullGCDelayFactor, fullGcGen);
     }
 
     /**
diff --git 
a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentNodeStoreBuilder.java
 
b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentNodeStoreBuilder.java
index bff7f0ff17..b8223fd599 100644
--- 
a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentNodeStoreBuilder.java
+++ 
b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentNodeStoreBuilder.java
@@ -183,6 +183,19 @@ public class RDBDocumentNodeStoreBuilder
         return 0;
     }
 
+    @Override
+    public RDBDocumentNodeStoreBuilder setFullGCGeneration(long v) {
+        // fullGC modes are not supported for RDB
+        log.warn("FullGC generation are not supported for RDB");
+        return thisBuilder();
+    }
+
+    @Override
+    public long getFullGCGeneration() {
+        // fullGC modes are not supported for RDB
+        return 0;
+    }
+
     @Override
     public RDBDocumentNodeStoreBuilder setFullGcMaxAgeMillis(long v) {
         // fullGC modes are not supported for RDB
diff --git 
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreServiceConfigurationTest.java
 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreServiceConfigurationTest.java
index 2e9429a9ad..1516626a85 100644
--- 
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreServiceConfigurationTest.java
+++ 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreServiceConfigurationTest.java
@@ -39,6 +39,7 @@ import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreServic
 import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreService.DEFAULT_FGC_PROGRESS_SIZE;
 import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreService.DEFAULT_FULL_GC_ENABLED;
 import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreService.DEFAULT_EMBEDDED_VERIFICATION_ENABLED;
+import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreService.DEFAULT_FULL_GC_GENERATION;
 import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreService.DEFAULT_FULL_GC_MODE;
 import static 
org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreService.DEFAULT_THROTTLING_ENABLED;
 import static org.junit.Assert.assertArrayEquals;
@@ -97,6 +98,7 @@ public class DocumentNodeStoreServiceConfigurationTest {
         assertEquals(DEFAULT_THROTTLING_ENABLED, config.throttlingEnabled());
         assertEquals(DEFAULT_FULL_GC_ENABLED, config.fullGCEnabled());
         assertEquals(DEFAULT_FULL_GC_MODE, config.fullGCMode());
+        assertEquals(DEFAULT_FULL_GC_GENERATION, config.fullGCGeneration());
         assertEquals(DEFAULT_FGC_DELAY_FACTOR, config.fullGCDelayFactor(), 
0.01);
         assertEquals(DEFAULT_FGC_BATCH_SIZE, config.fullGCBatchSize());
         assertEquals(DEFAULT_FGC_PROGRESS_SIZE, config.fullGCProgressSize());
@@ -139,6 +141,14 @@ public class DocumentNodeStoreServiceConfigurationTest {
         assertEquals(fullGCModeValue, config.fullGCMode());
     }
 
+    @Test
+    public void fullGCGenerationValueSet() throws Exception {
+        long fullGCGenerationValue = 2;
+        addConfigurationEntry(preset, "fullGCGeneration", 
fullGCGenerationValue);
+        Configuration config = createConfiguration();
+        assertEquals(fullGCGenerationValue, config.fullGCGeneration());
+    }
+
     @Test
     public void fullGCIncludePaths() throws Exception {
         final String[] includesPath = new String[]{"/foo", "/bar"};
diff --git 
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCInitTest.java
 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCInitTest.java
index 47afbf2b7e..dad613d6d0 100644
--- 
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCInitTest.java
+++ 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCInitTest.java
@@ -33,6 +33,7 @@ import static 
org.apache.jackrabbit.oak.plugins.document.NodeDocument.MIN_ID_VAL
 import static 
org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector.SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP;
 import static 
org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector.SETTINGS_COLLECTION_FULL_GC_DRY_RUN_DOCUMENT_ID_PROP;
 import static 
org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector.SETTINGS_COLLECTION_FULL_GC_DRY_RUN_TIMESTAMP_PROP;
+import static 
org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector.SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP;
 import static 
org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector.SETTINGS_COLLECTION_FULL_GC_TIMESTAMP_PROP;
 import static 
org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector.SETTINGS_COLLECTION_ID;
 import static 
org.apache.jackrabbit.oak.plugins.document.util.Utils.getIdFromPath;
@@ -66,6 +67,7 @@ public class VersionGCInitTest {
         // fullGC values shouldn't have been updated without fullGC enabled
         assertNull(vgc.get(SETTINGS_COLLECTION_FULL_GC_TIMESTAMP_PROP));
         assertNull(vgc.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
+        assertNull(vgc.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP));
     }
 
     @Test
@@ -88,6 +90,57 @@ public class VersionGCInitTest {
         assertEquals(stats.oldestModifiedDocTimeStamp, 
vgc.get(SETTINGS_COLLECTION_FULL_GC_TIMESTAMP_PROP));
         assertEquals(stats.oldestModifiedDocId, 
vgc.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
         assertEquals(MIN_ID_VALUE, 
vgc.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
+        assertNull(vgc.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP));
+    }
+
+    @Test
+    public void lazyInitializeWithFullGCWithGenerationWithFullGCDisabled() 
throws Exception {
+        ns = 
builderProvider.newBuilder().setFullGCGeneration(1).getNodeStore();
+        DocumentStore store = ns.getDocumentStore();
+        Document vgc = store.find(SETTINGS, SETTINGS_COLLECTION_ID);
+        assertNull(vgc);
+
+        enableFullGC(ns.getVersionGarbageCollector());
+        long offset = SECONDS.toMillis(42);
+        String id = getIdFromPath("/node");
+        Revision r = new Revision(offset, 0, 1);
+        UpdateOp op = new UpdateOp(id, true);
+        NodeDocument.setModified(op, r);
+        store.createOrUpdate(NODES, op);
+        VersionGCStats stats = ns.getVersionGarbageCollector().gc(1, DAYS);
+
+        vgc = store.find(SETTINGS, SETTINGS_COLLECTION_ID);
+        assertNotNull(vgc);
+        assertEquals(stats.oldestModifiedDocTimeStamp, 
vgc.get(SETTINGS_COLLECTION_FULL_GC_TIMESTAMP_PROP));
+        assertEquals(stats.oldestModifiedDocId, 
vgc.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
+        assertEquals(MIN_ID_VALUE, 
vgc.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
+        assertNull(vgc.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP));
+    }
+
+    @Test
+    public void lazyInitializeWithFullGCWithGeneration() throws Exception {
+        ns = 
builderProvider.newBuilder().setFullGCGeneration(1).setFullGCEnabled(true).getNodeStore();
+        DocumentStore store = ns.getDocumentStore();
+        Document vgc = store.find(SETTINGS, SETTINGS_COLLECTION_ID);
+        assertNotNull(vgc);
+        assertEquals(1L, vgc.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP));
+
+        enableFullGC(ns.getVersionGarbageCollector());
+        ns.getVersionGarbageCollector().resetFullGcIfGenChange(1);
+        long offset = SECONDS.toMillis(42);
+        String id = getIdFromPath("/node");
+        Revision r = new Revision(offset, 0, 1);
+        UpdateOp op = new UpdateOp(id, true);
+        NodeDocument.setModified(op, r);
+        store.createOrUpdate(NODES, op);
+        VersionGCStats stats = ns.getVersionGarbageCollector().gc(1, DAYS);
+
+        vgc = store.find(SETTINGS, SETTINGS_COLLECTION_ID);
+        assertNotNull(vgc);
+        assertEquals(stats.oldestModifiedDocTimeStamp, 
vgc.get(SETTINGS_COLLECTION_FULL_GC_TIMESTAMP_PROP));
+        assertEquals(stats.oldestModifiedDocId, 
vgc.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
+        assertEquals(MIN_ID_VALUE, 
vgc.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
+        assertEquals(1L, vgc.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP));
     }
 
     @Test
@@ -104,6 +157,7 @@ public class VersionGCInitTest {
         assertEquals(stats.oldestModifiedDocTimeStamp, 
vgc.get(SETTINGS_COLLECTION_FULL_GC_TIMESTAMP_PROP));
         assertEquals(stats.oldestModifiedDocId, 
vgc.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
         assertEquals(MIN_ID_VALUE, 
vgc.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
+        assertNull(vgc.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP));
     }
 
     @Test
@@ -128,6 +182,7 @@ public class VersionGCInitTest {
         // fullGC values shouldn't have been updated in dryRun mode
         assertNull(vgc.get(SETTINGS_COLLECTION_FULL_GC_TIMESTAMP_PROP));
         assertNull(vgc.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
+        assertNull(vgc.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP));
 
         // dryRun mode values should have been updated
         assertEquals(stats.oldestModifiedDocTimeStamp, 
vgc.get(SETTINGS_COLLECTION_FULL_GC_DRY_RUN_TIMESTAMP_PROP));
@@ -149,6 +204,7 @@ public class VersionGCInitTest {
         // fullGC values shouldn't have been updated in dryRun mode
         assertNull(vgc.get(SETTINGS_COLLECTION_FULL_GC_TIMESTAMP_PROP));
         assertNull(vgc.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
+        assertNull(vgc.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP));
 
         // dryRun mode values should have been updated
         assertEquals(stats.oldestModifiedDocTimeStamp, 
vgc.get(SETTINGS_COLLECTION_FULL_GC_DRY_RUN_TIMESTAMP_PROP));
diff --git 
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCTest.java
 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCTest.java
index e57c9b3950..5876e78177 100644
--- 
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCTest.java
+++ 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCTest.java
@@ -62,6 +62,7 @@ import static 
org.apache.jackrabbit.oak.plugins.document.FullGCHelper.enableFull
 import static 
org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector.SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP;
 import static 
org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector.SETTINGS_COLLECTION_FULL_GC_DRY_RUN_DOCUMENT_ID_PROP;
 import static 
org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector.SETTINGS_COLLECTION_FULL_GC_DRY_RUN_TIMESTAMP_PROP;
+import static 
org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector.SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP;
 import static 
org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector.SETTINGS_COLLECTION_FULL_GC_TIMESTAMP_PROP;
 import static 
org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector.SETTINGS_COLLECTION_ID;
 import static org.junit.Assert.assertEquals;
@@ -535,12 +536,128 @@ public class VersionGCTest {
 
     // OAK-10370 END
 
+    @Test
+    public void testResetWithFullGCGeneration() throws Exception {
+        enableFullGC(gc);
+        VersionGCStats stats = FullGCHelper.gc(gc, 30, TimeUnit.MINUTES);
+        assertNotNull(stats);
+
+        final Document settingsBefore = store.find(SETTINGS, 
SETTINGS_COLLECTION_ID);
+        assertNotNull(settingsBefore);
+        
assertNotNull(settingsBefore.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
+        
assertNotNull(settingsBefore.get(SETTINGS_COLLECTION_FULL_GC_TIMESTAMP_PROP));
+        
assertNull(settingsBefore.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP));
+
+        gc.resetFullGcIfGenChange(1);
+        final Document settingsAfter = store.find(SETTINGS, 
SETTINGS_COLLECTION_ID);
+        assertNotNull(settingsAfter);
+        
assertNull(settingsAfter.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
+        
assertNull(settingsAfter.get(SETTINGS_COLLECTION_FULL_GC_TIMESTAMP_PROP));
+        assertEquals(1L, 
settingsAfter.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP));
+    }
+
+    @Test
+    public void testResetWithFullGCGenerationIncrement() throws Exception {
+        enableFullGC(gc);
+        VersionGCStats stats = FullGCHelper.gc(gc, 30, TimeUnit.MINUTES);
+        assertNotNull(stats);
+
+        final Document settingsBefore = store.find(SETTINGS, 
SETTINGS_COLLECTION_ID);
+        assertNotNull(settingsBefore);
+        
assertNotNull(settingsBefore.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
+        
assertNotNull(settingsBefore.get(SETTINGS_COLLECTION_FULL_GC_TIMESTAMP_PROP));
+        
assertNull(settingsBefore.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP));
+
+        gc.resetFullGcIfGenChange(1);
+        final Document settingsAfter = store.find(SETTINGS, 
SETTINGS_COLLECTION_ID);
+        assertNotNull(settingsAfter);
+        
assertNull(settingsAfter.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
+        
assertNull(settingsAfter.get(SETTINGS_COLLECTION_FULL_GC_TIMESTAMP_PROP));
+        assertEquals(1L, 
settingsAfter.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP));
+
+        // run full gc and set fullgc variables again in db
+        stats = FullGCHelper.gc(gc, 30, TimeUnit.MINUTES);
+        assertNotNull(stats);
+
+        // change generation to a higher value
+        gc.resetFullGcIfGenChange(2);
+        final Document settingsAfter2 = store.find(SETTINGS, 
SETTINGS_COLLECTION_ID);
+        assertNotNull(settingsAfter2);
+        
assertNull(settingsAfter2.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
+        
assertNull(settingsAfter2.get(SETTINGS_COLLECTION_FULL_GC_TIMESTAMP_PROP));
+        assertEquals(2L, 
settingsAfter2.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP));
+    }
+
+    @Test
+    public void testResetWithFullGCGenerationDecrement() throws Exception {
+        enableFullGC(gc);
+        VersionGCStats stats = FullGCHelper.gc(gc, 30, TimeUnit.MINUTES);
+        assertNotNull(stats);
+
+        final Document settingsBefore = store.find(SETTINGS, 
SETTINGS_COLLECTION_ID);
+        assertNotNull(settingsBefore);
+        
assertNotNull(settingsBefore.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
+        
assertNotNull(settingsBefore.get(SETTINGS_COLLECTION_FULL_GC_TIMESTAMP_PROP));
+        
assertNull(settingsBefore.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP));
+
+        gc.resetFullGcIfGenChange(2);
+        final Document settingsAfter = store.find(SETTINGS, 
SETTINGS_COLLECTION_ID);
+        assertNotNull(settingsAfter);
+        
assertNull(settingsAfter.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
+        
assertNull(settingsAfter.get(SETTINGS_COLLECTION_FULL_GC_TIMESTAMP_PROP));
+        assertEquals(2L, 
settingsAfter.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP));
+
+        // run full gc and set fullgc variables again in db
+        stats = FullGCHelper.gc(gc, 30, TimeUnit.MINUTES);
+        assertNotNull(stats);
+
+        // change generation to a lower value
+        gc.resetFullGcIfGenChange(1);
+        final Document settingsAfter2 = store.find(SETTINGS, 
SETTINGS_COLLECTION_ID);
+        assertNotNull(settingsAfter2);
+        
assertNotNull(settingsAfter2.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
+        
assertNotNull(settingsAfter2.get(SETTINGS_COLLECTION_FULL_GC_TIMESTAMP_PROP));
+        assertEquals(2L, 
settingsAfter2.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP));
+    }
+
+    @Test
+    public void testResetWithFullGCGenerationSameValue() throws Exception {
+        enableFullGC(gc);
+        VersionGCStats stats = FullGCHelper.gc(gc, 30, TimeUnit.MINUTES);
+        assertNotNull(stats);
+
+        final Document settingsBefore = store.find(SETTINGS, 
SETTINGS_COLLECTION_ID);
+        assertNotNull(settingsBefore);
+        
assertNotNull(settingsBefore.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
+        
assertNotNull(settingsBefore.get(SETTINGS_COLLECTION_FULL_GC_TIMESTAMP_PROP));
+        
assertNull(settingsBefore.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP));
+
+        gc.resetFullGcIfGenChange(2);
+        final Document settingsAfter = store.find(SETTINGS, 
SETTINGS_COLLECTION_ID);
+        assertNotNull(settingsAfter);
+        
assertNull(settingsAfter.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
+        
assertNull(settingsAfter.get(SETTINGS_COLLECTION_FULL_GC_TIMESTAMP_PROP));
+        assertEquals(2L, 
settingsAfter.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP));
+
+        // run full gc and set fullgc variables again in db
+        stats = FullGCHelper.gc(gc, 30, TimeUnit.MINUTES);
+        assertNotNull(stats);
+
+        // change generation to a same value
+        gc.resetFullGcIfGenChange(2);
+        final Document settingsAfter2 = store.find(SETTINGS, 
SETTINGS_COLLECTION_ID);
+        assertNotNull(settingsAfter2);
+        
assertNotNull(settingsAfter2.get(SETTINGS_COLLECTION_FULL_GC_DOCUMENT_ID_PROP));
+        
assertNotNull(settingsAfter2.get(SETTINGS_COLLECTION_FULL_GC_TIMESTAMP_PROP));
+        assertEquals(2L, 
settingsAfter2.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP));
+    }
+
     // OAK-10745
     @Test
     public void testVGCWithBatchSizeSmallerThanProgressSize() throws 
IllegalAccessException {
         VersionGarbageCollector vgc = new VersionGarbageCollector(
                 ns, new VersionGCSupport(store), true, false, false,
-                0, 0, 1000, 5000, 
TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE));
+                0, 0, 1000, 5000, 
TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE), 0);
 
         assertEquals(1000, readDeclaredField(vgc, "fullGCBatchSize", true));
         assertEquals(5000, readDeclaredField(vgc, "fullGCProgressSize", true));
@@ -550,7 +667,7 @@ public class VersionGCTest {
     public void testVGCWithBatchSizeGreaterThanProgressSize() throws 
IllegalAccessException {
         VersionGarbageCollector vgc = new VersionGarbageCollector(
                 ns, new VersionGCSupport(store), true, false, false,
-                0, 0, 20000, 15000, 
TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE));
+                0, 0, 20000, 15000, 
TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE), 0);
 
         assertEquals(15000, readDeclaredField(vgc, "fullGCBatchSize", true));
         assertEquals(15000, readDeclaredField(vgc, "fullGCProgressSize", 
true));
@@ -571,7 +688,7 @@ public class VersionGCTest {
         // reinitialize VersionGarbageCollector with not allowed value
         VersionGarbageCollector gc = new VersionGarbageCollector(
                 ns, new VersionGCSupport(store), true, false, false,
-                fullGcModeNotAllowedValue, 0, DEFAULT_FGC_BATCH_SIZE, 
DEFAULT_FGC_PROGRESS_SIZE, TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE));
+                fullGcModeNotAllowedValue, 0, DEFAULT_FGC_BATCH_SIZE, 
DEFAULT_FGC_PROGRESS_SIZE, TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE), 
0);
 
         assertEquals("Starting VersionGarbageCollector with not applicable / 
not allowed value" +
                 "will set fullGcMode to default NONE", FullGCMode.NONE, 
VersionGarbageCollector.getFullGcMode());
@@ -582,7 +699,7 @@ public class VersionGCTest {
         int fullGcModeNone = 0;
         VersionGarbageCollector gc = new VersionGarbageCollector(
                 ns, new VersionGCSupport(store), true, false, false,
-                fullGcModeNone, 0, DEFAULT_FGC_BATCH_SIZE, 
DEFAULT_FGC_PROGRESS_SIZE, TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE));
+                fullGcModeNone, 0, DEFAULT_FGC_BATCH_SIZE, 
DEFAULT_FGC_PROGRESS_SIZE, TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE), 
0);
 
         assertEquals(FullGCMode.NONE, VersionGarbageCollector.getFullGcMode());
     }
@@ -592,7 +709,7 @@ public class VersionGCTest {
         int fullGcModeGapOrphans = 2;
         VersionGarbageCollector gc = new VersionGarbageCollector(
                 ns, new VersionGCSupport(store), true, false, false,
-                fullGcModeGapOrphans, 0, DEFAULT_FGC_BATCH_SIZE, 
DEFAULT_FGC_PROGRESS_SIZE, TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE));
+                fullGcModeGapOrphans, 0, DEFAULT_FGC_BATCH_SIZE, 
DEFAULT_FGC_PROGRESS_SIZE, TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE), 
0);
 
         assertEquals(FullGCMode.GAP_ORPHANS, 
VersionGarbageCollector.getFullGcMode());
     }
@@ -602,7 +719,7 @@ public class VersionGCTest {
         int fullGcModeGapOrphansEmptyProperties = 3;
         VersionGarbageCollector gc = new VersionGarbageCollector(
                 ns, new VersionGCSupport(store), true, false, false,
-                fullGcModeGapOrphansEmptyProperties, 0, 
DEFAULT_FGC_BATCH_SIZE, DEFAULT_FGC_PROGRESS_SIZE, 
TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE));
+                fullGcModeGapOrphansEmptyProperties, 0, 
DEFAULT_FGC_BATCH_SIZE, DEFAULT_FGC_PROGRESS_SIZE, 
TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE), 0);
 
         assertEquals(FullGCMode.GAP_ORPHANS_EMPTYPROPS, 
VersionGarbageCollector.getFullGcMode());
     }
@@ -616,7 +733,7 @@ public class VersionGCTest {
         int fullGcModeAllOrphansEmptyProperties = 4;
         VersionGarbageCollector gc = new VersionGarbageCollector(
                 ns, new VersionGCSupport(store), true, false, false,
-                fullGcModeAllOrphansEmptyProperties, 0, 
DEFAULT_FGC_BATCH_SIZE, DEFAULT_FGC_PROGRESS_SIZE, 
TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE));
+                fullGcModeAllOrphansEmptyProperties, 0, 
DEFAULT_FGC_BATCH_SIZE, DEFAULT_FGC_PROGRESS_SIZE, 
TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE), 0);
 
         assertEquals(FullGCMode.ALL_ORPHANS_EMPTYPROPS, 
VersionGarbageCollector.getFullGcMode());
     }
@@ -626,7 +743,7 @@ public class VersionGCTest {
         int fullGcModeAllOrphansEmptyPropertiesKeepOneUserProps = 5;
         VersionGarbageCollector gc = new VersionGarbageCollector(
                 ns, new VersionGCSupport(store), true, false, false,
-                fullGcModeAllOrphansEmptyPropertiesKeepOneUserProps, 0, 
DEFAULT_FGC_BATCH_SIZE, DEFAULT_FGC_PROGRESS_SIZE, 
TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE));
+                fullGcModeAllOrphansEmptyPropertiesKeepOneUserProps, 0, 
DEFAULT_FGC_BATCH_SIZE, DEFAULT_FGC_PROGRESS_SIZE, 
TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE), 0);
 
         assertEquals(FullGCMode.ORPHANS_EMPTYPROPS_KEEP_ONE_USER_PROPS, 
VersionGarbageCollector.getFullGcMode());
     }
@@ -636,7 +753,7 @@ public class VersionGCTest {
         int fullGcModeAllOrphansEmptyPropertiesKeepOneAllProps = 6;
         VersionGarbageCollector gc = new VersionGarbageCollector(
                 ns, new VersionGCSupport(store), true, false, false,
-                fullGcModeAllOrphansEmptyPropertiesKeepOneAllProps, 0, 
DEFAULT_FGC_BATCH_SIZE, DEFAULT_FGC_PROGRESS_SIZE, 
TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE));
+                fullGcModeAllOrphansEmptyPropertiesKeepOneAllProps, 0, 
DEFAULT_FGC_BATCH_SIZE, DEFAULT_FGC_PROGRESS_SIZE, 
TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE), 0);
 
         assertEquals(FullGCMode.ORPHANS_EMPTYPROPS_KEEP_ONE_ALL_PROPS, 
VersionGarbageCollector.getFullGcMode());
     }
@@ -646,7 +763,7 @@ public class VersionGCTest {
         int fullGcModeAllOrphansEmptyPropertiesUnmergedBC = 7;
         VersionGarbageCollector gc = new VersionGarbageCollector(
                 ns, new VersionGCSupport(store), true, false, false,
-                fullGcModeAllOrphansEmptyPropertiesUnmergedBC, 0, 
DEFAULT_FGC_BATCH_SIZE, DEFAULT_FGC_PROGRESS_SIZE, 
TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE));
+                fullGcModeAllOrphansEmptyPropertiesUnmergedBC, 0, 
DEFAULT_FGC_BATCH_SIZE, DEFAULT_FGC_PROGRESS_SIZE, 
TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE), 0);
 
         assertEquals(FullGCMode.ORPHANS_EMPTYPROPS_UNMERGED_BC, 
VersionGarbageCollector.getFullGcMode());
     }
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 7342cd7d43..f80076ee6b 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
@@ -1789,7 +1789,7 @@ public class VersionGarbageCollectorIT {
             }
         };
 
-        gcRef.set(new VersionGarbageCollector(store1, gcSupport, true, false, 
false, 3, 0, DEFAULT_FGC_BATCH_SIZE, DEFAULT_FGC_PROGRESS_SIZE, 
TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE)));
+        gcRef.set(new VersionGarbageCollector(store1, gcSupport, true, false, 
false, 3, 0, DEFAULT_FGC_BATCH_SIZE, DEFAULT_FGC_PROGRESS_SIZE, 
TimeUnit.SECONDS.toMillis(DEFAULT_FULL_GC_MAX_AGE), 0));
 
         //3. Check that deleted property does get collected post maxAge
         clock.waitUntil(clock.getTime() + HOURS.toMillis(maxAge*2) + delta);
diff --git 
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorTest.java
 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorTest.java
new file mode 100644
index 0000000000..f63824f5f6
--- /dev/null
+++ 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorTest.java
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.document;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.apache.jackrabbit.oak.plugins.document.Collection.SETTINGS;
+import static 
org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector.SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP;
+import static 
org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector.SETTINGS_COLLECTION_ID;
+
+/**
+ * Unit tests for {@link VersionGarbageCollector}
+ */
+public class VersionGarbageCollectorTest {
+
+    final DocumentStore ds = Mockito.mock(DocumentStore.class);
+    final DocumentNodeStore ns = Mockito.mock(DocumentNodeStore.class);
+    final VersionGCSupport gcSupport = Mockito.mock(VersionGCSupport.class);
+    final int fullGcGen = 2;
+    VersionGarbageCollector vgc;
+
+    @Before
+    public void before() {
+        Mockito.when(gcSupport.getDocumentStore()).thenReturn(ds);
+    }
+
+    @Test
+    public void testResetFullGcIfGenChangeWithFullGcDisabled() {
+        // Setup: ensure no settings document exists
+        final Document doc = Mockito.mock(Document.class);
+        
Mockito.when(doc.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP)).thenReturn(5);
+        Mockito.when(ds.find(SETTINGS, 
SETTINGS_COLLECTION_ID)).thenReturn(doc);
+
+        // Execute
+        vgc = new VersionGarbageCollector(ns, gcSupport, false, false, true, 
3, 0.0, 100, 1000, 86400, 2);
+
+        // no database query if generation is default value.
+        Mockito.verifyNoInteractions(ds);
+    }
+
+    @Test
+    public void testResetFullGcIfGenChangeWithDefaultValue() {
+        // Setup: ensure no settings document exists
+        Mockito.when(ds.find(SETTINGS, 
SETTINGS_COLLECTION_ID)).thenReturn(null);
+
+        // Execute
+        vgc = new VersionGarbageCollector(ns, gcSupport, true, false, true, 3, 
0.0, 100, 1000, 86400, 0);
+
+        // no database query if generation is default value.
+        Mockito.verifyNoInteractions(ds);
+    }
+
+    @Test
+    public void testResetFullGcIfGenChangeWithNoDocument() {
+        // Setup: ensure no settings document exists
+        Mockito.when(ds.find(SETTINGS, 
SETTINGS_COLLECTION_ID)).thenReturn(null);
+
+        // Execute
+        vgc = new VersionGarbageCollector(ns, gcSupport, true, false, true, 3, 
0.0, 100, 1000, 86400, fullGcGen);
+
+        Mockito.verify(ds, Mockito.times(1)).find(SETTINGS, 
SETTINGS_COLLECTION_ID);
+        Mockito.verify(ds, 
Mockito.times(1)).createOrUpdate(Mockito.eq(SETTINGS), (UpdateOp) 
Mockito.any());
+        // verify no calls to specific methods
+        Mockito.verify(ds, Mockito.never()).remove(Mockito.any(), (String) 
Mockito.any());
+        Mockito.verify(ds, Mockito.never()).findAndUpdate(Mockito.any(), 
(UpdateOp) Mockito.any());
+    }
+
+    @Test
+    public void testResetFullGcIfGenChangeWithEmptyGeneration() {
+        // Setup: document exists but has non-numeric generation value
+        final Document doc = Mockito.mock(Document.class);
+        Mockito.when(ds.find(SETTINGS, 
SETTINGS_COLLECTION_ID)).thenReturn(doc);
+        
Mockito.when(doc.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP)).thenReturn(null);
+
+        // Execute
+        vgc = new VersionGarbageCollector(ns, gcSupport, true, false, true, 3, 
0.0, 100, 1000, 86400, fullGcGen);
+
+        // Verify: logs warning and persists new value
+        Mockito.verify(ds).find(SETTINGS, SETTINGS_COLLECTION_ID);
+        Mockito.verify(ds, Mockito.times(1)).findAndUpdate(Mockito.any(), 
(UpdateOp) Mockito.any());
+        Mockito.verify(ds).createOrUpdate(Mockito.eq(SETTINGS), (UpdateOp) 
Mockito.any());
+
+        // verify no calls to specific methods
+        Mockito.verify(ds, Mockito.never()).remove(Mockito.any(), (String) 
Mockito.any());
+    }
+
+    @Test
+    public void testResetFullGcIfGenChangeWithNonNumberGeneration() {
+        // Setup: document exists but has non-numeric generation value
+        final Document doc = Mockito.mock(Document.class);
+        Mockito.when(ds.find(SETTINGS, 
SETTINGS_COLLECTION_ID)).thenReturn(doc);
+        
Mockito.when(doc.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP)).thenReturn("not-a-number");
+
+        // Execute
+        vgc = new VersionGarbageCollector(ns, gcSupport, true, false, true, 3, 
0.0, 100, 1000, 86400, fullGcGen);
+
+        // Verify: logs warning and persists new value
+        Mockito.verify(ds).find(SETTINGS, SETTINGS_COLLECTION_ID);
+        Mockito.verify(ds, Mockito.times(1)).findAndUpdate(Mockito.any(), 
(UpdateOp) Mockito.any());
+        Mockito.verify(ds).createOrUpdate(Mockito.eq(SETTINGS), (UpdateOp) 
Mockito.any());
+
+        // verify no calls to specific methods
+        Mockito.verify(ds, Mockito.never()).remove(Mockito.any(), (String) 
Mockito.any());
+    }
+
+    @Test
+    public void testResetFullGcIfGenChangeWithLowerGeneration() {
+        // Setup: document exists with higher generation
+        final Document doc = Mockito.mock(Document.class);
+        Mockito.when(ds.find(SETTINGS, 
SETTINGS_COLLECTION_ID)).thenReturn(doc);
+        
Mockito.when(doc.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP)).thenReturn(5);
+
+        // Execute
+        vgc = new VersionGarbageCollector(ns, gcSupport, true, false, true, 3, 
0.0, 100, 1000, 86400, fullGcGen);
+
+        // Verify: logs warning and persists new value
+        Mockito.verify(ds).find(SETTINGS, SETTINGS_COLLECTION_ID);
+        Mockito.verify(ds, 
Mockito.never()).createOrUpdate(Mockito.eq(SETTINGS), (UpdateOp) Mockito.any());
+
+        // verify no calls to specific methods
+        Mockito.verify(ds, Mockito.never()).remove(Mockito.any(), (String) 
Mockito.any());
+        Mockito.verify(ds, Mockito.never()).findAndUpdate(Mockito.any(), 
(UpdateOp) Mockito.any());
+    }
+
+    @Test
+    public void testResetFullGcIfGenChangeWithHigherGeneration() {
+        // Setup: document exists with lower generation
+        final Document doc = Mockito.mock(Document.class);
+        Mockito.when(ds.find(SETTINGS, 
SETTINGS_COLLECTION_ID)).thenReturn(doc);
+        
Mockito.when(doc.get(SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP)).thenReturn(1);
+
+        // Execute
+        vgc = new VersionGarbageCollector(ns, gcSupport, true, false, true, 3, 
0.0, 100, 1000, 86400, fullGcGen);
+
+        // Verify: logs warning and persists new value
+        Mockito.verify(ds, Mockito.times(1)).find(SETTINGS, 
SETTINGS_COLLECTION_ID);
+        Mockito.verify(ds, 
Mockito.times(1)).findAndUpdate(Mockito.eq(SETTINGS), (UpdateOp) Mockito.any());
+        Mockito.verify(ds, 
Mockito.times(1)).createOrUpdate(Mockito.eq(SETTINGS), (UpdateOp) 
Mockito.any());
+
+        // verify no calls to specific methods
+        Mockito.verify(ds, Mockito.never()).remove(Mockito.any(), (String) 
Mockito.any());
+    }
+
+}
\ No newline at end of file
diff --git 
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentNodeStoreBuilderTest.java
 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentNodeStoreBuilderTest.java
index d900d10f71..340d62ca6b 100644
--- 
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentNodeStoreBuilderTest.java
+++ 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentNodeStoreBuilderTest.java
@@ -130,7 +130,22 @@ public class MongoDocumentNodeStoreBuilderTest {
     public void fullGCModeDefaultValue() {
         MongoDocumentNodeStoreBuilder builder = new 
MongoDocumentNodeStoreBuilder();
         final int fullGcModeNone = 0;
-        assertEquals(builder.getFullGCMode(), fullGcModeNone);
+        assertEquals(fullGcModeNone, builder.getFullGCMode());
+    }
+
+    @Test
+    public void fullGCGenerationDefaultValue() {
+        MongoDocumentNodeStoreBuilder builder = new 
MongoDocumentNodeStoreBuilder();
+        final long fullGcGeneration = 0;
+        assertEquals(fullGcGeneration, builder.getFullGCGeneration());
+    }
+
+    @Test
+    public void fullGCGenerationSetValue() {
+        MongoDocumentNodeStoreBuilder builder = new 
MongoDocumentNodeStoreBuilder();
+        final long fullGcGeneration = 3;
+        builder.setFullGCGeneration(fullGcGeneration);
+        assertEquals(fullGcGeneration, builder.getFullGCGeneration());
     }
 
     @Test
diff --git 
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentNodeStoreBuilderTest.java
 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentNodeStoreBuilderTest.java
index 642b05e20e..fa0f2479e9 100755
--- 
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentNodeStoreBuilderTest.java
+++ 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentNodeStoreBuilderTest.java
@@ -115,6 +115,13 @@ public class RDBDocumentNodeStoreBuilderTest {
         assertEquals(0, builder.getFullGCMode());
     }
 
+    @Test
+    public void fullGCGenerationHasDefaultValue() {
+        RDBDocumentNodeStoreBuilder builder = new 
RDBDocumentNodeStoreBuilder();
+        builder.setFullGCGeneration(3);
+        assertEquals(0, builder.getFullGCGeneration());
+    }
+
     @Test
     public void fullGcMaxAgeInSecsHasDefaultValue() {
         RDBDocumentNodeStoreBuilder builder = new 
RDBDocumentNodeStoreBuilder();
diff --git 
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/UtilsTest.java
 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/UtilsTest.java
index 71ba70e2ad..2ec1de9e3d 100644
--- 
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/UtilsTest.java
+++ 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/UtilsTest.java
@@ -241,7 +241,15 @@ public class UtilsTest {
         DocumentNodeStoreBuilder<?> builder = newDocumentNodeStoreBuilder();
         int fullGCModeDefaultValue = builder.getFullGCMode();
         final int fullGcModeNone = 0;
-        assertEquals("Full GC mode has NONE value by default", 
fullGCModeDefaultValue, fullGcModeNone);
+        assertEquals("Full GC mode has NONE value by default", fullGcModeNone, 
fullGCModeDefaultValue);
+    }
+
+    @Test
+    public void fullGCGenerationDefaultValue() {
+        DocumentNodeStoreBuilder<?> builder = newDocumentNodeStoreBuilder();
+        long fullGCGenerationDefaultValue = builder.getFullGCGeneration();
+        final long fullGcgeneration = 0;
+        assertEquals("Full GC generation has 0 value by default", 
fullGcgeneration, fullGCGenerationDefaultValue);
     }
 
     @Test
@@ -250,7 +258,16 @@ public class UtilsTest {
         final int fullGcModeGapOrphans = 2;
         builder.setFullGCMode(fullGcModeGapOrphans);
         int fullGCModeValue = builder.getFullGCMode();
-        assertEquals("Full GC mode set correctly via configuration", 
fullGCModeValue, fullGcModeGapOrphans);
+        assertEquals("Full GC mode set correctly via configuration", 
fullGcModeGapOrphans, fullGCModeValue);
+    }
+
+    @Test
+    public void fullGCGenerationSetViaConfiguration() {
+        DocumentNodeStoreBuilder<?> builder = newDocumentNodeStoreBuilder();
+        final long fullGcGeneration = 2;
+        builder.setFullGCGeneration(fullGcGeneration);
+        long fullGCGenerationValue = builder.getFullGCGeneration();
+        assertEquals("Full GC generation set correctly via configuration", 
fullGcGeneration, fullGCGenerationValue);
     }
 
     @Test
@@ -258,7 +275,15 @@ public class UtilsTest {
         DocumentNodeStoreBuilder<?> builder = newRDBDocumentNodeStoreBuilder();
         builder.setFullGCMode(3);
         int fullGCModeValue = builder.getFullGCMode();
-        assertEquals("Full GC mode has default value 0 for RDB Document 
Store", fullGCModeValue, 0);
+        assertEquals("Full GC mode has default value 0 for RDB Document 
Store", 0, fullGCModeValue);
+    }
+
+    @Test
+    public void fullGCGenerationHasDefaultValueForRDB() {
+        DocumentNodeStoreBuilder<?> builder = newRDBDocumentNodeStoreBuilder();
+        builder.setFullGCGeneration(3);
+        long fullGCGenerationValue = builder.getFullGCGeneration();
+        assertEquals("Full GC generation has default value 0 for RDB Document 
Store", 0, fullGCGenerationValue);
     }
 
     @Test

Reply via email to