github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/gcjob/refresh_statuses.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 "math" 16 "time" 17 18 "github.com/cockroachdb/cockroach/pkg/config/zonepb" 19 "github.com/cockroachdb/cockroach/pkg/gossip" 20 "github.com/cockroachdb/cockroach/pkg/jobs/jobspb" 21 "github.com/cockroachdb/cockroach/pkg/keys" 22 "github.com/cockroachdb/cockroach/pkg/kv" 23 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/protectedts" 24 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/protectedts/ptpb" 25 "github.com/cockroachdb/cockroach/pkg/roachpb" 26 "github.com/cockroachdb/cockroach/pkg/sql" 27 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 28 "github.com/cockroachdb/cockroach/pkg/util/log" 29 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 30 ) 31 32 // refreshTables updates the status of tables/indexes that are waiting to be 33 // GC'd. 34 // It returns whether or not any index/table has expired and the duration until 35 // the next index/table expires. 36 func refreshTables( 37 ctx context.Context, 38 execCfg *sql.ExecutorConfig, 39 tableIDs []sqlbase.ID, 40 tableDropTimes map[sqlbase.ID]int64, 41 indexDropTimes map[sqlbase.IndexID]int64, 42 jobID int64, 43 progress *jobspb.SchemaChangeGCProgress, 44 ) (expired bool, earliestDeadline time.Time) { 45 earliestDeadline = timeutil.Unix(0, math.MaxInt64) 46 47 for _, tableID := range tableIDs { 48 tableHasExpiredElem, deadline := updateStatusForGCElements( 49 ctx, 50 execCfg, 51 tableID, 52 tableDropTimes, indexDropTimes, 53 progress, 54 ) 55 if tableHasExpiredElem { 56 expired = true 57 } 58 if deadline.Before(earliestDeadline) { 59 earliestDeadline = deadline 60 } 61 } 62 63 if expired { 64 persistProgress(ctx, execCfg, jobID, progress) 65 } 66 67 return expired, earliestDeadline 68 } 69 70 // updateStatusForGCElements updates the status for indexes on this table if any 71 // are waiting for GC. If the table is waiting for GC then the status of the table 72 // will be updated. 73 // It returns whether any indexes or the table have expired as well as the time 74 // until the next index expires if there are any more to drop. 75 func updateStatusForGCElements( 76 ctx context.Context, 77 execCfg *sql.ExecutorConfig, 78 tableID sqlbase.ID, 79 tableDropTimes map[sqlbase.ID]int64, 80 indexDropTimes map[sqlbase.IndexID]int64, 81 progress *jobspb.SchemaChangeGCProgress, 82 ) (expired bool, timeToNextTrigger time.Time) { 83 defTTL := execCfg.DefaultZoneConfig.GC.TTLSeconds 84 cfg := execCfg.Gossip.DeprecatedSystemConfig(47150) 85 protectedtsCache := execCfg.ProtectedTimestampProvider 86 87 earliestDeadline := timeutil.Unix(0, int64(math.MaxInt64)) 88 89 if err := execCfg.DB.Txn(ctx, func(ctx context.Context, txn *kv.Txn) error { 90 table, err := sqlbase.GetTableDescFromID(ctx, txn, execCfg.Codec, tableID) 91 if err != nil { 92 return err 93 } 94 95 zoneCfg, placeholder, _, err := sql.ZoneConfigHook(cfg, uint32(tableID)) 96 if err != nil { 97 log.Errorf(ctx, "zone config for desc: %d, err = %+v", tableID, err) 98 return nil 99 } 100 tableTTL := getTableTTL(defTTL, zoneCfg) 101 if placeholder == nil { 102 placeholder = zoneCfg 103 } 104 105 // Update the status of the table if the table was dropped. 106 if table.Dropped() { 107 deadline := updateTableStatus(ctx, execCfg, int64(tableTTL), protectedtsCache, table, tableDropTimes, progress) 108 if timeutil.Until(deadline) < 0 { 109 expired = true 110 } else if deadline.Before(earliestDeadline) { 111 earliestDeadline = deadline 112 } 113 } 114 115 // Update the status of any indexes waiting for GC. 116 indexesExpired, deadline := updateIndexesStatus(ctx, execCfg, tableTTL, table, protectedtsCache, placeholder, indexDropTimes, progress) 117 if indexesExpired { 118 expired = true 119 } 120 if deadline.Before(earliestDeadline) { 121 earliestDeadline = deadline 122 } 123 124 return nil 125 }); err != nil { 126 log.Warningf(ctx, "error while calculating GC time for table %d, err: %+v", tableID, err) 127 return false, earliestDeadline 128 } 129 130 return expired, earliestDeadline 131 } 132 133 // updateTableStatus sets the status the table to DELETING if the GC TTL has 134 // expired. 135 func updateTableStatus( 136 ctx context.Context, 137 execCfg *sql.ExecutorConfig, 138 ttlSeconds int64, 139 protectedtsCache protectedts.Cache, 140 table *sqlbase.TableDescriptor, 141 tableDropTimes map[sqlbase.ID]int64, 142 progress *jobspb.SchemaChangeGCProgress, 143 ) time.Time { 144 deadline := timeutil.Unix(0, int64(math.MaxInt64)) 145 sp := table.TableSpan(execCfg.Codec) 146 147 for i, t := range progress.Tables { 148 droppedTable := &progress.Tables[i] 149 if droppedTable.ID != table.ID || droppedTable.Status == jobspb.SchemaChangeGCProgress_DELETED { 150 continue 151 } 152 153 deadlineNanos := tableDropTimes[t.ID] + ttlSeconds*time.Second.Nanoseconds() 154 deadline = timeutil.Unix(0, deadlineNanos) 155 if isProtected(ctx, protectedtsCache, tableDropTimes[t.ID], sp) { 156 log.Infof(ctx, "a timestamp protection delayed GC of table %d", t.ID) 157 return deadline 158 } 159 160 lifetime := timeutil.Until(deadline) 161 if lifetime < 0 { 162 if log.V(2) { 163 log.Infof(ctx, "detected expired table %d", t.ID) 164 } 165 droppedTable.Status = jobspb.SchemaChangeGCProgress_DELETING 166 } else { 167 if log.V(2) { 168 log.Infof(ctx, "table %d still has %+v until GC", t.ID, lifetime) 169 } 170 } 171 break 172 } 173 174 return deadline 175 } 176 177 // updateIndexesStatus updates the status on every index that is waiting for GC 178 // TTL in this table. 179 // It returns whether any indexes have expired and the timestamp of when another 180 // index should be GC'd, if any, otherwise MaxInt. 181 func updateIndexesStatus( 182 ctx context.Context, 183 execCfg *sql.ExecutorConfig, 184 tableTTL int32, 185 table *sqlbase.TableDescriptor, 186 protectedtsCache protectedts.Cache, 187 placeholder *zonepb.ZoneConfig, 188 indexDropTimes map[sqlbase.IndexID]int64, 189 progress *jobspb.SchemaChangeGCProgress, 190 ) (expired bool, soonestDeadline time.Time) { 191 // Update the deadline for indexes that are being dropped, if any. 192 soonestDeadline = timeutil.Unix(0, int64(math.MaxInt64)) 193 for i := 0; i < len(progress.Indexes); i++ { 194 idxProgress := &progress.Indexes[i] 195 if idxProgress.Status == jobspb.SchemaChangeGCProgress_DELETED { 196 continue 197 } 198 199 sp := table.IndexSpan(execCfg.Codec, idxProgress.IndexID) 200 201 ttlSeconds := getIndexTTL(tableTTL, placeholder, idxProgress.IndexID) 202 203 deadlineNanos := indexDropTimes[idxProgress.IndexID] + int64(ttlSeconds)*time.Second.Nanoseconds() 204 deadline := timeutil.Unix(0, deadlineNanos) 205 if isProtected(ctx, protectedtsCache, indexDropTimes[idxProgress.IndexID], sp) { 206 log.Infof(ctx, "a timestamp protection delayed GC of index %d from table %d", idxProgress.IndexID, table.ID) 207 continue 208 } 209 lifetime := time.Until(deadline) 210 if lifetime > 0 { 211 if log.V(2) { 212 log.Infof(ctx, "index %d from table %d still has %+v until GC", idxProgress.IndexID, table.ID, lifetime) 213 } 214 } 215 if lifetime < 0 { 216 expired = true 217 if log.V(2) { 218 log.Infof(ctx, "detected expired index %d from table %d", idxProgress.IndexID, table.ID) 219 } 220 idxProgress.Status = jobspb.SchemaChangeGCProgress_DELETING 221 } else if deadline.Before(soonestDeadline) { 222 soonestDeadline = deadline 223 } 224 } 225 return expired, soonestDeadline 226 } 227 228 // Helpers. 229 230 func getIndexTTL(tableTTL int32, placeholder *zonepb.ZoneConfig, indexID sqlbase.IndexID) int32 { 231 ttlSeconds := tableTTL 232 if placeholder != nil { 233 if subzone := placeholder.GetSubzone( 234 uint32(indexID), ""); subzone != nil && subzone.Config.GC != nil { 235 ttlSeconds = subzone.Config.GC.TTLSeconds 236 } 237 } 238 return ttlSeconds 239 } 240 241 func getTableTTL(defTTL int32, zoneCfg *zonepb.ZoneConfig) int32 { 242 ttlSeconds := defTTL 243 if zoneCfg != nil { 244 ttlSeconds = zoneCfg.GC.TTLSeconds 245 } 246 return ttlSeconds 247 } 248 249 // Returns whether or not a key in the given spans is protected. 250 // TODO(pbardea): If the TTL for this index/table expired and we're only blocked 251 // on a protected timestamp, this may be useful information to surface to the 252 // user. 253 func isProtected( 254 ctx context.Context, protectedtsCache protectedts.Cache, atTime int64, sp roachpb.Span, 255 ) bool { 256 protected := false 257 protectedtsCache.Iterate(ctx, 258 sp.Key, sp.EndKey, 259 func(r *ptpb.Record) (wantMore bool) { 260 // If we encounter any protected timestamp records in this span, we 261 // can't GC. 262 if r.Timestamp.WallTime < atTime { 263 protected = true 264 return false 265 } 266 return true 267 }) 268 return protected 269 } 270 271 // setupConfigWatcher returns a filter to watch zone config changes and a 272 // channel that is notified when there are changes. 273 func setupConfigWatcher( 274 execCfg *sql.ExecutorConfig, 275 ) (gossip.SystemConfigDeltaFilter, <-chan struct{}) { 276 k := execCfg.Codec.IndexPrefix(uint32(keys.ZonesTableID), uint32(keys.ZonesTablePrimaryIndexID)) 277 zoneCfgFilter := gossip.MakeSystemConfigDeltaFilter(k) 278 gossipUpdateC := execCfg.Gossip.DeprecatedRegisterSystemConfigChannel(47150) 279 return zoneCfgFilter, gossipUpdateC 280 }