@@ -60,16 +60,22 @@ func (c *inspectResumer) Resume(ctx context.Context, execCtx interface{}) error
6060 return err
6161 }
6262
63- plan , planCtx , err := c .planInspectProcessors (ctx , jobExecCtx , pkSpans )
63+ progressTracker , cleanupProgress , remainingSpans , err := c .setupProgressTrackingAndFilter (ctx , execCfg , pkSpans )
6464 if err != nil {
6565 return err
6666 }
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+ }
6774
68- progressTracker , cleanupProgress , err := c .setupProgressTracking (ctx , execCfg , pkSpans )
75+ plan , planCtx , err := c .planInspectProcessors (ctx , jobExecCtx , remainingSpans )
6976 if err != nil {
7077 return err
7178 }
72- defer cleanupProgress ()
7379
7480 if err := c .runInspectPlan (ctx , jobExecCtx , planCtx , plan , progressTracker ); err != nil {
7581 return err
@@ -201,7 +207,7 @@ func (c *inspectResumer) runInspectPlan(
201207 metadataCallbackWriter := sql .NewMetadataOnlyMetadataCallbackWriter (
202208 func (ctx context.Context , meta * execinfrapb.ProducerMetadata ) error {
203209 if meta .BulkProcessorProgress != nil {
204- return progressTracker .handleProcessorProgress (ctx , meta )
210+ return progressTracker .handleProgressUpdate (ctx , meta )
205211 }
206212 return nil
207213 })
@@ -227,43 +233,71 @@ func (c *inspectResumer) runInspectPlan(
227233 return metadataCallbackWriter .Err ()
228234}
229235
230- // setupProgressTracking initializes and starts progress tracking for the INSPECT job.
231- // It calculates the total number of applicable checks, initializes progress to 0%, and starts
232- // the background update goroutine. Returns the progress tracker and cleanup function.
233- 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 (
234239 ctx context.Context , execCfg * sql.ExecutorConfig , pkSpans []roachpb.Span ,
235- ) (* inspectProgressTracker , func (), error ) {
236- fractionInterval := fractionUpdateInterval .Get (& execCfg .Settings .SV )
237- 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 )
238253
239- // Build applicability checkers for progress tracking
240- applicabilityCheckers , err := buildApplicabilityCheckers (details )
254+ applicabilityCheckers , err := buildApplicabilityCheckers (c .job .Details ().(jobspb.InspectDetails ))
241255 if err != nil {
242- return nil , nil , err
256+ return nil , nil , nil , err
243257 }
244258
245- // 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.
246261 totalCheckCount , err := countApplicableChecks (pkSpans , applicabilityCheckers , execCfg .Codec )
247262 if err != nil {
248- 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
249268 }
250269
251- // Create and initialize progress tracker
252- progressTracker := newInspectProgressTracker ( c . job , fractionInterval )
253- if err := progressTracker . initJobProgress ( ctx , totalCheckCount ); err != nil {
254- progressTracker . terminateTracker ( ctx )
255- 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 ()
256275 }
257276
258- // Start background progress updates
259- progressTracker . startBackgroundUpdates ( ctx )
277+ return progressTracker , cleanup , remainingSpans , nil
278+ }
260279
261- // Return cleanup function
262- cleanup := func () {
263- 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+ }
264298 }
265299
266- return progressTracker , cleanup , nil
300+ return remainingSpans
267301}
268302
269303// buildApplicabilityCheckers creates lightweight applicability checkers from InspectDetails.
0 commit comments