@@ -60,17 +60,22 @@ func (c *inspectResumer) Resume(ctx context.Context, execCtx interface{}) error
60
60
return err
61
61
}
62
62
63
- // TODO(149460): add a goroutine that will replan the job on topology changes
64
- plan , planCtx , err := c .planInspectProcessors (ctx , jobExecCtx , pkSpans )
63
+ progressTracker , cleanupProgress , remainingSpans , err := c .setupProgressTrackingAndFilter (ctx , execCfg , pkSpans )
65
64
if err != nil {
66
65
return err
67
66
}
67
+ defer cleanupProgress ()
68
+
69
+ // If all spans are completed, job is done.
70
+ if len (remainingSpans ) == 0 {
71
+ log .Dev .Infof (ctx , "all spans already completed, INSPECT job finished" )
72
+ return nil
73
+ }
68
74
69
- progressTracker , cleanupProgress , err := c .setupProgressTracking (ctx , execCfg , pkSpans )
75
+ plan , planCtx , err := c .planInspectProcessors (ctx , jobExecCtx , remainingSpans )
70
76
if err != nil {
71
77
return err
72
78
}
73
- defer cleanupProgress ()
74
79
75
80
if err := c .runInspectPlan (ctx , jobExecCtx , planCtx , plan , progressTracker ); err != nil {
76
81
return err
@@ -202,7 +207,7 @@ func (c *inspectResumer) runInspectPlan(
202
207
metadataCallbackWriter := sql .NewMetadataOnlyMetadataCallbackWriter (
203
208
func (ctx context.Context , meta * execinfrapb.ProducerMetadata ) error {
204
209
if meta .BulkProcessorProgress != nil {
205
- return progressTracker .handleProcessorProgress (ctx , meta )
210
+ return progressTracker .handleProgressUpdate (ctx , meta )
206
211
}
207
212
return nil
208
213
})
@@ -228,43 +233,71 @@ func (c *inspectResumer) runInspectPlan(
228
233
return metadataCallbackWriter .Err ()
229
234
}
230
235
231
- // setupProgressTracking initializes and starts progress tracking for the INSPECT job.
232
- // It calculates the total number of applicable checks, initializes progress to 0%, and starts
233
- // the background update goroutine. Returns the progress tracker and cleanup function.
234
- func (c * inspectResumer ) setupProgressTracking (
236
+ // setupProgressTrackingAndFilter initializes progress tracking and returns
237
+ // it, along with a cleanup function and the remaining spans to process.
238
+ func (c * inspectResumer ) setupProgressTrackingAndFilter (
235
239
ctx context.Context , execCfg * sql.ExecutorConfig , pkSpans []roachpb.Span ,
236
- ) (* inspectProgressTracker , func (), error ) {
237
- fractionInterval := fractionUpdateInterval .Get (& execCfg .Settings .SV )
238
- details := c .job .Details ().(jobspb.InspectDetails )
240
+ ) (* inspectProgressTracker , func (), []roachpb.Span , error ) {
241
+ // Create and initialize the tracker. We use the completed spans from the job
242
+ // (if any) to filter out the spans we need to process in this run of the job.
243
+ progressTracker := newInspectProgressTracker (
244
+ c .job ,
245
+ & execCfg .Settings .SV ,
246
+ execCfg .InternalDB ,
247
+ )
248
+ completedSpans , err := progressTracker .initTracker (ctx )
249
+ if err != nil {
250
+ return nil , nil , nil , err
251
+ }
252
+ remainingSpans := c .filterCompletedSpans (pkSpans , completedSpans )
239
253
240
- // Build applicability checkers for progress tracking
241
- applicabilityCheckers , err := buildApplicabilityCheckers (details )
254
+ applicabilityCheckers , err := buildApplicabilityCheckers (c .job .Details ().(jobspb.InspectDetails ))
242
255
if err != nil {
243
- return nil , nil , err
256
+ return nil , nil , nil , err
244
257
}
245
258
246
- // Calculate total applicable checks: only count checks that will actually run
259
+ // Calculate total applicable checks on ALL spans (not just remaining ones)
260
+ // This ensures consistent progress calculation across job restarts.
247
261
totalCheckCount , err := countApplicableChecks (pkSpans , applicabilityCheckers , execCfg .Codec )
248
262
if err != nil {
249
- return nil , nil , err
263
+ return nil , nil , nil , err
264
+ }
265
+ completedCheckCount , err := countApplicableChecks (completedSpans , applicabilityCheckers , execCfg .Codec )
266
+ if err != nil {
267
+ return nil , nil , nil , err
250
268
}
251
269
252
- // Create and initialize progress tracker
253
- progressTracker := newInspectProgressTracker ( c . job , fractionInterval )
254
- if err := progressTracker . initJobProgress ( ctx , totalCheckCount ); err != nil {
255
- progressTracker . terminateTracker ( ctx )
256
- return nil , nil , err
270
+ if err := progressTracker . initJobProgress ( ctx , totalCheckCount , completedCheckCount ); err != nil {
271
+ return nil , nil , nil , err
272
+ }
273
+ cleanup := func () {
274
+ progressTracker . terminateTracker ()
257
275
}
258
276
259
- // Start background progress updates
260
- progressTracker . startBackgroundUpdates ( ctx )
277
+ return progressTracker , cleanup , remainingSpans , nil
278
+ }
261
279
262
- // Return cleanup function
263
- cleanup := func () {
264
- progressTracker .terminateTracker (ctx )
280
+ // filterCompletedSpans removes spans that are already completed from the list to process.
281
+ func (c * inspectResumer ) filterCompletedSpans (
282
+ allSpans []roachpb.Span , completedSpans []roachpb.Span ,
283
+ ) []roachpb.Span {
284
+ if len (completedSpans ) == 0 {
285
+ return allSpans
286
+ }
287
+
288
+ completedGroup := roachpb.SpanGroup {}
289
+ completedGroup .Add (completedSpans ... )
290
+
291
+ var remainingSpans []roachpb.Span
292
+ for _ , span := range allSpans {
293
+ // Check if this span is fully contained in completed spans.
294
+ // We need to check if the entire span is covered by completed spans.
295
+ if ! completedGroup .Encloses (span ) {
296
+ remainingSpans = append (remainingSpans , span )
297
+ }
265
298
}
266
299
267
- return progressTracker , cleanup , nil
300
+ return remainingSpans
268
301
}
269
302
270
303
// buildApplicabilityCheckers creates lightweight applicability checkers from InspectDetails.
0 commit comments