github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/gcjob/gc_job.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" 18 "github.com/cockroachdb/cockroach/pkg/jobs/jobspb" 19 "github.com/cockroachdb/cockroach/pkg/roachpb" 20 "github.com/cockroachdb/cockroach/pkg/settings/cluster" 21 "github.com/cockroachdb/cockroach/pkg/sql" 22 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 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 var ( 30 // MaxSQLGCInterval is the longest the polling interval between checking if 31 // elements should be GC'd. 32 MaxSQLGCInterval = 5 * time.Minute 33 ) 34 35 // SetSmallMaxGCIntervalForTest sets the MaxSQLGCInterval and then returns a closure 36 // that resets it. 37 // This is to be used in tests like: 38 // defer SetSmallMaxGCIntervalForTest() 39 func SetSmallMaxGCIntervalForTest() func() { 40 oldInterval := MaxSQLGCInterval 41 MaxSQLGCInterval = 500 * time.Millisecond 42 return func() { 43 MaxSQLGCInterval = oldInterval 44 } 45 } 46 47 type schemaChangeGCResumer struct { 48 jobID int64 49 } 50 51 // performGC GCs any schema elements that are in the DELETING state and returns 52 // a bool indicating if it GC'd any elements. 53 func performGC( 54 ctx context.Context, 55 execCfg *sql.ExecutorConfig, 56 details *jobspb.SchemaChangeGCDetails, 57 progress *jobspb.SchemaChangeGCProgress, 58 ) (bool, error) { 59 didGC := false 60 if details.Indexes != nil { 61 if didGCIndex, err := gcIndexes(ctx, execCfg, details.ParentID, progress); err != nil { 62 return false, errors.Wrap(err, "attempting to GC indexes") 63 } else if didGCIndex { 64 didGC = true 65 } 66 } else if details.Tables != nil { 67 if didGCTable, err := gcTables(ctx, execCfg, progress); err != nil { 68 return false, errors.Wrap(err, "attempting to GC tables") 69 } else if didGCTable { 70 didGC = true 71 } 72 73 // Drop database zone config when all the tables have been GCed. 74 if details.ParentID != sqlbase.InvalidID && isDoneGC(progress) { 75 if err := deleteDatabaseZoneConfig(ctx, execCfg.DB, details.ParentID); err != nil { 76 return false, errors.Wrap(err, "deleting database zone config") 77 } 78 } 79 } 80 return didGC, nil 81 } 82 83 // Resume is part of the jobs.Resumer interface. 84 func (r schemaChangeGCResumer) Resume( 85 ctx context.Context, phs interface{}, _ chan<- tree.Datums, 86 ) error { 87 p := phs.(sql.PlanHookState) 88 // TODO(pbardea): Wait for no versions. 89 execCfg := p.ExecCfg() 90 if fn := execCfg.GCJobTestingKnobs.RunBeforeResume; fn != nil { 91 if err := fn(r.jobID); err != nil { 92 return err 93 } 94 } 95 details, progress, err := initDetailsAndProgress(ctx, execCfg, r.jobID) 96 if err != nil { 97 return err 98 } 99 zoneCfgFilter, gossipUpdateC := setupConfigWatcher(execCfg) 100 tableDropTimes, indexDropTimes := getDropTimes(details) 101 102 allTables := getAllTablesWaitingForGC(details, progress) 103 if len(allTables) == 0 { 104 return nil 105 } 106 expired, earliestDeadline := refreshTables(ctx, execCfg, allTables, tableDropTimes, indexDropTimes, r.jobID, progress) 107 timerDuration := timeutil.Until(earliestDeadline) 108 if expired { 109 timerDuration = 0 110 } else if timerDuration > MaxSQLGCInterval { 111 timerDuration = MaxSQLGCInterval 112 } 113 timer := timeutil.NewTimer() 114 defer timer.Stop() 115 timer.Reset(timerDuration) 116 117 for { 118 select { 119 case <-gossipUpdateC: 120 // Upon notification of a gossip update, update the status of the relevant schema elements. 121 if log.V(2) { 122 log.Info(ctx, "received a new system config") 123 } 124 // TODO (lucy): Currently we're calling refreshTables on every zone config 125 // update to any table. We should really be only updating a cached 126 // TTL whenever we get an update on one of the tables/indexes (or the db) 127 // that this job is responsible for, and computing the earliest deadline 128 // from our set of cached TTL values. 129 cfg := execCfg.Gossip.DeprecatedSystemConfig(47150) 130 zoneConfigUpdated := false 131 zoneCfgFilter.ForModified(cfg, func(kv roachpb.KeyValue) { 132 zoneConfigUpdated = true 133 }) 134 if !zoneConfigUpdated { 135 log.VEventf(ctx, 2, "no zone config updates, continuing") 136 continue 137 } 138 remainingTables := getAllTablesWaitingForGC(details, progress) 139 if len(remainingTables) == 0 { 140 return nil 141 } 142 expired, earliestDeadline = refreshTables(ctx, execCfg, remainingTables, tableDropTimes, indexDropTimes, r.jobID, progress) 143 timerDuration := time.Until(earliestDeadline) 144 if expired { 145 timerDuration = 0 146 } else if timerDuration > MaxSQLGCInterval { 147 timerDuration = MaxSQLGCInterval 148 } 149 150 timer.Reset(timerDuration) 151 case <-timer.C: 152 timer.Read = true 153 if log.V(2) { 154 log.Info(ctx, "SchemaChangeGC timer triggered") 155 } 156 // Refresh the status of all tables in case any GC TTLs have changed. 157 remainingTables := getAllTablesWaitingForGC(details, progress) 158 _, earliestDeadline = refreshTables(ctx, execCfg, remainingTables, tableDropTimes, indexDropTimes, r.jobID, progress) 159 160 if didWork, err := performGC(ctx, execCfg, details, progress); err != nil { 161 return err 162 } else if didWork { 163 persistProgress(ctx, execCfg, r.jobID, progress) 164 } 165 166 if isDoneGC(progress) { 167 return nil 168 } 169 170 // Schedule the next check for GC. 171 timerDuration := time.Until(earliestDeadline) 172 if timerDuration > MaxSQLGCInterval { 173 timerDuration = MaxSQLGCInterval 174 } 175 timer.Reset(timerDuration) 176 case <-ctx.Done(): 177 return ctx.Err() 178 } 179 } 180 } 181 182 // OnFailOrCancel is part of the jobs.Resumer interface. 183 func (r schemaChangeGCResumer) OnFailOrCancel(context.Context, interface{}) error { 184 return nil 185 } 186 187 func init() { 188 createResumerFn := func(job *jobs.Job, settings *cluster.Settings) jobs.Resumer { 189 return &schemaChangeGCResumer{ 190 jobID: *job.ID(), 191 } 192 } 193 jobs.RegisterConstructor(jobspb.TypeSchemaChangeGC, createResumerFn) 194 }