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 }