github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/gc_queue_test.go (about)

     1  // Copyright 2015 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 kvserver
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"math/rand"
    17  	"reflect"
    18  	"sync/atomic"
    19  	"testing"
    20  	"testing/quick"
    21  	"time"
    22  
    23  	"github.com/cockroachdb/cockroach/pkg/config/zonepb"
    24  	"github.com/cockroachdb/cockroach/pkg/keys"
    25  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/gc"
    26  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvserverbase"
    27  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    28  	"github.com/cockroachdb/cockroach/pkg/storage"
    29  	"github.com/cockroachdb/cockroach/pkg/storage/enginepb"
    30  	"github.com/cockroachdb/cockroach/pkg/testutils"
    31  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    32  	"github.com/cockroachdb/cockroach/pkg/util/humanizeutil"
    33  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    34  	"github.com/cockroachdb/cockroach/pkg/util/log"
    35  	"github.com/cockroachdb/cockroach/pkg/util/protoutil"
    36  	"github.com/cockroachdb/cockroach/pkg/util/randutil"
    37  	"github.com/cockroachdb/cockroach/pkg/util/stop"
    38  	"github.com/cockroachdb/cockroach/pkg/util/uuid"
    39  	"github.com/cockroachdb/errors"
    40  	"github.com/kr/pretty"
    41  	"github.com/stretchr/testify/require"
    42  	"golang.org/x/sync/syncmap"
    43  )
    44  
    45  // makeTS creates a new hybrid logical timestamp.
    46  func makeTS(nanos int64, logical int32) hlc.Timestamp {
    47  	return hlc.Timestamp{
    48  		WallTime: nanos,
    49  		Logical:  logical,
    50  	}
    51  }
    52  
    53  func TestGCQueueScoreString(t *testing.T) {
    54  	defer leaktest.AfterTest(t)()
    55  	for i, c := range []struct {
    56  		r   gcQueueScore
    57  		exp string
    58  	}{
    59  		{gcQueueScore{}, "(empty)"},
    60  		{gcQueueScore{
    61  			ShouldQueue:              true,
    62  			FuzzFactor:               1.25,
    63  			FinalScore:               3.45 * 1.25,
    64  			ValuesScalableScore:      4,
    65  			DeadFraction:             0.25,
    66  			IntentScore:              0.45,
    67  			ExpMinGCByteAgeReduction: 256 * 1024,
    68  			GCByteAge:                512 * 1024,
    69  			GCBytes:                  1024 * 3,
    70  			LikelyLastGC:             5 * time.Second,
    71  		},
    72  			`queue=true with 4.31/fuzz(1.25)=3.45=valScaleScore(4.00)*deadFrac(0.25)+intentScore(0.45)
    73  likely last GC: 5s ago, 3.0 KiB non-live, curr. age 512 KiB*s, min exp. reduction: 256 KiB*s`},
    74  		// Check case of empty Threshold.
    75  		{gcQueueScore{ShouldQueue: true}, `queue=true with 0.00/fuzz(0.00)=NaN=valScaleScore(0.00)*deadFrac(0.00)+intentScore(0.00)
    76  likely last GC: never, 0 B non-live, curr. age 0 B*s, min exp. reduction: 0 B*s`},
    77  	} {
    78  		if act := c.r.String(); act != c.exp {
    79  			t.Errorf("%d: wanted:\n'%s'\ngot:\n'%s'", i, c.exp, act)
    80  		}
    81  	}
    82  }
    83  
    84  func TestGCQueueMakeGCScoreInvariantQuick(t *testing.T) {
    85  	defer leaktest.AfterTest(t)()
    86  
    87  	rnd, seed := randutil.NewPseudoRand()
    88  	cfg := quick.Config{
    89  		MaxCount: 50000,
    90  		Rand:     rnd,
    91  	}
    92  	initialNow := hlc.Timestamp{}.Add(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).UnixNano(), 0)
    93  	ctx := context.Background()
    94  
    95  	// Verify that the basic assumption always holds: We won't queue based on
    96  	// GCByte{s,Age} unless TTL-based deletion would actually delete something.
    97  	if err := quick.Check(func(
    98  		seed uint16, uTTLSec uint32, uTimePassed time.Duration, uGCBytes uint32, uGCByteAge uint32,
    99  	) bool {
   100  		ttlSec, timePassed := int32(uTTLSec), uTimePassed
   101  		gcBytes, gcByteAge := int64(uGCBytes), int64(uGCByteAge)
   102  
   103  		ms := enginepb.MVCCStats{
   104  			LastUpdateNanos: initialNow.WallTime,
   105  			ValBytes:        gcBytes,
   106  			GCBytesAge:      gcByteAge,
   107  		}
   108  		now := initialNow.Add(timePassed.Nanoseconds(), 0)
   109  		r := makeGCQueueScoreImpl(ctx, int64(seed), now, ms, zonepb.GCPolicy{TTLSeconds: ttlSec}, hlc.Timestamp{})
   110  		wouldHaveToDeleteSomething := gcBytes*int64(ttlSec) < ms.GCByteAge(now.WallTime)
   111  		result := !r.ShouldQueue || wouldHaveToDeleteSomething
   112  		if !result {
   113  			log.Warningf(ctx, "failing on ttl=%d, timePassed=%s, gcBytes=%d, gcByteAge=%d:\n%s",
   114  				ttlSec, timePassed, gcBytes, gcByteAge, r)
   115  		}
   116  		return result
   117  	}, &cfg); err != nil {
   118  		t.Fatal(errors.Wrapf(err, "with seed %d", seed))
   119  	}
   120  }
   121  
   122  func TestGCQueueMakeGCScoreAnomalousStats(t *testing.T) {
   123  	defer leaktest.AfterTest(t)()
   124  	if err := quick.Check(func(keyBytes, valBytes, liveBytes int32, containsEstimates int64) bool {
   125  		r := makeGCQueueScoreImpl(context.Background(), 0, hlc.Timestamp{}, enginepb.MVCCStats{
   126  			ContainsEstimates: containsEstimates,
   127  			LiveBytes:         int64(liveBytes),
   128  			ValBytes:          int64(valBytes),
   129  			KeyBytes:          int64(keyBytes),
   130  		}, zonepb.GCPolicy{TTLSeconds: 60}, hlc.Timestamp{})
   131  		return r.DeadFraction >= 0 && r.DeadFraction <= 1
   132  	}, &quick.Config{MaxCount: 1000}); err != nil {
   133  		t.Fatal(err)
   134  	}
   135  }
   136  
   137  func TestGCQueueMakeGCScoreLargeAbortSpan(t *testing.T) {
   138  	defer leaktest.AfterTest(t)()
   139  	const seed = 1
   140  	var ms enginepb.MVCCStats
   141  	ms.SysCount += probablyLargeAbortSpanSysCountThreshold
   142  	ms.SysBytes += probablyLargeAbortSpanSysBytesThreshold
   143  
   144  	gcThresh := hlc.Timestamp{WallTime: 1}
   145  	expiration := kvserverbase.TxnCleanupThreshold.Nanoseconds() + 1
   146  
   147  	// GC triggered if abort span should all be gc'able and it's likely large.
   148  	{
   149  		r := makeGCQueueScoreImpl(
   150  			context.Background(), seed,
   151  			hlc.Timestamp{WallTime: expiration + 1},
   152  			ms, zonepb.GCPolicy{TTLSeconds: 10000},
   153  			gcThresh,
   154  		)
   155  		require.True(t, r.ShouldQueue)
   156  		require.NotZero(t, r.FinalScore)
   157  	}
   158  
   159  	// Heuristic doesn't fire if likely last GC within TxnCleanupThreshold.
   160  	{
   161  		r := makeGCQueueScoreImpl(context.Background(), seed,
   162  			hlc.Timestamp{WallTime: expiration},
   163  			ms, zonepb.GCPolicy{TTLSeconds: 10000},
   164  			gcThresh,
   165  		)
   166  		require.False(t, r.ShouldQueue)
   167  		require.Zero(t, r.FinalScore)
   168  	}
   169  }
   170  
   171  const cacheFirstLen = 3
   172  
   173  type gcTestCacheKey struct {
   174  	enginepb.MVCCStats
   175  	string
   176  }
   177  type gcTestCacheVal struct {
   178  	first [cacheFirstLen]enginepb.MVCCStats
   179  	last  enginepb.MVCCStats
   180  }
   181  
   182  type cachedWriteSimulator struct {
   183  	cache map[gcTestCacheKey]gcTestCacheVal
   184  	t     *testing.T
   185  }
   186  
   187  func newCachedWriteSimulator(t *testing.T) *cachedWriteSimulator {
   188  	var cws cachedWriteSimulator
   189  	cws.t = t
   190  	cws.cache = map[gcTestCacheKey]gcTestCacheVal{
   191  		{enginepb.MVCCStats{LastUpdateNanos: 946684800000000000}, "1-1m0s-1.0 MiB"}: {
   192  			first: [cacheFirstLen]enginepb.MVCCStats{
   193  				{ContainsEstimates: 0, LastUpdateNanos: 946684800000000000, IntentAge: 0, GCBytesAge: 0, LiveBytes: 1048604, LiveCount: 1, KeyBytes: 23, KeyCount: 1, ValBytes: 1048581, ValCount: 1, IntentBytes: 0, IntentCount: 0, SysBytes: 0, SysCount: 0},
   194  				{ContainsEstimates: 0, LastUpdateNanos: 946684801000000000, IntentAge: 0, GCBytesAge: 0, LiveBytes: 1048604, LiveCount: 1, KeyBytes: 35, KeyCount: 1, ValBytes: 2097162, ValCount: 2, IntentBytes: 0, IntentCount: 0, SysBytes: 0, SysCount: 0},
   195  				{ContainsEstimates: 0, LastUpdateNanos: 946684802000000000, IntentAge: 0, GCBytesAge: 1048593, LiveBytes: 1048604, LiveCount: 1, KeyBytes: 47, KeyCount: 1, ValBytes: 3145743, ValCount: 3, IntentBytes: 0, IntentCount: 0, SysBytes: 0, SysCount: 0},
   196  			},
   197  			last: enginepb.MVCCStats{ContainsEstimates: 0, LastUpdateNanos: 946684860000000000, IntentAge: 0, GCBytesAge: 1856009610, LiveBytes: 1048604, LiveCount: 1, KeyBytes: 743, KeyCount: 1, ValBytes: 63963441, ValCount: 61, IntentBytes: 0, IntentCount: 0, SysBytes: 0, SysCount: 0},
   198  		},
   199  	}
   200  	return &cws
   201  }
   202  
   203  func (cws *cachedWriteSimulator) value(size int) roachpb.Value {
   204  	var value roachpb.Value
   205  	kb := make([]byte, size)
   206  	if n, err := rand.New(rand.NewSource(int64(size))).Read(kb); err != nil {
   207  		cws.t.Fatal(err)
   208  	} else if n != size {
   209  		cws.t.Fatalf("wrote only %d < %d", n, size)
   210  	}
   211  	value.SetBytes(kb)
   212  	return value
   213  }
   214  
   215  func (cws *cachedWriteSimulator) multiKey(
   216  	numOps int, size int, txn *roachpb.Transaction, ms *enginepb.MVCCStats,
   217  ) {
   218  	eng := storage.NewDefaultInMem()
   219  	defer eng.Close()
   220  	t, ctx := cws.t, context.Background()
   221  
   222  	ts := hlc.Timestamp{}.Add(ms.LastUpdateNanos, 0)
   223  	key, value := []byte("multikey"), cws.value(size)
   224  	var eachMS enginepb.MVCCStats
   225  	if err := storage.MVCCPut(ctx, eng, &eachMS, key, ts, value, txn); err != nil {
   226  		t.Fatal(err)
   227  	}
   228  	for i := 1; i < numOps; i++ {
   229  		ms.Add(eachMS)
   230  	}
   231  }
   232  
   233  func (cws *cachedWriteSimulator) singleKeySteady(
   234  	qps int, duration time.Duration, size int, ms *enginepb.MVCCStats,
   235  ) {
   236  	eng := storage.NewDefaultInMem()
   237  	defer eng.Close()
   238  	t, ctx := cws.t, context.Background()
   239  	cacheKey := fmt.Sprintf("%d-%s-%s", qps, duration, humanizeutil.IBytes(int64(size)))
   240  	cached, ok := cws.cache[gcTestCacheKey{*ms, cacheKey}]
   241  	if !ok && duration > 0 {
   242  		t.Fatalf("cache key missing: %s with %s", cacheKey, pretty.Sprint(*ms))
   243  	}
   244  	firstSl := make([]enginepb.MVCCStats, 0, cacheFirstLen)
   245  	// Pick random bytes once; they don't matter for stats but we want reproducability.
   246  	key, value := []byte("0123456789"), cws.value(size)
   247  
   248  	initialNow := hlc.Timestamp{}.Add(ms.LastUpdateNanos, 0)
   249  
   250  	for elapsed := time.Duration(0); elapsed <= duration; elapsed += time.Second {
   251  		for i := 0; i < qps; i++ {
   252  			now := initialNow.Add(elapsed.Nanoseconds(), int32(i))
   253  
   254  			if err := storage.MVCCPut(ctx, eng, ms, key, now, value, nil /* txn */); err != nil {
   255  				t.Fatal(err)
   256  			}
   257  			if len(firstSl) < cacheFirstLen {
   258  				firstSl = append(firstSl, *ms)
   259  			} else if len(firstSl) == cacheFirstLen {
   260  				if ok && reflect.DeepEqual(firstSl, cached.first[:cacheFirstLen]) {
   261  					*ms = cached.last
   262  					// Exit both loops.
   263  					elapsed += duration
   264  					break
   265  				} else {
   266  					ok = false
   267  				}
   268  			}
   269  		}
   270  	}
   271  
   272  	if !ok && duration > 0 {
   273  		copy(cached.first[:3], firstSl)
   274  		cached.last = *ms
   275  		t.Fatalf("missing or incorrect cache entry for %s, should be \n%s", cacheKey, pretty.Sprint(cached))
   276  	}
   277  }
   278  
   279  func (cws *cachedWriteSimulator) shouldQueue(
   280  	b bool, prio float64, after time.Duration, ttl time.Duration, ms enginepb.MVCCStats,
   281  ) {
   282  	cws.t.Helper()
   283  	ts := hlc.Timestamp{}.Add(ms.LastUpdateNanos+after.Nanoseconds(), 0)
   284  	r := makeGCQueueScoreImpl(context.Background(), 0 /* seed */, ts, ms, zonepb.GCPolicy{
   285  		TTLSeconds: int32(ttl.Seconds()),
   286  	}, hlc.Timestamp{})
   287  	if fmt.Sprintf("%.2f", r.FinalScore) != fmt.Sprintf("%.2f", prio) || b != r.ShouldQueue {
   288  		cws.t.Errorf("expected queued=%t (is %t), prio=%.2f, got %.2f: after=%s, ttl=%s:\nms: %+v\nscore: %s",
   289  			b, r.ShouldQueue, prio, r.FinalScore, after, ttl, ms, r)
   290  	}
   291  }
   292  
   293  // TestGCQueueMakeGCScoreRealistic verifies conditions which inform priority and
   294  // whether or not the range should be queued into the GC queue. Ranges are
   295  // queued for GC based on two conditions. The age of bytes available to be GC'd,
   296  // and the age of unresolved intents.
   297  func TestGCQueueMakeGCScoreRealistic(t *testing.T) {
   298  	defer leaktest.AfterTest(t)()
   299  
   300  	cws := newCachedWriteSimulator(t)
   301  
   302  	initialMS := func() enginepb.MVCCStats {
   303  		var zeroMS enginepb.MVCCStats
   304  		zeroMS.AgeTo(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).UnixNano())
   305  		return zeroMS
   306  	}
   307  
   308  	minuteTTL, hourTTL := time.Minute, time.Hour
   309  
   310  	{
   311  		// Hammer a key with 1MB blobs for a (simulated) minute. Logically, at
   312  		// the end of that you have around 60MB that are non-live, but cannot
   313  		// immediately be deleted. Deletion is only possible for keys that are
   314  		// (more or less) past TTL, and shouldQueue should respect that.
   315  		ms := initialMS()
   316  		cws.singleKeySteady(1, time.Minute, 1<<20, &ms)
   317  
   318  		// First, check that a zero second TTL behaves like a one second one.
   319  		// This is a special case without which the priority would be +Inf.
   320  		//
   321  		// Since at the time of this check the data is already 30s old on
   322  		// average (i.e. ~30x the TTL), we expect to *really* want GC.
   323  		cws.shouldQueue(true, 28.94, time.Duration(0), 0, ms)
   324  		cws.shouldQueue(true, 28.94, time.Duration(0), 0, ms)
   325  
   326  		// Right after we finished writing, we don't want to GC yet with a one-minute TTL.
   327  		cws.shouldQueue(false, 0.48, time.Duration(0), minuteTTL, ms)
   328  
   329  		// Neither after a minute. The first values are about to become GC'able, though.
   330  		cws.shouldQueue(false, 1.46, time.Minute, minuteTTL, ms)
   331  		// 90 seconds in it's really close, but still just shy of GC. Half of the
   332  		// values could be deleted now (remember that we wrote them over a one
   333  		// minute period).
   334  		cws.shouldQueue(false, 1.95, 3*time.Minute/2, minuteTTL, ms)
   335  		// Advancing another 1/4 minute does the trick.
   336  		cws.shouldQueue(true, 2.20, 7*time.Minute/4, minuteTTL, ms)
   337  		// After an hour, that's (of course) still true with a very high priority.
   338  		cws.shouldQueue(true, 59.34, time.Hour, minuteTTL, ms)
   339  
   340  		// Let's see what the same would look like with a 1h TTL.
   341  		// Can't delete anything until 59min have passed, and indeed the score is low.
   342  		cws.shouldQueue(false, 0.01, time.Duration(0), hourTTL, ms)
   343  		cws.shouldQueue(false, 0.02, time.Minute, hourTTL, ms)
   344  		cws.shouldQueue(false, 0.99, time.Hour, hourTTL, ms)
   345  		// After 90 minutes, we're getting closer. After just over two hours,
   346  		// definitely ripe for GC (and this would delete all the values).
   347  		cws.shouldQueue(false, 1.48, 90*time.Minute, hourTTL, ms)
   348  		cws.shouldQueue(true, 2.05, 125*time.Minute, hourTTL, ms)
   349  	}
   350  
   351  	{
   352  		ms, valSize := initialMS(), 1<<10
   353  
   354  		// Write 999 distinct 1kb keys at the initial timestamp without transaction.
   355  		cws.multiKey(999, valSize, nil /* no txn */, &ms)
   356  
   357  		// GC shouldn't move at all, even after a long time and short TTL.
   358  		cws.shouldQueue(false, 0, 24*time.Hour, minuteTTL, ms)
   359  
   360  		// Write a single key twice.
   361  		cws.singleKeySteady(2, 0, valSize, &ms)
   362  
   363  		// Now we have the situation in which the replica is pretty large
   364  		// compared to the amount of (ever) GC'able data it has. Consequently,
   365  		// the GC score should rise very slowly.
   366  
   367  		// If the fact that 99.9% of the replica is live were not taken into
   368  		// account, this would get us at least close to GC.
   369  		cws.shouldQueue(false, 0.00, 5*time.Minute, minuteTTL, ms)
   370  
   371  		// 12 hours in the score becomes relevant, but not yet large enough.
   372  		// The key is of course GC'able and has been for a long time, but
   373  		// to find it we'd have to scan all the other kv pairs as well.
   374  		cws.shouldQueue(false, 0.71, 12*time.Hour, minuteTTL, ms)
   375  
   376  		// Two days in we're more than ready to go, would queue for GC, and
   377  		// delete.
   378  		cws.shouldQueue(true, 2.85, 48*time.Hour, minuteTTL, ms)
   379  	}
   380  
   381  	{
   382  		irrelevantTTL := 24 * time.Hour * 365
   383  		ms, valSize := initialMS(), 1<<10
   384  		txn := newTransaction(
   385  			"txn", roachpb.Key("key"), roachpb.NormalUserPriority,
   386  			hlc.NewClock(func() int64 { return ms.LastUpdateNanos }, time.Millisecond))
   387  
   388  		// Write 1000 distinct 1kb intents at the initial timestamp. This means that
   389  		// the average intent age is just the time elapsed from now, and this is roughly
   390  		// normalized by one day at the time of writing. Note that the size of the writes
   391  		// doesn't matter. In reality, the value-based GC score will often strike first.
   392  		cws.multiKey(100, valSize, txn, &ms)
   393  
   394  		cws.shouldQueue(false, 1.00, 24*time.Hour, irrelevantTTL, ms)
   395  		cws.shouldQueue(false, 1.99, 2*24*time.Hour, irrelevantTTL, ms)
   396  		cws.shouldQueue(false, 3.99, 4*24*time.Hour, irrelevantTTL, ms)
   397  		cws.shouldQueue(false, 6.98, 7*24*time.Hour, irrelevantTTL, ms)
   398  		cws.shouldQueue(true, 11.97, 12*24*time.Hour, irrelevantTTL, ms)
   399  	}
   400  }
   401  
   402  // TestGCQueueProcess creates test data in the range over various time
   403  // scales and verifies that scan queue process properly GCs test data.
   404  func TestGCQueueProcess(t *testing.T) {
   405  	defer leaktest.AfterTest(t)()
   406  	ctx := context.Background()
   407  	tc := testContext{}
   408  	stopper := stop.NewStopper()
   409  	defer stopper.Stop(ctx)
   410  	tc.Start(t, stopper)
   411  
   412  	tc.manualClock.Increment(48 * 60 * 60 * 1e9) // 2d past the epoch
   413  	now := tc.Clock().Now().WallTime
   414  
   415  	ts1 := makeTS(now-2*24*60*60*1e9+1, 0)                        // 2d old (add one nanosecond so we're not using zero timestamp)
   416  	ts2 := makeTS(now-25*60*60*1e9, 0)                            // GC will occur at time=25 hours
   417  	ts2m1 := ts2.Prev()                                           // ts2 - 1 so we have something not right at the GC time
   418  	ts3 := makeTS(now-gc.IntentAgeThreshold.Nanoseconds(), 0)     // 2h old
   419  	ts4 := makeTS(now-(gc.IntentAgeThreshold.Nanoseconds()-1), 0) // 2h-1ns old
   420  	ts5 := makeTS(now-1e9, 0)                                     // 1s old
   421  	key1 := roachpb.Key("a")
   422  	key2 := roachpb.Key("b")
   423  	key3 := roachpb.Key("c")
   424  	key4 := roachpb.Key("d")
   425  	key5 := roachpb.Key("e")
   426  	key6 := roachpb.Key("f")
   427  	key7 := roachpb.Key("g")
   428  	key8 := roachpb.Key("h")
   429  	key9 := roachpb.Key("i")
   430  	key10 := roachpb.Key("j")
   431  	key11 := roachpb.Key("k")
   432  
   433  	data := []struct {
   434  		key roachpb.Key
   435  		ts  hlc.Timestamp
   436  		del bool
   437  		txn bool
   438  	}{
   439  		// For key1, we expect first value to GC.
   440  		{key1, ts1, false, false},
   441  		{key1, ts2, false, false},
   442  		{key1, ts5, false, false},
   443  		// For key2, we expect values to GC, even though most recent is deletion.
   444  		{key2, ts1, false, false},
   445  		{key2, ts2m1, false, false}, // use a value < the GC time to verify it's kept
   446  		{key2, ts5, true, false},
   447  		// For key3, we expect just ts1 to GC, because most recent deletion is intent.
   448  		{key3, ts1, false, false},
   449  		{key3, ts2, false, false},
   450  		{key3, ts5, true, true},
   451  		// For key4, expect oldest value to GC.
   452  		{key4, ts1, false, false},
   453  		{key4, ts2, false, false},
   454  		// For key5, expect all values to GC (most recent value deleted).
   455  		{key5, ts1, false, false},
   456  		{key5, ts2, true, false}, // deleted, so GC
   457  		// For key6, expect no values to GC because most recent value is intent.
   458  		{key6, ts1, false, false},
   459  		{key6, ts5, false, true},
   460  		// For key7, expect no values to GC because intent is exactly 2h old.
   461  		{key7, ts2, false, false},
   462  		{key7, ts4, false, true},
   463  		// For key8, expect most recent value to resolve by aborting, which will clean it up.
   464  		{key8, ts2, false, false},
   465  		{key8, ts3, true, true},
   466  		// For key9, resolve naked intent with no remaining values.
   467  		{key9, ts3, false, true},
   468  		// For key10, GC ts1 because it's a delete but not ts3 because it's above the threshold.
   469  		{key10, ts1, true, false},
   470  		{key10, ts3, true, false},
   471  		{key10, ts4, false, false},
   472  		{key10, ts5, false, false},
   473  		// For key11, we can't GC anything because ts1 isn't a delete.
   474  		{key11, ts1, false, false},
   475  		{key11, ts3, true, false},
   476  		{key11, ts4, true, false},
   477  		{key11, ts5, true, false},
   478  	}
   479  
   480  	for i, datum := range data {
   481  		if datum.del {
   482  			dArgs := deleteArgs(datum.key)
   483  			var txn *roachpb.Transaction
   484  			if datum.txn {
   485  				txn = newTransaction("test", datum.key, 1, tc.Clock())
   486  				// Overwrite the timestamps set by newTransaction().
   487  				txn.ReadTimestamp = datum.ts
   488  				txn.WriteTimestamp = datum.ts
   489  				txn.MinTimestamp = datum.ts
   490  				txn.DeprecatedOrigTimestamp = datum.ts
   491  				assignSeqNumsForReqs(txn, &dArgs)
   492  			}
   493  			if _, err := tc.SendWrappedWith(roachpb.Header{
   494  				Timestamp: datum.ts,
   495  				Txn:       txn,
   496  			}, &dArgs); err != nil {
   497  				t.Fatalf("%d: could not delete data: %+v", i, err)
   498  			}
   499  		} else {
   500  			pArgs := putArgs(datum.key, []byte("value"))
   501  			var txn *roachpb.Transaction
   502  			if datum.txn {
   503  				txn = newTransaction("test", datum.key, 1, tc.Clock())
   504  				// Overwrite the timestamps set by newTransaction().
   505  				txn.ReadTimestamp = datum.ts
   506  				txn.WriteTimestamp = datum.ts
   507  				txn.MinTimestamp = datum.ts
   508  				txn.DeprecatedOrigTimestamp = datum.ts
   509  				assignSeqNumsForReqs(txn, &pArgs)
   510  			}
   511  			if _, err := tc.SendWrappedWith(roachpb.Header{
   512  				Timestamp: datum.ts,
   513  				Txn:       txn,
   514  			}, &pArgs); err != nil {
   515  				t.Fatalf("%d: could not put data: %+v", i, err)
   516  			}
   517  		}
   518  	}
   519  
   520  	cfg := tc.gossip.GetSystemConfig()
   521  	if cfg == nil {
   522  		t.Fatal("config not set")
   523  	}
   524  
   525  	// The total size of the GC'able versions of the keys and values in Info.
   526  	// Key size: len("a") + MVCCVersionTimestampSize (13 bytes) = 14 bytes.
   527  	// Value size: len("value") + headerSize (5 bytes) = 10 bytes.
   528  	// key1 at ts1  (14 bytes) => "value" (10 bytes)
   529  	// key2 at ts1  (14 bytes) => "value" (10 bytes)
   530  	// key3 at ts1  (14 bytes) => "value" (10 bytes)
   531  	// key4 at ts1  (14 bytes) => "value" (10 bytes)
   532  	// key5 at ts1  (14 bytes) => "value" (10 bytes)
   533  	// key5 at ts2  (14 bytes) => delete (0 bytes)
   534  	// key10 at ts1 (14 bytes) => delete (0 bytes)
   535  	var expectedVersionsKeyBytes int64 = 7 * 14
   536  	var expectedVersionsValBytes int64 = 5 * 10
   537  
   538  	// Call Run with dummy functions to get current Info.
   539  	gcInfo, err := func() (gc.Info, error) {
   540  		snap := tc.repl.store.Engine().NewSnapshot()
   541  		desc := tc.repl.Desc()
   542  		defer snap.Close()
   543  
   544  		zone, err := cfg.GetZoneConfigForKey(desc.StartKey)
   545  		if err != nil {
   546  			t.Fatalf("could not find zone config for range %s: %+v", tc.repl, err)
   547  		}
   548  
   549  		now := tc.Clock().Now()
   550  		newThreshold := gc.CalculateThreshold(now, *zone.GC)
   551  		return gc.Run(ctx, desc, snap, now, newThreshold, *zone.GC,
   552  			gc.NoopGCer{},
   553  			func(ctx context.Context, intents []roachpb.Intent) error {
   554  				return nil
   555  			},
   556  			func(ctx context.Context, txn *roachpb.Transaction, intents []roachpb.LockUpdate) error {
   557  				return nil
   558  			})
   559  	}()
   560  	if err != nil {
   561  		t.Fatal(err)
   562  	}
   563  	if gcInfo.AffectedVersionsKeyBytes != expectedVersionsKeyBytes {
   564  		t.Errorf("expected total keys size: %d bytes; got %d bytes", expectedVersionsKeyBytes, gcInfo.AffectedVersionsKeyBytes)
   565  	}
   566  	if gcInfo.AffectedVersionsValBytes != expectedVersionsValBytes {
   567  		t.Errorf("expected total values size: %d bytes; got %d bytes", expectedVersionsValBytes, gcInfo.AffectedVersionsValBytes)
   568  	}
   569  
   570  	// Process through a scan queue.
   571  	gcQ := newGCQueue(tc.store, tc.gossip)
   572  	if err := gcQ.process(ctx, tc.repl, cfg); err != nil {
   573  		t.Fatal(err)
   574  	}
   575  
   576  	expKVs := []struct {
   577  		key roachpb.Key
   578  		ts  hlc.Timestamp
   579  	}{
   580  		{key1, ts5},
   581  		{key1, ts2},
   582  		{key2, ts5},
   583  		{key2, ts2m1},
   584  		{key3, hlc.Timestamp{}},
   585  		{key3, ts5},
   586  		{key3, ts2},
   587  		{key4, ts2},
   588  		{key6, hlc.Timestamp{}},
   589  		{key6, ts5},
   590  		{key6, ts1},
   591  		{key7, hlc.Timestamp{}},
   592  		{key7, ts4},
   593  		{key7, ts2},
   594  		{key8, ts2},
   595  		{key10, ts5},
   596  		{key10, ts4},
   597  		{key10, ts3},
   598  		{key11, ts5},
   599  		{key11, ts4},
   600  		{key11, ts3},
   601  		{key11, ts1},
   602  	}
   603  	// Read data directly from engine to avoid intent errors from MVCC.
   604  	// However, because the GC processing pushes transactions and
   605  	// resolves intents asynchronously, we use a SucceedsSoon loop.
   606  	testutils.SucceedsSoon(t, func() error {
   607  		kvs, err := storage.Scan(tc.store.Engine(), key1, keys.MaxKey, 0)
   608  		if err != nil {
   609  			return err
   610  		}
   611  		for i, kv := range kvs {
   612  			log.VEventf(ctx, 1, "%d: %s", i, kv.Key)
   613  		}
   614  		if len(kvs) != len(expKVs) {
   615  			return fmt.Errorf("expected length %d; got %d", len(expKVs), len(kvs))
   616  		}
   617  		for i, kv := range kvs {
   618  			if !kv.Key.Key.Equal(expKVs[i].key) {
   619  				return fmt.Errorf("%d: expected key %q; got %q", i, expKVs[i].key, kv.Key.Key)
   620  			}
   621  			if kv.Key.Timestamp != expKVs[i].ts {
   622  				return fmt.Errorf("%d: expected ts=%s; got %s", i, expKVs[i].ts, kv.Key.Timestamp)
   623  			}
   624  			log.VEventf(ctx, 2, "%d: %s", i, kv.Key)
   625  		}
   626  		return nil
   627  	})
   628  }
   629  
   630  func TestGCQueueTransactionTable(t *testing.T) {
   631  	defer leaktest.AfterTest(t)()
   632  	ctx := context.Background()
   633  
   634  	manual := hlc.NewManualClock(123)
   635  	tsc := TestStoreConfig(hlc.NewClock(manual.UnixNano, time.Nanosecond))
   636  	manual.Set(3 * 24 * time.Hour.Nanoseconds())
   637  
   638  	testTime := manual.UnixNano() + 2*time.Hour.Nanoseconds()
   639  	gcExpiration := testTime - kvserverbase.TxnCleanupThreshold.Nanoseconds()
   640  
   641  	type spec struct {
   642  		status      roachpb.TransactionStatus
   643  		orig        int64
   644  		hb          int64                     // last heartbeat (none if Timestamp{})
   645  		newStatus   roachpb.TransactionStatus // -1 for GCed
   646  		failResolve bool                      // do we want to fail resolves in this trial?
   647  		expResolve  bool                      // expect attempt at removing txn-persisted intents?
   648  		expAbortGC  bool                      // expect AbortSpan entries removed?
   649  	}
   650  	// Describes the state of the Txn table before the test.
   651  	// Many of the AbortSpan entries deleted wouldn't even be there, so don't
   652  	// be confused by that.
   653  	testCases := map[string]spec{
   654  		// Too young, should not touch.
   655  		"a": {
   656  			status:    roachpb.PENDING,
   657  			orig:      gcExpiration + 1,
   658  			newStatus: roachpb.PENDING,
   659  		},
   660  		// Old and pending, but still heartbeat (so no Push attempted; it
   661  		// would not succeed).
   662  		"b": {
   663  			status:    roachpb.PENDING,
   664  			orig:      1, // immaterial
   665  			hb:        gcExpiration + 1,
   666  			newStatus: roachpb.PENDING,
   667  		},
   668  		// Old, pending, and abandoned. Should push and abort it
   669  		// successfully, and GC it, along with resolving the intent. The
   670  		// AbortSpan is also cleaned up.
   671  		"c": {
   672  			status:     roachpb.PENDING,
   673  			orig:       gcExpiration - 1,
   674  			newStatus:  -1,
   675  			expResolve: true,
   676  			expAbortGC: true,
   677  		},
   678  		// Staging and fresh, so no action.
   679  		"d": {
   680  			status:    roachpb.STAGING,
   681  			orig:      gcExpiration + 1,
   682  			newStatus: roachpb.STAGING,
   683  		},
   684  		// Old and staging, but still heartbeat (so no Push attempted; it
   685  		// would not succeed).
   686  		"e": {
   687  			status:    roachpb.STAGING,
   688  			orig:      1, // immaterial
   689  			hb:        gcExpiration + 1,
   690  			newStatus: roachpb.STAGING,
   691  		},
   692  		// Old, staging, and abandoned. Should push it and hit an indeterminate
   693  		// commit error. Should successfully recover the transaction and GC it,
   694  		// along with resolving the intent.
   695  		"f": {
   696  			status:     roachpb.STAGING,
   697  			orig:       gcExpiration - 1,
   698  			newStatus:  -1,
   699  			expResolve: true,
   700  			expAbortGC: true,
   701  		},
   702  		// Old and aborted, should delete.
   703  		"g": {
   704  			status:     roachpb.ABORTED,
   705  			orig:       gcExpiration - 1,
   706  			newStatus:  -1,
   707  			expResolve: true,
   708  			expAbortGC: true,
   709  		},
   710  		// Committed and fresh, so no action.
   711  		"h": {
   712  			status:    roachpb.COMMITTED,
   713  			orig:      gcExpiration + 1,
   714  			newStatus: roachpb.COMMITTED,
   715  		},
   716  		// Committed and old. It has an intent (like all tests here), which is
   717  		// resolvable and hence we can GC.
   718  		"i": {
   719  			status:     roachpb.COMMITTED,
   720  			orig:       gcExpiration - 1,
   721  			newStatus:  -1,
   722  			expResolve: true,
   723  			expAbortGC: true,
   724  		},
   725  		// Same as the previous one, but we've rigged things so that the intent
   726  		// resolution here will fail and consequently no GC is expected.
   727  		"j": {
   728  			status:      roachpb.COMMITTED,
   729  			orig:        gcExpiration - 1,
   730  			newStatus:   roachpb.COMMITTED,
   731  			failResolve: true,
   732  			expResolve:  true,
   733  			expAbortGC:  true,
   734  		},
   735  	}
   736  
   737  	var resolved syncmap.Map
   738  	// Set the MaxIntentResolutionBatchSize to 1 so that the injected error for
   739  	// resolution of "g" does not lead to the failure of resolution of "f".
   740  	// The need to set this highlights the "fate sharing" aspect of batching
   741  	// intent resolution.
   742  	tsc.TestingKnobs.IntentResolverKnobs.MaxIntentResolutionBatchSize = 1
   743  	tsc.TestingKnobs.EvalKnobs.TestingEvalFilter =
   744  		func(filterArgs kvserverbase.FilterArgs) *roachpb.Error {
   745  			if resArgs, ok := filterArgs.Req.(*roachpb.ResolveIntentRequest); ok {
   746  				id := string(resArgs.IntentTxn.Key)
   747  				// Only count finalizing intent resolution attempts in `resolved`.
   748  				if resArgs.Status != roachpb.PENDING {
   749  					var spans []roachpb.Span
   750  					val, ok := resolved.Load(id)
   751  					if ok {
   752  						spans = val.([]roachpb.Span)
   753  					}
   754  					spans = append(spans, roachpb.Span{
   755  						Key:    resArgs.Key,
   756  						EndKey: resArgs.EndKey,
   757  					})
   758  					resolved.Store(id, spans)
   759  				}
   760  				// We've special cased one test case. Note that the intent is still
   761  				// counted in `resolved`.
   762  				if testCases[id].failResolve {
   763  					return roachpb.NewErrorWithTxn(errors.Errorf("boom"), filterArgs.Hdr.Txn)
   764  				}
   765  			}
   766  			return nil
   767  		}
   768  	tc := testContext{manualClock: manual}
   769  	stopper := stop.NewStopper()
   770  	defer stopper.Stop(ctx)
   771  	tc.StartWithStoreConfig(t, stopper, tsc)
   772  	manual.Set(testTime)
   773  
   774  	outsideKey := tc.repl.Desc().EndKey.Next().AsRawKey()
   775  	testIntents := []roachpb.Span{{Key: roachpb.Key("intent")}}
   776  
   777  	txns := map[string]roachpb.Transaction{}
   778  	for strKey, test := range testCases {
   779  		baseKey := roachpb.Key(strKey)
   780  		txnClock := hlc.NewClock(hlc.NewManualClock(test.orig).UnixNano, time.Nanosecond)
   781  		txn := newTransaction("txn1", baseKey, 1, txnClock)
   782  		txn.Status = test.status
   783  		txn.LockSpans = testIntents
   784  		if test.hb > 0 {
   785  			txn.LastHeartbeat = hlc.Timestamp{WallTime: test.hb}
   786  		}
   787  		txns[strKey] = *txn
   788  		for _, addrKey := range []roachpb.Key{baseKey, outsideKey} {
   789  			key := keys.TransactionKey(addrKey, txn.ID)
   790  			if err := storage.MVCCPutProto(ctx, tc.engine, nil, key, hlc.Timestamp{}, nil, txn); err != nil {
   791  				t.Fatal(err)
   792  			}
   793  		}
   794  		entry := roachpb.AbortSpanEntry{Key: txn.Key, Timestamp: txn.LastActive()}
   795  		if err := tc.repl.abortSpan.Put(ctx, tc.engine, nil, txn.ID, &entry); err != nil {
   796  			t.Fatal(err)
   797  		}
   798  	}
   799  
   800  	// Run GC.
   801  	gcQ := newGCQueue(tc.store, tc.gossip)
   802  	cfg := tc.gossip.GetSystemConfig()
   803  	if cfg == nil {
   804  		t.Fatal("config not set")
   805  	}
   806  
   807  	if err := gcQ.process(ctx, tc.repl, cfg); err != nil {
   808  		t.Fatal(err)
   809  	}
   810  
   811  	testutils.SucceedsSoon(t, func() error {
   812  		for strKey, sp := range testCases {
   813  			txn := &roachpb.Transaction{}
   814  			txnKey := keys.TransactionKey(roachpb.Key(strKey), txns[strKey].ID)
   815  			txnTombstoneTSCacheKey := transactionTombstoneMarker(
   816  				roachpb.Key(strKey), txns[strKey].ID)
   817  			ok, err := storage.MVCCGetProto(ctx, tc.engine, txnKey, hlc.Timestamp{}, txn,
   818  				storage.MVCCGetOptions{})
   819  			if err != nil {
   820  				return err
   821  			}
   822  			if expGC := (sp.newStatus == -1); expGC {
   823  				if expGC != !ok {
   824  					return fmt.Errorf("%s: expected gc: %t, but found %s\n%s", strKey, expGC, txn, roachpb.Key(strKey))
   825  				}
   826  				// If the transaction record was GCed and didn't begin the test
   827  				// as finalized, verify that the timestamp cache was
   828  				// updated (by the corresponding PushTxn that marked the record
   829  				// as ABORTED) to prevent it from being created again in the
   830  				// future.
   831  				if !sp.status.IsFinalized() {
   832  					tombstoneTimestamp, _ := tc.store.tsCache.GetMax(
   833  						txnTombstoneTSCacheKey, nil /* end */)
   834  					if min := (hlc.Timestamp{WallTime: sp.orig}); tombstoneTimestamp.Less(min) {
   835  						return fmt.Errorf("%s: expected tscache entry for tombstone key to be >= %s, "+
   836  							"but found %s", strKey, min, tombstoneTimestamp)
   837  					}
   838  				}
   839  			} else if sp.newStatus != txn.Status {
   840  				return fmt.Errorf("%s: expected status %s, but found %s", strKey, sp.newStatus, txn.Status)
   841  			}
   842  			var expIntents []roachpb.Span
   843  			if sp.expResolve {
   844  				expIntents = testIntents
   845  			}
   846  			var spans []roachpb.Span
   847  			val, ok := resolved.Load(strKey)
   848  			if ok {
   849  				spans = val.([]roachpb.Span)
   850  			}
   851  			if !reflect.DeepEqual(spans, expIntents) {
   852  				return fmt.Errorf("%s: unexpected intent resolutions:\nexpected: %s\nobserved: %s", strKey, expIntents, spans)
   853  			}
   854  			entry := &roachpb.AbortSpanEntry{}
   855  			abortExists, err := tc.repl.abortSpan.Get(ctx, tc.store.Engine(), txns[strKey].ID, entry)
   856  			if err != nil {
   857  				t.Fatal(err)
   858  			}
   859  			if abortExists == sp.expAbortGC {
   860  				return fmt.Errorf("%s: expected AbortSpan gc: %t, found %+v", strKey, sp.expAbortGC, entry)
   861  			}
   862  		}
   863  		return nil
   864  	})
   865  
   866  	outsideTxnPrefix := keys.TransactionKey(outsideKey, uuid.UUID{})
   867  	outsideTxnPrefixEnd := keys.TransactionKey(outsideKey.Next(), uuid.UUID{})
   868  	var count int
   869  	if _, err := storage.MVCCIterate(ctx, tc.store.Engine(), outsideTxnPrefix, outsideTxnPrefixEnd, hlc.Timestamp{},
   870  		storage.MVCCScanOptions{}, func(roachpb.KeyValue) (bool, error) {
   871  			count++
   872  			return false, nil
   873  		}); err != nil {
   874  		t.Fatal(err)
   875  	}
   876  	if exp := len(testCases); exp != count {
   877  		t.Fatalf("expected the %d external transaction entries to remain untouched, "+
   878  			"but only %d are left", exp, count)
   879  	}
   880  
   881  	batch := tc.engine.NewSnapshot()
   882  	defer batch.Close()
   883  	tc.repl.raftMu.Lock()
   884  	tc.repl.mu.Lock()
   885  	tc.repl.assertStateLocked(ctx, batch) // check that in-mem and on-disk state were updated
   886  	tc.repl.mu.Unlock()
   887  	tc.repl.raftMu.Unlock()
   888  }
   889  
   890  // TestGCQueueIntentResolution verifies intent resolution with many
   891  // intents spanning just two transactions.
   892  func TestGCQueueIntentResolution(t *testing.T) {
   893  	defer leaktest.AfterTest(t)()
   894  	ctx := context.Background()
   895  	tc := testContext{}
   896  	stopper := stop.NewStopper()
   897  	defer stopper.Stop(ctx)
   898  	tc.Start(t, stopper)
   899  
   900  	tc.manualClock.Set(48 * 60 * 60 * 1e9) // 2d past the epoch
   901  	now := tc.Clock().Now().WallTime
   902  
   903  	txns := []*roachpb.Transaction{
   904  		newTransaction("txn1", roachpb.Key("0-0"), 1, tc.Clock()),
   905  		newTransaction("txn2", roachpb.Key("1-0"), 1, tc.Clock()),
   906  	}
   907  	intentResolveTS := makeTS(now-gc.IntentAgeThreshold.Nanoseconds(), 0)
   908  	txns[0].ReadTimestamp = intentResolveTS
   909  	txns[0].WriteTimestamp = intentResolveTS
   910  	// The MinTimestamp is used by pushers that don't find a transaction record to
   911  	// infer when the coordinator was alive.
   912  	txns[0].MinTimestamp = intentResolveTS
   913  	txns[1].ReadTimestamp = intentResolveTS
   914  	txns[1].WriteTimestamp = intentResolveTS
   915  	txns[1].MinTimestamp = intentResolveTS
   916  
   917  	// Two transactions.
   918  	for i := 0; i < 2; i++ {
   919  		// 5 puts per transaction.
   920  		for j := 0; j < 5; j++ {
   921  			pArgs := putArgs(roachpb.Key(fmt.Sprintf("%d-%d", i, j)), []byte("value"))
   922  			assignSeqNumsForReqs(txns[i], &pArgs)
   923  			if _, err := tc.SendWrappedWith(roachpb.Header{
   924  				Txn: txns[i],
   925  			}, &pArgs); err != nil {
   926  				t.Fatalf("%d: could not put data: %+v", i, err)
   927  			}
   928  		}
   929  	}
   930  
   931  	// Process through GC queue.
   932  	cfg := tc.gossip.GetSystemConfig()
   933  	if cfg == nil {
   934  		t.Fatal("config not set")
   935  	}
   936  	gcQ := newGCQueue(tc.store, tc.gossip)
   937  	if err := gcQ.process(ctx, tc.repl, cfg); err != nil {
   938  		t.Fatal(err)
   939  	}
   940  
   941  	// Iterate through all values to ensure intents have been fully resolved.
   942  	// This must be done in a SucceedsSoon loop because intent resolution
   943  	// is initiated asynchronously from the GC queue.
   944  	testutils.SucceedsSoon(t, func() error {
   945  		meta := &enginepb.MVCCMetadata{}
   946  		return tc.store.Engine().Iterate(roachpb.KeyMin, roachpb.KeyMax,
   947  			func(kv storage.MVCCKeyValue) (bool, error) {
   948  				if !kv.Key.IsValue() {
   949  					if err := protoutil.Unmarshal(kv.Value, meta); err != nil {
   950  						return false, err
   951  					}
   952  					if meta.Txn != nil {
   953  						return false, errors.Errorf("non-nil Txn after GC for key %s", kv.Key)
   954  					}
   955  				}
   956  				return false, nil
   957  			})
   958  	})
   959  }
   960  
   961  func TestGCQueueLastProcessedTimestamps(t *testing.T) {
   962  	defer leaktest.AfterTest(t)()
   963  	ctx := context.Background()
   964  	tc := testContext{}
   965  	stopper := stop.NewStopper()
   966  	defer stopper.Stop(ctx)
   967  	tc.Start(t, stopper)
   968  
   969  	// Create two last processed times both at the range start key and
   970  	// also at some mid-point key in order to simulate a merge.
   971  	// Two transactions.
   972  	lastProcessedVals := []struct {
   973  		key   roachpb.Key
   974  		expGC bool
   975  	}{
   976  		{keys.QueueLastProcessedKey(roachpb.RKeyMin, "timeSeriesMaintenance"), false},
   977  		{keys.QueueLastProcessedKey(roachpb.RKeyMin, "replica consistency checker"), false},
   978  		{keys.QueueLastProcessedKey(roachpb.RKey("a"), "timeSeriesMaintenance"), true},
   979  		{keys.QueueLastProcessedKey(roachpb.RKey("b"), "replica consistency checker"), true},
   980  	}
   981  
   982  	ts := tc.Clock().Now()
   983  	for _, lpv := range lastProcessedVals {
   984  		if err := storage.MVCCPutProto(ctx, tc.engine, nil, lpv.key, hlc.Timestamp{}, nil, &ts); err != nil {
   985  			t.Fatal(err)
   986  		}
   987  	}
   988  
   989  	cfg := tc.gossip.GetSystemConfig()
   990  	if cfg == nil {
   991  		t.Fatal("config not set")
   992  	}
   993  
   994  	// Process through a scan queue.
   995  	gcQ := newGCQueue(tc.store, tc.gossip)
   996  	if err := gcQ.process(ctx, tc.repl, cfg); err != nil {
   997  		t.Fatal(err)
   998  	}
   999  
  1000  	// Verify GC.
  1001  	testutils.SucceedsSoon(t, func() error {
  1002  		for _, lpv := range lastProcessedVals {
  1003  			ok, err := storage.MVCCGetProto(ctx, tc.engine, lpv.key, hlc.Timestamp{}, &ts,
  1004  				storage.MVCCGetOptions{})
  1005  			if err != nil {
  1006  				return err
  1007  			}
  1008  			if ok == lpv.expGC {
  1009  				return errors.Errorf("expected GC of %s: %t; got %t", lpv.key, lpv.expGC, ok)
  1010  			}
  1011  		}
  1012  		return nil
  1013  	})
  1014  }
  1015  
  1016  // TestGCQueueChunkRequests verifies that many intents are chunked
  1017  // into separate batches. This is verified both for many different
  1018  // keys and also for many different versions of keys.
  1019  func TestGCQueueChunkRequests(t *testing.T) {
  1020  	defer leaktest.AfterTest(t)()
  1021  	ctx := context.Background()
  1022  
  1023  	var gcRequests int32
  1024  	manual := hlc.NewManualClock(123)
  1025  	tsc := TestStoreConfig(hlc.NewClock(manual.UnixNano, time.Nanosecond))
  1026  	tsc.TestingKnobs.EvalKnobs.TestingEvalFilter =
  1027  		func(filterArgs kvserverbase.FilterArgs) *roachpb.Error {
  1028  			if _, ok := filterArgs.Req.(*roachpb.GCRequest); ok {
  1029  				atomic.AddInt32(&gcRequests, 1)
  1030  				return nil
  1031  			}
  1032  			return nil
  1033  		}
  1034  	tc := testContext{manualClock: manual}
  1035  	stopper := stop.NewStopper()
  1036  	defer stopper.Stop(ctx)
  1037  	tc.StartWithStoreConfig(t, stopper, tsc)
  1038  
  1039  	const keyCount = 100
  1040  	if gc.KeyVersionChunkBytes%keyCount != 0 {
  1041  		t.Fatalf("expected gcKeyVersionChunkBytes to be a multiple of %d", keyCount)
  1042  	}
  1043  	// Reduce the key size by MVCCVersionTimestampSize (13 bytes) to prevent batch overflow.
  1044  	// This is due to MVCCKey.EncodedSize(), which returns the full size of the encoded key.
  1045  	const keySize = (gc.KeyVersionChunkBytes / keyCount) - 13
  1046  	// Create a format string for creating version keys of exactly
  1047  	// length keySize.
  1048  	fmtStr := fmt.Sprintf("%%0%dd", keySize)
  1049  
  1050  	// First write 2 * gcKeyVersionChunkBytes different keys (each with two versions).
  1051  	ba1, ba2 := roachpb.BatchRequest{}, roachpb.BatchRequest{}
  1052  	for i := 0; i < 2*keyCount; i++ {
  1053  		// Create keys which are
  1054  		key := roachpb.Key(fmt.Sprintf(fmtStr, i))
  1055  		pArgs := putArgs(key, []byte("value1"))
  1056  		ba1.Add(&pArgs)
  1057  		pArgs = putArgs(key, []byte("value2"))
  1058  		ba2.Add(&pArgs)
  1059  	}
  1060  	ba1.Header = roachpb.Header{Timestamp: tc.Clock().Now()}
  1061  	if _, pErr := tc.Sender().Send(ctx, ba1); pErr != nil {
  1062  		t.Fatal(pErr)
  1063  	}
  1064  	ba2.Header = roachpb.Header{Timestamp: tc.Clock().Now()}
  1065  	if _, pErr := tc.Sender().Send(ctx, ba2); pErr != nil {
  1066  		t.Fatal(pErr)
  1067  	}
  1068  
  1069  	// Next write 2 keys, the first with keyCount different GC'able
  1070  	// versions, and the second with 2*keyCount GC'able versions.
  1071  	key1 := roachpb.Key(fmt.Sprintf(fmtStr, 2*keyCount))
  1072  	key2 := roachpb.Key(fmt.Sprintf(fmtStr, 2*keyCount+1))
  1073  	for i := 0; i < 2*keyCount+1; i++ {
  1074  		ba := roachpb.BatchRequest{}
  1075  		// Only write keyCount+1 versions of key1.
  1076  		if i < keyCount+1 {
  1077  			pArgs1 := putArgs(key1, []byte(fmt.Sprintf("value%04d", i)))
  1078  			ba.Add(&pArgs1)
  1079  		}
  1080  		// Write all 2*keyCount+1 versions of key2 to verify we'll
  1081  		// tackle key2 in two separate batches.
  1082  		pArgs2 := putArgs(key2, []byte(fmt.Sprintf("value%04d", i)))
  1083  		ba.Add(&pArgs2)
  1084  		ba.Header = roachpb.Header{Timestamp: tc.Clock().Now()}
  1085  		if _, pErr := tc.Sender().Send(ctx, ba); pErr != nil {
  1086  			t.Fatal(pErr)
  1087  		}
  1088  	}
  1089  
  1090  	// Forward the clock past the default GC time.
  1091  	cfg := tc.gossip.GetSystemConfig()
  1092  	if cfg == nil {
  1093  		t.Fatal("config not set")
  1094  	}
  1095  	zone, err := cfg.GetZoneConfigForKey(roachpb.RKey("key"))
  1096  	if err != nil {
  1097  		t.Fatalf("could not find zone config for range %s", err)
  1098  	}
  1099  	tc.manualClock.Increment(int64(zone.GC.TTLSeconds)*1e9 + 1)
  1100  	gcQ := newGCQueue(tc.store, tc.gossip)
  1101  	if err := gcQ.process(ctx, tc.repl, cfg); err != nil {
  1102  		t.Fatal(err)
  1103  	}
  1104  
  1105  	// We wrote two batches worth of keys spread out, and two keys that
  1106  	// each have enough old versions to fill a whole batch each in the
  1107  	// first case, and two whole batches in the second, adding up to
  1108  	// five batches. There is also a first batch which sets the GC
  1109  	// thresholds, making six total batches.
  1110  	if a, e := atomic.LoadInt32(&gcRequests), int32(6); a != e {
  1111  		t.Errorf("expected %d gc requests; got %d", e, a)
  1112  	}
  1113  }