@@ -27,14 +27,23 @@ import (
2727// runFunctionStartedCallbackFunc is a test seam called at the top of Run before any compaction phases execute.
2828type runFunctionStartedCallbackFunc func (context.Context , map [string ]any , updateStatusCallbackFunc , * base.SafeTerminator )
2929
30+ // AttachmentCompactionManager implements the attachment compaction background process. Compaction
31+ // runs in three sequential phases — mark, sweep, cleanup.
32+ //
33+ // Fields _compactID, _phase, _vbuuids, _dryRun are protected by lock.
3034type AttachmentCompactionManager struct {
31- MarkedAttachments base.AtomicInt
32- PurgedAttachments base.AtomicInt
33- CompactID string
34- Phase string
35- VBUUIDs []uint64
36- dryRun bool
37- lock sync.Mutex
35+ // MarkedAttachments counts attachments marked as live during the mark phase.
36+ MarkedAttachments base.AtomicInt
37+ // PurgedAttachments counts attachments deleted during the sweep phase.
38+ PurgedAttachments base.AtomicInt
39+
40+ // The following fields are protected by lock and must be accessed via their getters/setters.
41+ _compactID string // unique identifier for the current compaction run, used to tag marked attachments
42+ _phase string // current phase: "mark", "sweep", "cleanup", or "" when idle
43+ _vbuuids []uint64 // vBucket UUIDs captured after the mark phase, used to detect DCP rollbacks in cleanup
44+ _dryRun bool // when true, sweep phase reports but does not delete attachments
45+
46+ lock sync.RWMutex
3847 runFunctionStartedCallback atomic.Pointer [runFunctionStartedCallbackFunc ]
3948}
4049
@@ -68,9 +77,9 @@ func (a *AttachmentCompactionManager) Init(ctx context.Context, options map[stri
6877 base .InfofCtx (ctx , base .KeyAll , "Attachment Compaction: Running as dry run. No attachments will be purged" )
6978 }
7079
71- a . dryRun = dryRun
72- a .CompactID = uniqueUUID . String ( )
73- base .InfofCtx (ctx , base .KeyAll , "Attachment Compaction: Starting new compaction run with compact ID: %q" , a . CompactID )
80+ compactID := uniqueUUID . String ()
81+ a .initializeNewRun ( compactID , dryRun )
82+ base .InfofCtx (ctx , base .KeyAll , "Attachment Compaction: Starting new compaction run with compact ID: %q" , compactID )
7483 return nil
7584 }
7685
@@ -90,14 +99,8 @@ func (a *AttachmentCompactionManager) Init(ctx context.Context, options map[stri
9099 if statusDoc .State == BackgroundProcessStateCompleted || err != nil || (reset && ok ) {
91100 return backgroundManagerInitReset , newRunInit ()
92101 } else {
93- a .CompactID = statusDoc .CompactID
94- a .Phase = statusDoc .Phase
95- a .dryRun = statusDoc .DryRun
96- a .MarkedAttachments .Set (statusDoc .MarkedAttachments )
97- a .PurgedAttachments .Set (statusDoc .PurgedAttachments )
98- a .VBUUIDs = statusDoc .VBUUIDs
99-
100- base .InfofCtx (ctx , base .KeyAll , "Attachment Compaction: Attempting to resume compaction with compact ID: %q phase %q" , a .CompactID , a .Phase )
102+ compactID , phase := a .initializeFromPreviousStatus (statusDoc )
103+ base .InfofCtx (ctx , base .KeyAll , "Attachment Compaction: Attempting to resume compaction with compact ID: %q phase %q" , compactID , phase )
101104 }
102105
103106 return backgroundManagerInitResume , nil
@@ -136,14 +139,14 @@ func (a *AttachmentCompactionManager) Run(ctx context.Context, options map[strin
136139 // Need to check the current phase in the event we are resuming - No need to run mark again if we got as far as
137140 // cleanup last time...
138141 var err error
139- switch a .Phase {
142+ switch a .getPhase () {
140143 case "mark" , "" :
141144 a .SetPhase ("mark" )
142145 worker := func () (shouldRetry bool , err error , value any ) {
143146 persistClusterStatus ()
144- _ , dcpClient , err := attachmentCompactMarkPhase (ctx , dataStore , collectionID , database , a .CompactID , terminator , & a .MarkedAttachments )
147+ _ , dcpClient , err := attachmentCompactMarkPhase (ctx , dataStore , collectionID , database , a .getCompactID () , terminator , & a .MarkedAttachments )
145148 if dcpClient != nil {
146- a .VBUUIDs = base .GetVBUUIDs (dcpClient .GetMetadata ())
149+ a .setVBUUIDs ( base .GetVBUUIDs (dcpClient .GetMetadata () ))
147150 }
148151 if err != nil {
149152 // if dcpClient is nil, then dcpClient.GetMetadataKeyPrefix() will panic. This isn't a rollback
@@ -169,7 +172,7 @@ func (a *AttachmentCompactionManager) Run(ctx context.Context, options map[strin
169172 case "sweep" :
170173 a .SetPhase ("sweep" )
171174 persistClusterStatus ()
172- _ , _ , err := attachmentCompactSweepPhase (ctx , dataStore , collectionID , database , a .CompactID , a .VBUUIDs , a .dryRun , terminator , & a .PurgedAttachments )
175+ _ , _ , err := attachmentCompactSweepPhase (ctx , dataStore , collectionID , database , a .getCompactID () , a .getVBUUIDs () , a .getDryRun () , terminator , & a .PurgedAttachments )
173176 if err != nil || terminator .IsClosed () {
174177 return err
175178 }
@@ -178,7 +181,7 @@ func (a *AttachmentCompactionManager) Run(ctx context.Context, options map[strin
178181 a .SetPhase ("cleanup" )
179182 worker := func () (shouldRetry bool , err error , value any ) {
180183 persistClusterStatus ()
181- metadataKeyPrefix , err := attachmentCompactCleanupPhase (ctx , dataStore , collectionID , database , a .CompactID , a .VBUUIDs , terminator )
184+ metadataKeyPrefix , err := attachmentCompactCleanupPhase (ctx , dataStore , collectionID , database , a .getCompactID () , a .getVBUUIDs () , terminator )
182185 if err != nil {
183186 shouldRetry , err = a .handleAttachmentCompactionRollbackError (ctx , options , dataStore , database , err , CleanupPhase , metadataKeyPrefix )
184187 }
@@ -214,7 +217,7 @@ func (a *AttachmentCompactionManager) handleAttachmentCompactionRollbackError(ct
214217 if errors .As (err , & rollbackErr ) || errors .Is (err , base .ErrVbUUIDMismatch ) {
215218 base .InfofCtx (ctx , base .KeyDCP , "rollback indicated on %s phase of attachment compaction, resetting the task" , phase )
216219 // to rollback any phase for attachment compaction we need to purge all persisted dcp metadata
217- base .InfofCtx (ctx , base .KeyDCP , "Purging invalid checkpoints for background task run %s" , a .CompactID )
220+ base .InfofCtx (ctx , base .KeyDCP , "Purging invalid checkpoints for background task run %s" , a .getCompactID () )
218221 err = a .purgeCheckpoints (ctx , database , keyPrefix )
219222 if err != nil {
220223 base .WarnfCtx (ctx , "error occurred during purging of dcp metadata: %s" , err )
@@ -231,7 +234,7 @@ func (a *AttachmentCompactionManager) handleAttachmentCompactionRollbackError(ct
231234 // we only handle rollback for mark and cleanup so if we call here it will be for cleanup phase
232235 // we need to clear the vbUUID's on the manager for cleanup phase otherwise we will end up in loop of constant rollback
233236 // as these are used for the initial metadata on the client
234- a .VBUUIDs = nil
237+ a .setVBUUIDs ( nil )
235238 }
236239 // we should try again if it is rollback error
237240 return true , nil
@@ -244,7 +247,69 @@ func (a *AttachmentCompactionManager) SetPhase(phase string) {
244247 a .lock .Lock ()
245248 defer a .lock .Unlock ()
246249
247- a .Phase = phase
250+ a ._phase = phase
251+ }
252+
253+ // setVBUUIDs updates the VBUUIDs stored on the manager.
254+ func (a * AttachmentCompactionManager ) setVBUUIDs (vbuuids []uint64 ) {
255+ a .lock .Lock ()
256+ defer a .lock .Unlock ()
257+
258+ a ._vbuuids = vbuuids
259+ }
260+
261+ // initializeNewRun sets the compactID and dryRun fields for a new compaction run.
262+ func (a * AttachmentCompactionManager ) initializeNewRun (compactID string , dryRun bool ) {
263+ a .lock .Lock ()
264+ defer a .lock .Unlock ()
265+
266+ a ._compactID = compactID
267+ a ._dryRun = dryRun
268+ }
269+
270+ // initializeFromPreviousStatus restores in-memory state from a previously persisted status document
271+ // so that a resumed run starts with the correct accumulated counts and identifiers.
272+ // Returns the compactID and phase.
273+ func (a * AttachmentCompactionManager ) initializeFromPreviousStatus (statusDoc AttachmentManagerStatusDoc ) (compactID , phase string ) {
274+ a .lock .Lock ()
275+ defer a .lock .Unlock ()
276+
277+ a ._compactID = statusDoc .CompactID
278+ a ._phase = statusDoc .Phase
279+ a ._dryRun = statusDoc .DryRun
280+ a .MarkedAttachments .Set (statusDoc .MarkedAttachments )
281+ a .PurgedAttachments .Set (statusDoc .PurgedAttachments )
282+ a ._vbuuids = statusDoc .VBUUIDs
283+ return a ._compactID , a ._phase
284+ }
285+
286+ // getCompactID returns the unique identifier for the current compaction run.
287+ func (a * AttachmentCompactionManager ) getCompactID () string {
288+ a .lock .RLock ()
289+ defer a .lock .RUnlock ()
290+ return a ._compactID
291+ }
292+
293+ // getPhase returns the current compaction phase ("mark", "sweep", "cleanup", or "" when idle).
294+ func (a * AttachmentCompactionManager ) getPhase () string {
295+ a .lock .RLock ()
296+ defer a .lock .RUnlock ()
297+ return a ._phase
298+ }
299+
300+ // getDryRun returns whether the compaction is running in dry-run mode, where attachments are
301+ // identified but not deleted.
302+ func (a * AttachmentCompactionManager ) getDryRun () bool {
303+ a .lock .RLock ()
304+ defer a .lock .RUnlock ()
305+ return a ._dryRun
306+ }
307+
308+ // getVBUUIDs returns the vBucket UUIDs recorded after the mark phase.
309+ func (a * AttachmentCompactionManager ) getVBUUIDs () []uint64 {
310+ a .lock .RLock ()
311+ defer a .lock .RUnlock ()
312+ return a ._vbuuids
248313}
249314
250315type AttachmentManagerResponse struct {
@@ -268,20 +333,20 @@ type AttachmentManagerStatusDoc struct {
268333func (a * AttachmentCompactionManager ) SetProcessStatus (context.Context , []byte , []byte ) {}
269334
270335func (a * AttachmentCompactionManager ) GetProcessStatus (status BackgroundManagerStatus , _ []byte ) ([]byte , []byte , error ) {
271- a .lock .Lock ()
272- defer a .lock .Unlock ()
336+ a .lock .RLock ()
337+ defer a .lock .RUnlock ()
273338
274339 response := AttachmentManagerResponse {
275340 BackgroundManagerStatus : status ,
276341 MarkedAttachments : a .MarkedAttachments .Value (),
277342 PurgedAttachments : a .PurgedAttachments .Value (),
278- CompactID : a .CompactID ,
279- Phase : a .Phase ,
280- DryRun : a .dryRun ,
343+ CompactID : a ._compactID ,
344+ Phase : a ._phase ,
345+ DryRun : a ._dryRun ,
281346 }
282347
283348 meta := AttachmentManagerMeta {
284- VBUUIDs : a .VBUUIDs ,
349+ VBUUIDs : a ._vbuuids ,
285350 }
286351
287352 statusJSON , err := base .JSONMarshal (response )
@@ -303,5 +368,5 @@ func (a *AttachmentCompactionManager) ResetStatus() {
303368
304369 a .MarkedAttachments .Set (0 )
305370 a .PurgedAttachments .Set (0 )
306- a .dryRun = false
371+ a ._dryRun = false
307372}
0 commit comments