github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/gcjob/gc_job_utils.go (about)

     1  // Copyright 2020 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package gcjob
    12  
    13  import (
    14  	"context"
    15  
    16  	"github.com/cockroachdb/cockroach/pkg/jobs"
    17  	"github.com/cockroachdb/cockroach/pkg/jobs/jobspb"
    18  	"github.com/cockroachdb/cockroach/pkg/kv"
    19  	"github.com/cockroachdb/cockroach/pkg/sql"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    21  	"github.com/cockroachdb/cockroach/pkg/util/log"
    22  	"github.com/cockroachdb/errors"
    23  )
    24  
    25  // markTableGCed updates the job payload details to indicate that the specified
    26  // table was GC'd.
    27  func markTableGCed(
    28  	ctx context.Context, tableID sqlbase.ID, progress *jobspb.SchemaChangeGCProgress,
    29  ) {
    30  	for i := range progress.Tables {
    31  		tableProgress := &progress.Tables[i]
    32  		if tableProgress.ID == tableID {
    33  			tableProgress.Status = jobspb.SchemaChangeGCProgress_DELETED
    34  			if log.V(2) {
    35  				log.Infof(ctx, "determined table %d is GC'd", tableID)
    36  			}
    37  		}
    38  	}
    39  }
    40  
    41  // markIndexGCed marks the index as GC'd.
    42  func markIndexGCed(
    43  	ctx context.Context,
    44  	garbageCollectedIndexID sqlbase.IndexID,
    45  	progress *jobspb.SchemaChangeGCProgress,
    46  ) {
    47  	// Update the job details to remove the dropped indexes.
    48  	for i := range progress.Indexes {
    49  		indexToUpdate := &progress.Indexes[i]
    50  		if indexToUpdate.IndexID == garbageCollectedIndexID {
    51  			indexToUpdate.Status = jobspb.SchemaChangeGCProgress_DELETED
    52  			log.Infof(ctx, "marked index %d as GC'd", garbageCollectedIndexID)
    53  		}
    54  	}
    55  }
    56  
    57  // initDetailsAndProgress sets up the job progress if not already populated and
    58  // validates that the job details is properly formatted.
    59  func initDetailsAndProgress(
    60  	ctx context.Context, execCfg *sql.ExecutorConfig, jobID int64,
    61  ) (*jobspb.SchemaChangeGCDetails, *jobspb.SchemaChangeGCProgress, error) {
    62  	var details jobspb.SchemaChangeGCDetails
    63  	var progress *jobspb.SchemaChangeGCProgress
    64  	var job *jobs.Job
    65  	if err := execCfg.DB.Txn(ctx, func(ctx context.Context, txn *kv.Txn) error {
    66  		var err error
    67  		job, err = execCfg.JobRegistry.LoadJobWithTxn(ctx, jobID, txn)
    68  		if err != nil {
    69  			return err
    70  		}
    71  		details = job.Details().(jobspb.SchemaChangeGCDetails)
    72  		jobProgress := job.Progress()
    73  		progress = jobProgress.GetSchemaChangeGC()
    74  		return nil
    75  	}); err != nil {
    76  		return nil, nil, err
    77  	}
    78  	if err := validateDetails(&details); err != nil {
    79  		return nil, nil, err
    80  	}
    81  	if err := initializeProgress(ctx, execCfg, jobID, &details, progress); err != nil {
    82  		return nil, nil, err
    83  	}
    84  	return &details, progress, nil
    85  }
    86  
    87  // initializeProgress converts the details provided into a progress payload that
    88  // will be updated as the elements that need to be GC'd get processed.
    89  func initializeProgress(
    90  	ctx context.Context,
    91  	execCfg *sql.ExecutorConfig,
    92  	jobID int64,
    93  	details *jobspb.SchemaChangeGCDetails,
    94  	progress *jobspb.SchemaChangeGCProgress,
    95  ) error {
    96  	if len(progress.Tables) != len(details.Tables) || len(progress.Indexes) != len(details.Indexes) {
    97  		for _, table := range details.Tables {
    98  			progress.Tables = append(progress.Tables, jobspb.SchemaChangeGCProgress_TableProgress{ID: table.ID})
    99  		}
   100  		for _, index := range details.Indexes {
   101  			progress.Indexes = append(progress.Indexes, jobspb.SchemaChangeGCProgress_IndexProgress{IndexID: index.IndexID})
   102  		}
   103  
   104  		// Write out new progress.
   105  		if err := execCfg.DB.Txn(ctx, func(ctx context.Context, txn *kv.Txn) error {
   106  			job, err := execCfg.JobRegistry.LoadJobWithTxn(ctx, jobID, txn)
   107  			if err != nil {
   108  				return err
   109  			}
   110  			return job.SetProgress(ctx, *progress)
   111  		}); err != nil {
   112  			return err
   113  		}
   114  	}
   115  	return nil
   116  }
   117  
   118  // Check if we are done GC'ing everything.
   119  func isDoneGC(progress *jobspb.SchemaChangeGCProgress) bool {
   120  	for _, index := range progress.Indexes {
   121  		if index.Status != jobspb.SchemaChangeGCProgress_DELETED {
   122  			return false
   123  		}
   124  	}
   125  	for _, table := range progress.Tables {
   126  		if table.Status != jobspb.SchemaChangeGCProgress_DELETED {
   127  			return false
   128  		}
   129  	}
   130  
   131  	return true
   132  }
   133  
   134  // getAllTablesWaitingForGC returns a slice with all of the table IDs which have
   135  // note yet been been GC'd. This is used to determine which tables' statuses
   136  // need to be updated.
   137  func getAllTablesWaitingForGC(
   138  	details *jobspb.SchemaChangeGCDetails, progress *jobspb.SchemaChangeGCProgress,
   139  ) []sqlbase.ID {
   140  	allRemainingTableIDs := make([]sqlbase.ID, 0, len(progress.Tables))
   141  	if len(details.Indexes) > 0 {
   142  		allRemainingTableIDs = append(allRemainingTableIDs, details.ParentID)
   143  	}
   144  	for _, table := range progress.Tables {
   145  		if table.Status != jobspb.SchemaChangeGCProgress_DELETED {
   146  			allRemainingTableIDs = append(allRemainingTableIDs, table.ID)
   147  		}
   148  	}
   149  
   150  	return allRemainingTableIDs
   151  }
   152  
   153  // validateDetails ensures that the job details payload follows the structure
   154  // described in the comment for SchemaChangeGCDetails.
   155  func validateDetails(details *jobspb.SchemaChangeGCDetails) error {
   156  	if len(details.Indexes) > 0 {
   157  		if details.ParentID == sqlbase.InvalidID {
   158  			return errors.Errorf("must provide a parentID when dropping an index")
   159  		}
   160  	}
   161  	return nil
   162  }
   163  
   164  // persistProgress sets the current state of the progress back on the job.
   165  func persistProgress(
   166  	ctx context.Context,
   167  	execCfg *sql.ExecutorConfig,
   168  	jobID int64,
   169  	progress *jobspb.SchemaChangeGCProgress,
   170  ) {
   171  	if err := execCfg.DB.Txn(ctx, func(ctx context.Context, txn *kv.Txn) error {
   172  		job, err := execCfg.JobRegistry.LoadJobWithTxn(ctx, jobID, txn)
   173  		if err != nil {
   174  			return err
   175  		}
   176  		if err := job.SetProgress(ctx, *progress); err != nil {
   177  			return err
   178  		}
   179  		log.Infof(ctx, "updated progress payload: %+v", progress)
   180  		return nil
   181  	}); err != nil {
   182  		log.Warningf(ctx, "failed to update job's progress payload err: %+v", err)
   183  	}
   184  }
   185  
   186  // getDropTimes returns the data stored in details as a map for convenience.
   187  func getDropTimes(
   188  	details *jobspb.SchemaChangeGCDetails,
   189  ) (map[sqlbase.ID]int64, map[sqlbase.IndexID]int64) {
   190  	tableDropTimes := make(map[sqlbase.ID]int64)
   191  	for _, table := range details.Tables {
   192  		tableDropTimes[table.ID] = table.DropTime
   193  	}
   194  	indexDropTimes := make(map[sqlbase.IndexID]int64)
   195  	for _, index := range details.Indexes {
   196  		indexDropTimes[index.IndexID] = index.DropTime
   197  	}
   198  	return tableDropTimes, indexDropTimes
   199  }