github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/gcjob/table_garbage_collection.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  	"time"
    16  
    17  	"github.com/cockroachdb/cockroach/pkg/jobs/jobspb"
    18  	"github.com/cockroachdb/cockroach/pkg/keys"
    19  	"github.com/cockroachdb/cockroach/pkg/kv"
    20  	"github.com/cockroachdb/cockroach/pkg/kv/kvclient/kvcoord"
    21  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    22  	"github.com/cockroachdb/cockroach/pkg/sql"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    24  	"github.com/cockroachdb/cockroach/pkg/util/log"
    25  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    26  	"github.com/cockroachdb/errors"
    27  )
    28  
    29  // gcTables drops the table data and descriptor of tables that have an expired
    30  // deadline and updates the job details to mark the work it did.
    31  // The job progress is updated in place, but needs to be persisted to the job.
    32  func gcTables(
    33  	ctx context.Context, execCfg *sql.ExecutorConfig, progress *jobspb.SchemaChangeGCProgress,
    34  ) (bool, error) {
    35  	didGC := false
    36  	if log.V(2) {
    37  		log.Infof(ctx, "GC is being considered for tables: %+v", progress.Tables)
    38  	}
    39  	for _, droppedTable := range progress.Tables {
    40  		if droppedTable.Status != jobspb.SchemaChangeGCProgress_DELETING {
    41  			// Table is not ready to be dropped, or has already been dropped.
    42  			continue
    43  		}
    44  
    45  		var table *sqlbase.TableDescriptor
    46  		if err := execCfg.DB.Txn(ctx, func(ctx context.Context, txn *kv.Txn) error {
    47  			var err error
    48  			table, err = sqlbase.GetTableDescFromID(ctx, txn, execCfg.Codec, droppedTable.ID)
    49  			return err
    50  		}); err != nil {
    51  			return false, errors.Wrapf(err, "fetching table %d", droppedTable.ID)
    52  		}
    53  
    54  		if !table.Dropped() {
    55  			// We shouldn't drop this table yet.
    56  			continue
    57  		}
    58  
    59  		// First, delete all the table data.
    60  		if err := clearTableData(ctx, execCfg.DB, execCfg.DistSender, execCfg.Codec, table); err != nil {
    61  			return false, errors.Wrapf(err, "clearing data for table %d", table.ID)
    62  		}
    63  
    64  		// Finished deleting all the table data, now delete the table meta data.
    65  		if err := dropTableDesc(ctx, execCfg.DB, execCfg.Codec, table); err != nil {
    66  			return false, errors.Wrapf(err, "dropping table descriptor for table %d", table.ID)
    67  		}
    68  
    69  		// Update the details payload to indicate that the table was dropped.
    70  		markTableGCed(ctx, table.ID, progress)
    71  		didGC = true
    72  	}
    73  	return didGC, nil
    74  }
    75  
    76  // clearTableData deletes all of the data in the specified table.
    77  func clearTableData(
    78  	ctx context.Context,
    79  	db *kv.DB,
    80  	distSender *kvcoord.DistSender,
    81  	codec keys.SQLCodec,
    82  	table *sqlbase.TableDescriptor,
    83  ) error {
    84  	// If DropTime isn't set, assume this drop request is from a version
    85  	// 1.1 server and invoke legacy code that uses DeleteRange and range GC.
    86  	// TODO(pbardea): Note that we never set the drop time for interleaved tables,
    87  	// but this check was added to be more explicit about it. This should get
    88  	// cleaned up.
    89  	if table.DropTime == 0 || table.IsInterleaved() {
    90  		log.Infof(ctx, "clearing data in chunks for table %d", table.ID)
    91  		return sql.ClearTableDataInChunks(ctx, db, codec, table, false /* traceKV */)
    92  	}
    93  	log.Infof(ctx, "clearing data for table %d", table.ID)
    94  
    95  	tableKey := roachpb.RKey(codec.TablePrefix(uint32(table.ID)))
    96  	tableSpan := roachpb.RSpan{Key: tableKey, EndKey: tableKey.PrefixEnd()}
    97  
    98  	// ClearRange requests lays down RocksDB range deletion tombstones that have
    99  	// serious performance implications (#24029). The logic below attempts to
   100  	// bound the number of tombstones in one store by sending the ClearRange
   101  	// requests to each range in the table in small, sequential batches rather
   102  	// than letting DistSender send them all in parallel, to hopefully give the
   103  	// compaction queue time to compact the range tombstones away in between
   104  	// requests.
   105  	//
   106  	// As written, this approach has several deficiencies. It does not actually
   107  	// wait for the compaction queue to compact the tombstones away before
   108  	// sending the next request. It is likely insufficient if multiple DROP
   109  	// TABLEs are in flight at once. It does not save its progress in case the
   110  	// coordinator goes down. These deficiencies could be addressed, but this code
   111  	// was originally a stopgap to avoid the range tombstone performance hit. The
   112  	// RocksDB range tombstone implementation has since been improved and the
   113  	// performance implications of many range tombstones has been reduced
   114  	// dramatically making this simplistic throttling sufficient.
   115  
   116  	// These numbers were chosen empirically for the clearrange roachtest and
   117  	// could certainly use more tuning.
   118  	const batchSize = 100
   119  	const waitTime = 500 * time.Millisecond
   120  
   121  	var n int
   122  	lastKey := tableSpan.Key
   123  	ri := kvcoord.NewRangeIterator(distSender)
   124  	timer := timeutil.NewTimer()
   125  	defer timer.Stop()
   126  
   127  	for ri.Seek(ctx, tableSpan.Key, kvcoord.Ascending); ; ri.Next(ctx) {
   128  		if !ri.Valid() {
   129  			return ri.Error()
   130  		}
   131  
   132  		if n++; n >= batchSize || !ri.NeedAnother(tableSpan) {
   133  			endKey := ri.Desc().EndKey
   134  			if tableSpan.EndKey.Less(endKey) {
   135  				endKey = tableSpan.EndKey
   136  			}
   137  			var b kv.Batch
   138  			b.AddRawRequest(&roachpb.ClearRangeRequest{
   139  				RequestHeader: roachpb.RequestHeader{
   140  					Key:    lastKey.AsRawKey(),
   141  					EndKey: endKey.AsRawKey(),
   142  				},
   143  			})
   144  			log.VEventf(ctx, 2, "ClearRange %s - %s", lastKey, endKey)
   145  			if err := db.Run(ctx, &b); err != nil {
   146  				return errors.Wrapf(err, "clear range %s - %s", lastKey, endKey)
   147  			}
   148  			n = 0
   149  			lastKey = endKey
   150  			timer.Reset(waitTime)
   151  			select {
   152  			case <-timer.C:
   153  				timer.Read = true
   154  			case <-ctx.Done():
   155  				return ctx.Err()
   156  			}
   157  		}
   158  
   159  		if !ri.NeedAnother(tableSpan) {
   160  			break
   161  		}
   162  	}
   163  
   164  	return nil
   165  }