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  }