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 }