github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/closedts/storage/storage_test.go (about) 1 // Copyright 2018 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 storage 12 13 import ( 14 "fmt" 15 "math/rand" 16 "testing" 17 "time" 18 19 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/closedts/ctpb" 20 "github.com/cockroachdb/cockroach/pkg/roachpb" 21 "github.com/cockroachdb/cockroach/pkg/util/hlc" 22 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 23 "github.com/cockroachdb/cockroach/pkg/util/randutil" 24 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 25 "github.com/cockroachdb/errors" 26 "golang.org/x/sync/errgroup" 27 ) 28 29 func ExampleSingleStorage() { 30 s := NewMemStorage(10*time.Second, 4) 31 fmt.Println("The empty storage renders as below:") 32 fmt.Println(s) 33 34 fmt.Println("After adding the following entry:") 35 e1 := ctpb.Entry{ 36 Full: true, 37 ClosedTimestamp: hlc.Timestamp{WallTime: 123e9}, 38 MLAI: map[roachpb.RangeID]ctpb.LAI{ 39 1: 1000, 40 9: 2000, 41 }, 42 } 43 fmt.Println(e1) 44 s.Add(e1) 45 fmt.Println("the result is:") 46 fmt.Println(s) 47 fmt.Println("Note how the most recent bucket picked up the update.") 48 49 fmt.Println("A new update comes in only two seconds later:") 50 e2 := ctpb.Entry{ 51 ClosedTimestamp: hlc.Timestamp{WallTime: 125e9}, 52 MLAI: map[roachpb.RangeID]ctpb.LAI{ 53 1: 1001, 54 7: 12, 55 }, 56 } 57 fmt.Println(e2) 58 s.Add(e2) 59 fmt.Println("The first bucket now contains the union of both updates.") 60 fmt.Println("The second bucket holds on to the previous value of the first.") 61 fmt.Println("The remaining buckets are unchanged. The best we could do is") 62 fmt.Println("give them identical copies of the second, but that's nonsense.") 63 fmt.Println(s) 64 65 fmt.Println("Another update, another eight seconds later:") 66 e3 := ctpb.Entry{ 67 ClosedTimestamp: hlc.Timestamp{WallTime: 133e9}, 68 MLAI: map[roachpb.RangeID]ctpb.LAI{ 69 9: 2020, 70 1: 999, 71 }, 72 } 73 fmt.Println(e3) 74 s.Add(e3) 75 fmt.Println("Note how the second bucket didn't rotate, for it is not yet") 76 fmt.Println("older than 10s. Note also how the first bucket ignores the") 77 fmt.Println("downgrade for r1; these can occur in practice.") 78 fmt.Println(s) 79 80 fmt.Println("Half a second later, with the next update, it will rotate:") 81 e4 := ctpb.Entry{ 82 ClosedTimestamp: hlc.Timestamp{WallTime: 133e9 + 1e9/2}, 83 MLAI: map[roachpb.RangeID]ctpb.LAI{ 84 7: 17, 85 8: 711, 86 }, 87 } 88 fmt.Println(e4) 89 s.Add(e4) 90 fmt.Println("Consequently we now see the third bucket fill up.") 91 fmt.Println(s) 92 93 fmt.Println("Next update arrives a whopping 46.5s later (why not).") 94 e5 := ctpb.Entry{ 95 ClosedTimestamp: hlc.Timestamp{WallTime: 180e9}, 96 MLAI: map[roachpb.RangeID]ctpb.LAI{ 97 1: 1004, 98 7: 19, 99 2: 929922, 100 }, 101 } 102 fmt.Println(e5) 103 s.Add(e5) 104 fmt.Println("The second bucket rotated, but due to the sparseness of updates,") 105 fmt.Println("it's still above its target age and will rotate again next time.") 106 fmt.Println("The same is true for the remaining buckets.") 107 fmt.Println(s) 108 109 fmt.Println("Another five seconds later, another update:") 110 e6 := ctpb.Entry{ 111 ClosedTimestamp: hlc.Timestamp{WallTime: 185e9}, 112 MLAI: map[roachpb.RangeID]ctpb.LAI{ 113 3: 1771, 114 }, 115 } 116 fmt.Println(e6) 117 s.Add(e6) 118 fmt.Println("All buckets rotate, but the third and fourth remain over target age.") 119 fmt.Println("This would resolve itself if reasonably spaced updates kept coming in.") 120 fmt.Println(s) 121 122 fmt.Println("Finally, when the storage is cleared, all buckets are reset.") 123 s.Clear() 124 fmt.Println(s) 125 126 // Output: 127 // The empty storage renders as below: 128 // +--+---------------------+----------------------+----------------------+----------------------+ 129 // 0,0 age=0s (target 0,0 age=0s (target 0,0 age=0s (target 0,0 age=0s (target 130 // ≤0s) epoch=0 ≤10s) epoch=0 ≤20s) epoch=0 ≤40s) epoch=0 131 // +--+---------------------+----------------------+----------------------+----------------------+ 132 // +--+---------------------+----------------------+----------------------+----------------------+ 133 // 134 // After adding the following entry: 135 // CT: 123.000000000,0 @ Epoch 0 136 // Full: true 137 // MLAI: r1: 1000, r9: 2000 138 // 139 // the result is: 140 // +----+---------------------+------------------------+------------------------+------------------------+ 141 // 123.000000000,0 0,0 age=2m3s (target 0,0 age=2m3s (target 0,0 age=2m3s (target 142 // age=0s (target ≤0s) ≤10s) epoch=0 ≤20s) epoch=0 ≤40s) epoch=0 143 // epoch=0 144 // +----+---------------------+------------------------+------------------------+------------------------+ 145 // r1 1000 146 // r9 2000 147 // +----+---------------------+------------------------+------------------------+------------------------+ 148 // 149 // Note how the most recent bucket picked up the update. 150 // A new update comes in only two seconds later: 151 // CT: 125.000000000,0 @ Epoch 0 152 // Full: false 153 // MLAI: r1: 1001, r7: 12 154 // 155 // The first bucket now contains the union of both updates. 156 // The second bucket holds on to the previous value of the first. 157 // The remaining buckets are unchanged. The best we could do is 158 // give them identical copies of the second, but that's nonsense. 159 // +----+---------------------+----------------------+------------------------+------------------------+ 160 // 125.000000000,0 123.000000000,0 0,0 age=2m5s (target 0,0 age=2m5s (target 161 // age=0s (target ≤0s) age=2s (target ≤10s) ≤20s) epoch=0 ≤40s) epoch=0 162 // epoch=0 epoch=0 163 // +----+---------------------+----------------------+------------------------+------------------------+ 164 // r1 1001 1000 165 // r7 12 166 // r9 2000 2000 167 // +----+---------------------+----------------------+------------------------+------------------------+ 168 // 169 // Another update, another eight seconds later: 170 // CT: 133.000000000,0 @ Epoch 0 171 // Full: false 172 // MLAI: r1: 999, r9: 2020 173 // 174 // Note how the second bucket didn't rotate, for it is not yet 175 // older than 10s. Note also how the first bucket ignores the 176 // downgrade for r1; these can occur in practice. 177 // +----+---------------------+-----------------------+-------------------------+-------------------------+ 178 // 133.000000000,0 123.000000000,0 0,0 age=2m13s (target 0,0 age=2m13s (target 179 // age=0s (target ≤0s) age=10s (target ≤10s) ≤20s) epoch=0 ≤40s) epoch=0 180 // epoch=0 epoch=0 181 // +----+---------------------+-----------------------+-------------------------+-------------------------+ 182 // r1 1001 1000 183 // r7 12 184 // r9 2020 2000 185 // +----+---------------------+-----------------------+-------------------------+-------------------------+ 186 // 187 // Half a second later, with the next update, it will rotate: 188 // CT: 133.500000000,0 @ Epoch 0 189 // Full: false 190 // MLAI: r7: 17, r8: 711 191 // 192 // Consequently we now see the third bucket fill up. 193 // +----+---------------------+-------------------------+-------------------------+---------------------------+ 194 // 133.500000000,0 133.000000000,0 123.000000000,0 0,0 age=2m13.5s (target 195 // age=0s (target ≤0s) age=500ms (target ≤10s) age=10.5s (target ≤20s) ≤40s) epoch=0 196 // epoch=0 epoch=0 epoch=0 197 // +----+---------------------+-------------------------+-------------------------+---------------------------+ 198 // r1 1001 1001 1000 199 // r7 17 12 200 // r8 711 201 // r9 2020 2020 2000 202 // +----+---------------------+-------------------------+-------------------------+---------------------------+ 203 // 204 // Next update arrives a whopping 46.5s later (why not). 205 // CT: 180.000000000,0 @ Epoch 0 206 // Full: false 207 // MLAI: r1: 1004, r2: 929922, r7: 19 208 // 209 // The second bucket rotated, but due to the sparseness of updates, 210 // it's still above its target age and will rotate again next time. 211 // The same is true for the remaining buckets. 212 // +----+---------------------+-------------------------+-----------------------+-----------------------+ 213 // 180.000000000,0 133.500000000,0 133.000000000,0 123.000000000,0 214 // age=0s (target ≤0s) age=46.5s (target ≤10s) age=47s (target ≤20s) age=57s (target ≤40s) 215 // epoch=0 epoch=0 epoch=0 epoch=0 216 // +----+---------------------+-------------------------+-----------------------+-----------------------+ 217 // r1 1004 1001 1001 1000 218 // r2 929922 219 // r7 19 17 12 220 // r8 711 711 221 // r9 2020 2020 2020 2000 222 // +----+---------------------+-------------------------+-----------------------+-----------------------+ 223 // 224 // Another five seconds later, another update: 225 // CT: 185.000000000,0 @ Epoch 0 226 // Full: false 227 // MLAI: r3: 1771 228 // 229 // All buckets rotate, but the third and fourth remain over target age. 230 // This would resolve itself if reasonably spaced updates kept coming in. 231 // +----+---------------------+----------------------+-------------------------+-----------------------+ 232 // 185.000000000,0 180.000000000,0 133.500000000,0 133.000000000,0 233 // age=0s (target ≤0s) age=5s (target ≤10s) age=51.5s (target ≤20s) age=52s (target ≤40s) 234 // epoch=0 epoch=0 epoch=0 epoch=0 235 // +----+---------------------+----------------------+-------------------------+-----------------------+ 236 // r1 1004 1004 1001 1001 237 // r2 929922 929922 238 // r3 1771 239 // r7 19 19 17 12 240 // r8 711 711 711 241 // r9 2020 2020 2020 2020 242 // +----+---------------------+----------------------+-------------------------+-----------------------+ 243 // 244 // Finally, when the storage is cleared, all buckets are reset. 245 // +--+---------------------+----------------------+----------------------+----------------------+ 246 // 0,0 age=0s (target 0,0 age=0s (target 0,0 age=0s (target 0,0 age=0s (target 247 // ≤0s) epoch=0 ≤10s) epoch=0 ≤20s) epoch=0 ≤40s) epoch=0 248 // +--+---------------------+----------------------+----------------------+----------------------+ 249 // +--+---------------------+----------------------+----------------------+----------------------+ 250 } 251 252 func ExampleMultiStorage_epoch() { 253 ms := NewMultiStorage(func() SingleStorage { 254 return NewMemStorage(time.Millisecond, 2) 255 }) 256 257 e1 := ctpb.Entry{ 258 Epoch: 10, 259 ClosedTimestamp: hlc.Timestamp{WallTime: 1e9}, 260 MLAI: map[roachpb.RangeID]ctpb.LAI{ 261 9: 17, 262 }, 263 } 264 fmt.Println("First, the following entry is added:") 265 fmt.Println(e1) 266 ms.Add(1, e1) 267 fmt.Println(ms) 268 269 fmt.Println("The epoch changes. It can only increase, for we receive Entries in a fixed order.") 270 e2 := ctpb.Entry{ 271 Epoch: 11, 272 ClosedTimestamp: hlc.Timestamp{WallTime: 2e9}, 273 MLAI: map[roachpb.RangeID]ctpb.LAI{ 274 9: 18, 275 10: 99, 276 }, 277 } 278 ms.Add(1, e2) 279 fmt.Println(e2) 280 fmt.Println(ms) 281 282 fmt.Println("If it *did* decrease, a higher level component should trigger an assertion.") 283 fmt.Println("The storage itself will simply ignore such updates:") 284 e3 := ctpb.Entry{ 285 Epoch: 8, 286 ClosedTimestamp: hlc.Timestamp{WallTime: 3e9}, 287 MLAI: map[roachpb.RangeID]ctpb.LAI{ 288 9: 19, 289 10: 199, 290 }, 291 } 292 fmt.Println(e3) 293 ms.Add(1, e3) 294 fmt.Println(ms) 295 296 // Output: 297 // First, the following entry is added: 298 // CT: 1.000000000,0 @ Epoch 10 299 // Full: false 300 // MLAI: r9: 17 301 // 302 // ***** n1 ***** 303 // +----+---------------------+----------------------+ 304 // 1.000000000,0 0,0 age=1s (target 305 // age=0s (target ≤0s) ≤1ms) epoch=0 306 // epoch=10 307 // +----+---------------------+----------------------+ 308 // r9 17 309 // +----+---------------------+----------------------+ 310 // 311 // The epoch changes. It can only increase, for we receive Entries in a fixed order. 312 // CT: 2.000000000,0 @ Epoch 11 313 // Full: false 314 // MLAI: r9: 18, r10: 99 315 // 316 // ***** n1 ***** 317 // +-----+---------------------+----------------------+ 318 // 2.000000000,0 1.000000000,0 319 // age=0s (target ≤0s) age=1s (target ≤1ms) 320 // epoch=11 epoch=10 321 // +-----+---------------------+----------------------+ 322 // r9 18 17 323 // r10 99 324 // +-----+---------------------+----------------------+ 325 // 326 // If it *did* decrease, a higher level component should trigger an assertion. 327 // The storage itself will simply ignore such updates: 328 // CT: 3.000000000,0 @ Epoch 8 329 // Full: false 330 // MLAI: r9: 19, r10: 199 331 // 332 // ***** n1 ***** 333 // +-----+---------------------+----------------------+ 334 // 2.000000000,0 2.000000000,0 335 // age=0s (target ≤0s) age=0s (target ≤1ms) 336 // epoch=11 epoch=11 337 // +-----+---------------------+----------------------+ 338 // r9 18 18 339 // r10 99 99 340 // +-----+---------------------+----------------------+ 341 } 342 343 func TestZeroValueGetsStored(t *testing.T) { 344 defer leaktest.AfterTest(t)() 345 // This test ensures that a zero values MLAI is stored for an epoch especially 346 // after we've already stored a non-zero MLAI for a different range in the 347 // same epoch. See #32904. 348 ms := NewMultiStorage(func() SingleStorage { 349 return NewMemStorage(time.Millisecond, 10) 350 }) 351 e := ctpb.Entry{ 352 Epoch: 1, 353 ClosedTimestamp: hlc.Timestamp{WallTime: timeutil.Now().UnixNano()}, 354 MLAI: map[roachpb.RangeID]ctpb.LAI{1: 1}, 355 } 356 ms.Add(1, e) 357 e.ClosedTimestamp.WallTime++ 358 r := roachpb.RangeID(2) 359 e.MLAI = map[roachpb.RangeID]ctpb.LAI{r: 0} 360 ms.Add(1, e) 361 var seen bool 362 ms.VisitDescending(1, func(e ctpb.Entry) (done bool) { 363 for rr, mlai := range e.MLAI { 364 if rr == r && mlai == 0 { 365 seen = true 366 return true 367 } 368 } 369 return false 370 }) 371 if !seen { 372 t.Fatalf("Failed to see added zero value MLAI for range %v", r) 373 } 374 } 375 376 // TestConcurrent runs a very basic sanity check against a Storage, verifiying 377 // that the bucketed Entries don't regress in obvious ways. 378 func TestConcurrent(t *testing.T) { 379 defer leaktest.AfterTest(t)() 380 381 ms := NewMultiStorage(func() SingleStorage { 382 return NewMemStorage(time.Millisecond, 10) 383 }) 384 385 var g errgroup.Group 386 387 const ( 388 iters = 10 389 numNodes = roachpb.NodeID(2) 390 numRanges = roachpb.RangeID(3) 391 numReadersPerNode = 3 392 numWritersPerNode = 3 393 ) 394 395 // concurrently add and read from storage 396 // after add: needs to be visible to future read 397 // read ts never regresses 398 globalRand, seed := randutil.NewPseudoRand() 399 t.Log("seed is", seed) 400 401 for i := 0; i < numWritersPerNode; i++ { 402 for nodeID := roachpb.NodeID(1); nodeID <= numNodes; nodeID++ { 403 nodeID := nodeID // goroutine-local copy 404 for i := 0; i < iters; i++ { 405 r := rand.New(rand.NewSource(globalRand.Int63())) 406 m := make(map[roachpb.RangeID]ctpb.LAI) 407 for rangeID := roachpb.RangeID(1); rangeID < numRanges; rangeID++ { 408 if r.Intn(int(numRanges)) == 0 { 409 continue 410 } 411 m[rangeID] = ctpb.LAI(rand.Intn(100)) 412 } 413 ct := hlc.Timestamp{WallTime: r.Int63n(100), Logical: r.Int31n(10)} 414 epo := ctpb.Epoch(r.Int63n(100)) 415 g.Go(func() error { 416 <-time.After(time.Duration(rand.Intn(1e7))) 417 ms.Add(nodeID, ctpb.Entry{ 418 Epoch: epo, 419 ClosedTimestamp: ct, 420 MLAI: m, 421 }) 422 return nil 423 }) 424 } 425 } 426 } 427 428 for i := 0; i < numReadersPerNode; i++ { 429 for nodeID := roachpb.NodeID(1); nodeID <= numNodes; nodeID++ { 430 nodeID := nodeID 431 g.Go(func() error { 432 epo := ctpb.Epoch(-1) 433 var ct hlc.Timestamp 434 var mlai map[roachpb.RangeID]ctpb.LAI 435 var err error 436 var n int 437 ms.VisitDescending(nodeID, func(e ctpb.Entry) bool { 438 n++ 439 if n > 1 && e.Epoch > epo { 440 err = errors.Errorf("epoch regressed from %d to %d", epo, e.Epoch) 441 return true // done 442 } 443 if n > 1 && ct.Less(e.ClosedTimestamp) { 444 err = errors.Errorf("closed timestamp regressed from %s to %s", ct, e.ClosedTimestamp) 445 return true // done 446 } 447 for rangeID := roachpb.RangeID(1); rangeID <= numRanges; rangeID++ { 448 if l := mlai[rangeID]; l < e.MLAI[rangeID] && n > 1 { 449 err = errors.Errorf("MLAI for r%d regressed: %+v to %+v", rangeID, mlai, e.MLAI) 450 return true // done 451 } 452 } 453 454 epo = e.Epoch 455 ct = e.ClosedTimestamp 456 mlai = e.MLAI 457 return false // not done 458 }) 459 return err 460 }) 461 } 462 } 463 464 if err := g.Wait(); err != nil { 465 t.Fatal(err) 466 } 467 }