8383import static org .apache .jackrabbit .oak .plugins .document .Document .ID ;
8484import static org .apache .jackrabbit .oak .plugins .document .DocumentNodeStoreService .DEFAULT_FGC_BATCH_SIZE ;
8585import static org .apache .jackrabbit .oak .plugins .document .DocumentNodeStoreService .DEFAULT_FGC_PROGRESS_SIZE ;
86+ import static org .apache .jackrabbit .oak .plugins .document .DocumentNodeStoreService .DEFAULT_FULL_GC_GENERATION ;
8687import static org .apache .jackrabbit .oak .plugins .document .DocumentNodeStoreService .DEFAULT_FULL_GC_MAX_AGE ;
8788import static org .apache .jackrabbit .oak .plugins .document .DocumentNodeStoreService .DEFAULT_FULL_GC_MODE ;
8889import static org .apache .jackrabbit .oak .plugins .document .NodeDocument .BRANCH_COMMITS ;
8990import static org .apache .jackrabbit .oak .plugins .document .NodeDocument .COLLISIONS ;
9091import static org .apache .jackrabbit .oak .plugins .document .NodeDocument .COMMIT_ROOT ;
92+ import static org .apache .jackrabbit .oak .plugins .document .NodeDocument .LOG ;
9193import static org .apache .jackrabbit .oak .plugins .document .NodeDocument .MIN_ID_VALUE ;
9294import static org .apache .jackrabbit .oak .plugins .document .NodeDocument .MODIFIED_IN_SECS ;
9395import static org .apache .jackrabbit .oak .plugins .document .NodeDocument .REVISIONS ;
@@ -153,6 +155,11 @@ public class VersionGarbageCollector {
153155 */
154156 static final String SETTINGS_COLLECTION_FULL_GC_DRY_RUN_TIMESTAMP_PROP = "fullGCDryRunTimeStamp" ;
155157
158+ /**
159+ * Property name to fullGcGeneration which is currently running
160+ */
161+ static final String SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP = "fullGCGeneration" ;
162+
156163 /**
157164 * Property name to _id till when last full-GC run happened in dryRun mode only
158165 */
@@ -174,6 +181,78 @@ static void setFullGcMode(int fullGcMode) {
174181 VersionGarbageCollector .fullGcMode = FullGCMode .getMode (fullGcMode );
175182 }
176183
184+ /**
185+ * Sets the full GC generation for this document store and performs a reset if needed.
186+ * <p>
187+ * This method checks the existing full GC generation stored in the settings document:
188+ * <ul>
189+ * <li>If no document exists, the new generation value is persisted</li>
190+ * <li>If the previous generation isn't a number or null, resets full GC and persists the new value</li>
191+ * <li>If the new generation is higher than the previous one, resets full GC and updates the value</li>
192+ * <li>If the new generation is less than or equal to the previous one, only logs the information</li>
193+ * </ul>
194+ *
195+ * @param fullGcGen The new full GC generation value to set
196+ * @return the generation with which the full GC has started
197+ */
198+ long resetFullGcIfGenChange (final long fullGcGen ) {
199+
200+ if (fullGcGen == DEFAULT_FULL_GC_GENERATION ) {
201+ // generation hasn't been set yet, no need to make any change to make this backward compatible
202+ LOG .info ("Full GC generation is set to default value {}. No action needed." , fullGcGen );
203+ return fullGcGen ;
204+ }
205+
206+ final Document doc = ds .find (SETTINGS , SETTINGS_COLLECTION_ID );
207+
208+ if (doc == null ) {
209+ // No version gc document exists, must be a new environment
210+ persistFullGcGen (fullGcGen );
211+ return fullGcGen ;
212+ }
213+
214+ final Object prevFullGcGenObj = doc .get (SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP );
215+
216+ // If no previous generation or not a Number, just persist the new value
217+ if (!(prevFullGcGenObj instanceof Number )) {
218+ // this could happen if the previous value was set to a non-numeric value i.e. (not present)
219+ LOG .info ("Full GC generation {} is not a valid number or null, resetting to {}." , prevFullGcGenObj , fullGcGen );
220+ resetFullGC ();
221+ persistFullGcGen (fullGcGen );
222+ return fullGcGen ;
223+ }
224+
225+ // Compare with the previous generation
226+ long prevFullGcGen = ((Number ) prevFullGcGenObj ).longValue ();
227+ if (prevFullGcGen >= fullGcGen ) {
228+ LOG .info ("Full GC generation {} is less than or equal to the previously saved value {}." , fullGcGen , prevFullGcGen );
229+ return prevFullGcGen ;
230+ } else {
231+ LOG .info ("Found a new generation of FullGC {}, resetting the Old gen {} values." , fullGcGen , prevFullGcGen );
232+ resetFullGC ();
233+ persistFullGcGen (fullGcGen );
234+ return fullGcGen ;
235+ }
236+ }
237+
238+ /**
239+ * Persists the full garbage collection generation value to the settings document.
240+ * <p>
241+ * This method creates or updates a document in the settings collection with the
242+ * specified full GC generation number. The generation value is used to track major
243+ * changes in the garbage collection process across restarts or different cluster nodes.
244+ * <p>
245+ * When the system detects a higher generation number than previously stored, it will
246+ * reset the full GC state before persisting the new generation value.
247+ *
248+ * @param fullGcGeneration The full garbage collection generation value to persist
249+ */
250+ private void persistFullGcGen (long fullGcGeneration ) {
251+ UpdateOp op = new UpdateOp (SETTINGS_COLLECTION_ID , true );
252+ op .set (SETTINGS_COLLECTION_FULL_GC_GENERATION_PROP , fullGcGeneration );
253+ ds .createOrUpdate (SETTINGS , op );
254+ }
255+
177256 private final DocumentNodeStore nodeStore ;
178257 private final DocumentStore ds ;
179258 private final boolean fullGCEnabled ;
@@ -198,7 +277,7 @@ static void setFullGcMode(int fullGcMode) {
198277 final boolean isFullGCDryRun ,
199278 final boolean embeddedVerification ) {
200279 this (nodeStore , gcSupport , fullGCEnabled , isFullGCDryRun , embeddedVerification , DEFAULT_FULL_GC_MODE ,
201- 0 , DEFAULT_FGC_BATCH_SIZE , DEFAULT_FGC_PROGRESS_SIZE , SECONDS .toMillis (DEFAULT_FULL_GC_MAX_AGE ));
280+ 0 , DEFAULT_FGC_BATCH_SIZE , DEFAULT_FGC_PROGRESS_SIZE , SECONDS .toMillis (DEFAULT_FULL_GC_MAX_AGE ), 0 );
202281 }
203282
204283 VersionGarbageCollector (DocumentNodeStore nodeStore ,
@@ -210,7 +289,8 @@ static void setFullGcMode(int fullGcMode) {
210289 final double fullGCDelayFactor ,
211290 final int fullGCBatchSize ,
212291 final int fullGCProgressSize ,
213- final long fullGcMaxAgeInMillis ) {
292+ final long fullGcMaxAgeInMillis ,
293+ final long fullGcGeneration ) {
214294 this .nodeStore = nodeStore ;
215295 this .versionStore = gcSupport ;
216296 this .ds = gcSupport .getDocumentStore ();
@@ -224,8 +304,9 @@ static void setFullGcMode(int fullGcMode) {
224304 this .options = new VersionGCOptions ();
225305
226306 setFullGcMode (fullGCMode );
227- AUDIT_LOG .info ("<init> VersionGarbageCollector created with fullGcMode: {}, maxFullGcAgeInMillis: {}, batchSize: {}, progressSize: {}, delayFactor: {}" ,
228- fullGcMode , fullGcMaxAgeInMillis , fullGCBatchSize , fullGCProgressSize , fullGCDelayFactor );
307+ long fullGcGen = fullGCEnabled ? resetFullGcIfGenChange (fullGcGeneration ) : fullGcGeneration ;
308+ AUDIT_LOG .info ("<init> VersionGarbageCollector created with fullGcMode: {}, maxFullGcAgeInMillis: {}, batchSize: {}, progressSize: {}, delayFactor: {}, fullGcGeneration: {}" ,
309+ fullGcMode , fullGcMaxAgeInMillis , fullGCBatchSize , fullGCProgressSize , fullGCDelayFactor , fullGcGen );
229310 }
230311
231312 /**
0 commit comments