github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/storage/mvcc_stats_test.go (about) 1 // Copyright 2014 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 "context" 15 "fmt" 16 "math/rand" 17 "sort" 18 "testing" 19 20 "github.com/cockroachdb/cockroach/pkg/keys" 21 "github.com/cockroachdb/cockroach/pkg/roachpb" 22 "github.com/cockroachdb/cockroach/pkg/storage/enginepb" 23 "github.com/cockroachdb/cockroach/pkg/testutils" 24 "github.com/cockroachdb/cockroach/pkg/util/hlc" 25 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 26 "github.com/cockroachdb/cockroach/pkg/util/log" 27 "github.com/cockroachdb/cockroach/pkg/util/randutil" 28 "github.com/cockroachdb/cockroach/pkg/util/uuid" 29 "github.com/kr/pretty" 30 "github.com/stretchr/testify/require" 31 ) 32 33 // assertEq compares the given ms and expMS and errors when they don't match. It 34 // also recomputes the stats over the whole ReadWriter with all known 35 // implementations and errors on mismatch with any of them. 36 func assertEq(t *testing.T, rw ReadWriter, debug string, ms, expMS *enginepb.MVCCStats) { 37 t.Helper() 38 39 msCpy := *ms // shallow copy 40 ms = &msCpy 41 ms.AgeTo(expMS.LastUpdateNanos) 42 if !ms.Equal(expMS) { 43 pretty.Ldiff(t, ms, expMS) 44 t.Errorf("%s: diff(ms, expMS) nontrivial", debug) 45 } 46 47 it := rw.NewIterator(IterOptions{UpperBound: roachpb.KeyMax}) 48 defer it.Close() 49 50 for _, mvccStatsTest := range mvccStatsTests { 51 compMS, err := mvccStatsTest.fn(it, roachpb.KeyMin, roachpb.KeyMax, ms.LastUpdateNanos) 52 if err != nil { 53 t.Fatal(err) 54 } 55 if !compMS.Equal(*ms) { 56 t.Errorf("%s: diff(ms, %s) = %s", debug, mvccStatsTest.name, pretty.Diff(*ms, compMS)) 57 } 58 } 59 } 60 61 // TestMVCCStatsDeleteCommitMovesTimestamp exercises the case in which a value 62 // is written, later deleted via an intent and the deletion committed at an even 63 // higher timestamp. This exercises subtleties related to the implicit push of 64 // the intent (before resolution) and the accumulation of GCByteAge. 65 func TestMVCCStatsDeleteCommitMovesTimestamp(t *testing.T) { 66 defer leaktest.AfterTest(t)() 67 for _, engineImpl := range mvccEngineImpls { 68 t.Run(engineImpl.name, func(t *testing.T) { 69 engine := engineImpl.create() 70 defer engine.Close() 71 72 ctx := context.Background() 73 aggMS := &enginepb.MVCCStats{} 74 75 assertEq(t, engine, "initially", aggMS, &enginepb.MVCCStats{}) 76 77 key := roachpb.Key("a") 78 ts1 := hlc.Timestamp{WallTime: 1e9} 79 // Put a value. 80 value := roachpb.MakeValueFromString("value") 81 if err := MVCCPut(ctx, engine, aggMS, key, ts1, value, nil); err != nil { 82 t.Fatal(err) 83 } 84 85 mKeySize := int64(mvccKey(key).EncodedSize()) // 2 86 vKeySize := MVCCVersionTimestampSize // 12 87 vValSize := int64(len(value.RawBytes)) // 10 88 89 expMS := enginepb.MVCCStats{ 90 LiveBytes: mKeySize + vKeySize + vValSize, // 24 91 LiveCount: 1, 92 KeyBytes: mKeySize + vKeySize, // 14 93 KeyCount: 1, 94 ValBytes: vValSize, // 10 95 ValCount: 1, 96 LastUpdateNanos: 1e9, 97 } 98 assertEq(t, engine, "after put", aggMS, &expMS) 99 100 // Delete the value at ts=3. We'll commit this at ts=4 later. 101 ts3 := hlc.Timestamp{WallTime: 3 * 1e9} 102 txn := &roachpb.Transaction{ 103 TxnMeta: enginepb.TxnMeta{ID: uuid.MakeV4(), WriteTimestamp: ts3}, 104 ReadTimestamp: ts3, 105 } 106 if err := MVCCDelete(ctx, engine, aggMS, key, txn.ReadTimestamp, txn); err != nil { 107 t.Fatal(err) 108 } 109 110 // Now commit the value, but with a timestamp gap (i.e. this is a 111 // push-commit as it would happen for a SNAPSHOT txn) 112 ts4 := hlc.Timestamp{WallTime: 4 * 1e9} 113 txn.Status = roachpb.COMMITTED 114 txn.WriteTimestamp.Forward(ts4) 115 if _, err := MVCCResolveWriteIntent(ctx, engine, aggMS, 116 roachpb.MakeLockUpdate(txn, roachpb.Span{Key: key}), 117 ); err != nil { 118 t.Fatal(err) 119 } 120 121 expAggMS := enginepb.MVCCStats{ 122 LastUpdateNanos: 4e9, 123 LiveBytes: 0, 124 LiveCount: 0, 125 KeyCount: 1, 126 ValCount: 2, 127 // The implicit meta record (deletion tombstone) counts for len("a")+1=2. 128 // Two versioned keys count for 2*vKeySize. 129 KeyBytes: mKeySize + 2*vKeySize, 130 ValBytes: vValSize, // the initial write (10) 131 // No GCBytesAge has been accrued yet, as the value just got non-live at 4s. 132 GCBytesAge: 0, 133 } 134 135 assertEq(t, engine, "after committing", aggMS, &expAggMS) 136 }) 137 } 138 } 139 140 // TestMVCCStatsPutCommitMovesTimestamp is similar to 141 // TestMVCCStatsDeleteCommitMovesTimestamp, but is simpler: a first intent is 142 // written and then committed at a later timestamp. 143 func TestMVCCStatsPutCommitMovesTimestamp(t *testing.T) { 144 defer leaktest.AfterTest(t)() 145 for _, engineImpl := range mvccEngineImpls { 146 t.Run(engineImpl.name, func(t *testing.T) { 147 engine := engineImpl.create() 148 defer engine.Close() 149 150 ctx := context.Background() 151 aggMS := &enginepb.MVCCStats{} 152 153 assertEq(t, engine, "initially", aggMS, &enginepb.MVCCStats{}) 154 155 key := roachpb.Key("a") 156 ts1 := hlc.Timestamp{WallTime: 1e9} 157 txn := &roachpb.Transaction{ 158 TxnMeta: enginepb.TxnMeta{ID: uuid.MakeV4(), WriteTimestamp: ts1}, 159 ReadTimestamp: ts1, 160 } 161 // Write an intent at t=1s. 162 value := roachpb.MakeValueFromString("value") 163 if err := MVCCPut(ctx, engine, aggMS, key, ts1, value, txn); err != nil { 164 t.Fatal(err) 165 } 166 167 mKeySize := int64(mvccKey(key).EncodedSize()) // 2 168 mValSize := int64((&enginepb.MVCCMetadata{ // 44 169 Timestamp: hlc.LegacyTimestamp(ts1), 170 Deleted: false, 171 Txn: &txn.TxnMeta, 172 }).Size()) 173 vKeySize := MVCCVersionTimestampSize // 12 174 vValSize := int64(len(value.RawBytes)) // 10 175 176 expMS := enginepb.MVCCStats{ 177 LastUpdateNanos: 1e9, 178 LiveBytes: mKeySize + mValSize + vKeySize + vValSize, // 2+44+12+10 = 68 179 LiveCount: 1, 180 KeyBytes: mKeySize + vKeySize, // 2+12 =14 181 KeyCount: 1, 182 ValBytes: mValSize + vValSize, // 44+10 = 54 183 ValCount: 1, 184 IntentCount: 1, 185 IntentBytes: vKeySize + vValSize, // 12+10 = 22 186 GCBytesAge: 0, 187 } 188 assertEq(t, engine, "after put", aggMS, &expMS) 189 190 // Now commit the intent, but with a timestamp gap (i.e. this is a 191 // push-commit as it would happen for a SNAPSHOT txn) 192 ts4 := hlc.Timestamp{WallTime: 4 * 1e9} 193 txn.Status = roachpb.COMMITTED 194 txn.WriteTimestamp.Forward(ts4) 195 if _, err := MVCCResolveWriteIntent(ctx, engine, aggMS, 196 roachpb.MakeLockUpdate(txn, roachpb.Span{Key: key}), 197 ); err != nil { 198 t.Fatal(err) 199 } 200 201 expAggMS := enginepb.MVCCStats{ 202 LastUpdateNanos: 4e9, 203 LiveBytes: mKeySize + vKeySize + vValSize, // 2+12+20 = 24 204 LiveCount: 1, 205 KeyCount: 1, 206 ValCount: 1, 207 // The implicit meta record counts for len("a")+1=2. 208 // One versioned key counts for vKeySize. 209 KeyBytes: mKeySize + vKeySize, 210 ValBytes: vValSize, 211 GCBytesAge: 0, // this was once erroneously negative 212 } 213 214 assertEq(t, engine, "after committing", aggMS, &expAggMS) 215 }) 216 } 217 } 218 219 // TestMVCCStatsPutPushMovesTimestamp is similar to TestMVCCStatsPutCommitMovesTimestamp: 220 // An intent is written and then re-written at a higher timestamp. This formerly messed up 221 // the IntentAge computation. 222 func TestMVCCStatsPutPushMovesTimestamp(t *testing.T) { 223 defer leaktest.AfterTest(t)() 224 for _, engineImpl := range mvccEngineImpls { 225 t.Run(engineImpl.name, func(t *testing.T) { 226 engine := engineImpl.create() 227 defer engine.Close() 228 229 ctx := context.Background() 230 aggMS := &enginepb.MVCCStats{} 231 232 assertEq(t, engine, "initially", aggMS, &enginepb.MVCCStats{}) 233 234 key := roachpb.Key("a") 235 ts1 := hlc.Timestamp{WallTime: 1e9} 236 txn := &roachpb.Transaction{ 237 TxnMeta: enginepb.TxnMeta{ID: uuid.MakeV4(), WriteTimestamp: ts1}, 238 ReadTimestamp: ts1, 239 } 240 // Write an intent. 241 value := roachpb.MakeValueFromString("value") 242 if err := MVCCPut(ctx, engine, aggMS, key, txn.ReadTimestamp, value, txn); err != nil { 243 t.Fatal(err) 244 } 245 246 mKeySize := int64(mvccKey(key).EncodedSize()) // 2 247 mValSize := int64((&enginepb.MVCCMetadata{ // 44 248 Timestamp: hlc.LegacyTimestamp(ts1), 249 Deleted: false, 250 Txn: &txn.TxnMeta, 251 }).Size()) 252 vKeySize := MVCCVersionTimestampSize // 12 253 vValSize := int64(len(value.RawBytes)) // 10 254 255 expMS := enginepb.MVCCStats{ 256 LastUpdateNanos: 1e9, 257 LiveBytes: mKeySize + mValSize + vKeySize + vValSize, // 2+44+12+10 = 68 258 LiveCount: 1, 259 KeyBytes: mKeySize + vKeySize, // 2+12 = 14 260 KeyCount: 1, 261 ValBytes: mValSize + vValSize, // 44+10 = 54 262 ValCount: 1, 263 IntentAge: 0, 264 IntentCount: 1, 265 IntentBytes: vKeySize + vValSize, // 12+10 = 22 266 } 267 assertEq(t, engine, "after put", aggMS, &expMS) 268 269 // Now push the value, but with a timestamp gap (i.e. this is a 270 // push as it would happen for a SNAPSHOT txn) 271 ts4 := hlc.Timestamp{WallTime: 4 * 1e9} 272 txn.WriteTimestamp.Forward(ts4) 273 if _, err := MVCCResolveWriteIntent(ctx, engine, aggMS, 274 roachpb.MakeLockUpdate(txn, roachpb.Span{Key: key}), 275 ); err != nil { 276 t.Fatal(err) 277 } 278 279 expAggMS := enginepb.MVCCStats{ 280 LastUpdateNanos: 4e9, 281 LiveBytes: mKeySize + mValSize + vKeySize + vValSize, // 2+44+12+20 = 78 282 LiveCount: 1, 283 KeyCount: 1, 284 ValCount: 1, 285 // The explicit meta record counts for len("a")+1=2. 286 // One versioned key counts for vKeySize. 287 KeyBytes: mKeySize + vKeySize, 288 // The intent is still there, so we see mValSize. 289 ValBytes: vValSize + mValSize, // 44+10 = 54 290 IntentAge: 0, // this was once erroneously positive 291 IntentCount: 1, // still there 292 IntentBytes: vKeySize + vValSize, // still there 293 } 294 295 assertEq(t, engine, "after pushing", aggMS, &expAggMS) 296 }) 297 } 298 } 299 300 // TestMVCCStatsDeleteMovesTimestamp is similar to TestMVCCStatsPutCommitMovesTimestamp: 301 // An intent is written and then re-written at a higher timestamp. This formerly messed up 302 // the GCBytesAge computation. 303 func TestMVCCStatsDeleteMovesTimestamp(t *testing.T) { 304 defer leaktest.AfterTest(t)() 305 for _, engineImpl := range mvccEngineImpls { 306 t.Run(engineImpl.name, func(t *testing.T) { 307 engine := engineImpl.create() 308 defer engine.Close() 309 310 ctx := context.Background() 311 aggMS := &enginepb.MVCCStats{} 312 313 assertEq(t, engine, "initially", aggMS, &enginepb.MVCCStats{}) 314 315 ts1 := hlc.Timestamp{WallTime: 1e9} 316 ts2 := hlc.Timestamp{WallTime: 2 * 1e9} 317 318 key := roachpb.Key("a") 319 txn := &roachpb.Transaction{ 320 TxnMeta: enginepb.TxnMeta{ID: uuid.MakeV4(), WriteTimestamp: ts1}, 321 ReadTimestamp: ts1, 322 } 323 324 // Write an intent. 325 value := roachpb.MakeValueFromString("value") 326 if err := MVCCPut(ctx, engine, aggMS, key, txn.ReadTimestamp, value, txn); err != nil { 327 t.Fatal(err) 328 } 329 330 mKeySize := int64(mvccKey(key).EncodedSize()) 331 require.EqualValues(t, mKeySize, 2) 332 333 mVal1Size := int64((&enginepb.MVCCMetadata{ 334 Timestamp: hlc.LegacyTimestamp(ts1), 335 Deleted: false, 336 Txn: &txn.TxnMeta, 337 }).Size()) 338 require.EqualValues(t, mVal1Size, 46) 339 340 m1ValSize := int64((&enginepb.MVCCMetadata{ 341 Timestamp: hlc.LegacyTimestamp(ts2), 342 Deleted: false, 343 Txn: &txn.TxnMeta, 344 }).Size()) 345 require.EqualValues(t, m1ValSize, 46) 346 347 vKeySize := MVCCVersionTimestampSize 348 require.EqualValues(t, vKeySize, 12) 349 350 vValSize := int64(len(value.RawBytes)) 351 require.EqualValues(t, vValSize, 10) 352 353 expMS := enginepb.MVCCStats{ 354 LastUpdateNanos: 1e9, 355 LiveBytes: mKeySize + m1ValSize + vKeySize + vValSize, // 2+44+12+10 = 68 356 LiveCount: 1, 357 KeyBytes: mKeySize + vKeySize, // 2+12 = 14 358 KeyCount: 1, 359 ValBytes: mVal1Size + vValSize, // 44+10 = 54 360 ValCount: 1, 361 IntentAge: 0, 362 IntentCount: 1, 363 IntentBytes: vKeySize + vValSize, // 12+10 = 22 364 } 365 assertEq(t, engine, "after put", aggMS, &expMS) 366 367 // Now replace our intent with a deletion intent, but with a timestamp gap. 368 // This could happen if a transaction got restarted with a higher timestamp 369 // and ran logic different from that in the first attempt. 370 txn.WriteTimestamp.Forward(ts2) 371 372 txn.Sequence++ 373 374 // Annoyingly, the new meta value is actually a little larger thanks to the 375 // sequence number. Also since there was a write previously on the same 376 // transaction, the IntentHistory will add a few bytes to the metadata. 377 m2ValSize := int64((&enginepb.MVCCMetadata{ 378 Timestamp: hlc.LegacyTimestamp(ts2), 379 Txn: &txn.TxnMeta, 380 IntentHistory: []enginepb.MVCCMetadata_SequencedIntent{ 381 {Sequence: 0, Value: value.RawBytes}, 382 }, 383 }).Size()) 384 require.EqualValues(t, m2ValSize, 64) 385 386 if err := MVCCDelete(ctx, engine, aggMS, key, txn.ReadTimestamp, txn); err != nil { 387 t.Fatal(err) 388 } 389 390 expAggMS := enginepb.MVCCStats{ 391 LastUpdateNanos: 2e9, 392 LiveBytes: 0, 393 LiveCount: 0, 394 KeyCount: 1, 395 ValCount: 1, 396 // The explicit meta record counts for len("a")+1=2. 397 // One versioned key counts for vKeySize. 398 KeyBytes: mKeySize + vKeySize, 399 // The intent is still there, but this time with mVal2Size, and a zero vValSize. 400 ValBytes: m2ValSize, // 10+46 = 56 401 IntentAge: 0, 402 IntentCount: 1, // still there 403 IntentBytes: vKeySize, // still there, but now without vValSize 404 GCBytesAge: 0, // this was once erroneously negative 405 } 406 407 assertEq(t, engine, "after deleting", aggMS, &expAggMS) 408 }) 409 } 410 } 411 412 // TestMVCCStatsPutMovesDeletionTimestamp is similar to TestMVCCStatsPutCommitMovesTimestamp: A 413 // tombstone intent is written and then replaced by a value intent at a higher timestamp. This 414 // formerly messed up the GCBytesAge computation. 415 func TestMVCCStatsPutMovesDeletionTimestamp(t *testing.T) { 416 defer leaktest.AfterTest(t)() 417 for _, engineImpl := range mvccEngineImpls { 418 t.Run(engineImpl.name, func(t *testing.T) { 419 engine := engineImpl.create() 420 defer engine.Close() 421 422 ctx := context.Background() 423 aggMS := &enginepb.MVCCStats{} 424 425 assertEq(t, engine, "initially", aggMS, &enginepb.MVCCStats{}) 426 427 ts1 := hlc.Timestamp{WallTime: 1e9} 428 ts2 := hlc.Timestamp{WallTime: 2 * 1e9} 429 430 key := roachpb.Key("a") 431 txn := &roachpb.Transaction{ 432 TxnMeta: enginepb.TxnMeta{ID: uuid.MakeV4(), WriteTimestamp: ts1}, 433 ReadTimestamp: ts1, 434 } 435 436 // Write a deletion tombstone intent. 437 if err := MVCCDelete(ctx, engine, aggMS, key, txn.ReadTimestamp, txn); err != nil { 438 t.Fatal(err) 439 } 440 441 value := roachpb.MakeValueFromString("value") 442 443 mKeySize := int64(mvccKey(key).EncodedSize()) 444 require.EqualValues(t, mKeySize, 2) 445 446 mVal1Size := int64((&enginepb.MVCCMetadata{ 447 Timestamp: hlc.LegacyTimestamp(ts1), 448 Deleted: false, 449 Txn: &txn.TxnMeta, 450 }).Size()) 451 require.EqualValues(t, mVal1Size, 46) 452 453 m1ValSize := int64((&enginepb.MVCCMetadata{ 454 Timestamp: hlc.LegacyTimestamp(ts2), 455 Deleted: false, 456 Txn: &txn.TxnMeta, 457 }).Size()) 458 require.EqualValues(t, m1ValSize, 46) 459 460 vKeySize := MVCCVersionTimestampSize 461 require.EqualValues(t, vKeySize, 12) 462 463 vValSize := int64(len(value.RawBytes)) 464 require.EqualValues(t, vValSize, 10) 465 466 expMS := enginepb.MVCCStats{ 467 LastUpdateNanos: 1e9, 468 LiveBytes: 0, 469 LiveCount: 0, 470 KeyBytes: mKeySize + vKeySize, // 2 + 12 = 24 471 KeyCount: 1, 472 ValBytes: mVal1Size, // 44 473 ValCount: 1, 474 IntentAge: 0, 475 IntentCount: 1, 476 IntentBytes: vKeySize, // 12 477 GCBytesAge: 0, 478 } 479 assertEq(t, engine, "after delete", aggMS, &expMS) 480 481 // Now replace our deletion with a value intent, but with a timestamp gap. 482 // This could happen if a transaction got restarted with a higher timestamp 483 // and ran logic different from that in the first attempt. 484 txn.WriteTimestamp.Forward(ts2) 485 486 txn.Sequence++ 487 488 // Annoyingly, the new meta value is actually a little larger thanks to the 489 // sequence number. Also the value is larger because the previous intent on the 490 // transaction is recorded in the IntentHistory. 491 m2ValSize := int64((&enginepb.MVCCMetadata{ 492 Timestamp: hlc.LegacyTimestamp(ts2), 493 Txn: &txn.TxnMeta, 494 IntentHistory: []enginepb.MVCCMetadata_SequencedIntent{ 495 {Sequence: 0, Value: []byte{}}, 496 }, 497 }).Size()) 498 require.EqualValues(t, m2ValSize, 54) 499 500 if err := MVCCPut(ctx, engine, aggMS, key, txn.ReadTimestamp, value, txn); err != nil { 501 t.Fatal(err) 502 } 503 504 expAggMS := enginepb.MVCCStats{ 505 LastUpdateNanos: 2e9, 506 LiveBytes: mKeySize + m2ValSize + vKeySize + vValSize, // 2+46+12+10 = 70 507 LiveCount: 1, 508 KeyCount: 1, 509 ValCount: 1, 510 // The explicit meta record counts for len("a")+1=2. 511 // One versioned key counts for vKeySize. 512 KeyBytes: mKeySize + vKeySize, 513 // The intent is still there, but this time with mVal2Size, and a zero vValSize. 514 ValBytes: vValSize + m2ValSize, // 10+46 = 56 515 IntentAge: 0, 516 IntentCount: 1, // still there 517 IntentBytes: vKeySize + vValSize, // still there, now bigger 518 GCBytesAge: 0, // this was once erroneously negative 519 } 520 521 assertEq(t, engine, "after put", aggMS, &expAggMS) 522 }) 523 } 524 } 525 526 // TestMVCCStatsDelDelCommit writes a non-transactional tombstone, and then adds an intent tombstone 527 // on top that is then committed or aborted at a higher timestamp. This random-looking sequence was 528 // the smallest failing example that highlighted a stats computation error once, and exercises a 529 // code path in which MVCCResolveIntent has to read the pre-intent value in order to arrive at the 530 // correct stats. 531 func TestMVCCStatsDelDelCommitMovesTimestamp(t *testing.T) { 532 defer leaktest.AfterTest(t)() 533 for _, engineImpl := range mvccEngineImpls { 534 t.Run(engineImpl.name, func(t *testing.T) { 535 engine := engineImpl.create() 536 defer engine.Close() 537 538 ctx := context.Background() 539 aggMS := &enginepb.MVCCStats{} 540 541 assertEq(t, engine, "initially", aggMS, &enginepb.MVCCStats{}) 542 543 key := roachpb.Key("a") 544 545 ts1 := hlc.Timestamp{WallTime: 1e9} 546 ts2 := hlc.Timestamp{WallTime: 2e9} 547 ts3 := hlc.Timestamp{WallTime: 3e9} 548 549 // Write a non-transactional tombstone at t=1s. 550 if err := MVCCDelete(ctx, engine, aggMS, key, ts1, nil /* txn */); err != nil { 551 t.Fatal(err) 552 } 553 554 mKeySize := int64(mvccKey(key).EncodedSize()) 555 require.EqualValues(t, mKeySize, 2) 556 vKeySize := MVCCVersionTimestampSize 557 require.EqualValues(t, vKeySize, 12) 558 559 expMS := enginepb.MVCCStats{ 560 LastUpdateNanos: 1e9, 561 KeyBytes: mKeySize + vKeySize, 562 KeyCount: 1, 563 ValBytes: 0, 564 ValCount: 1, 565 } 566 567 assertEq(t, engine, "after non-transactional delete", aggMS, &expMS) 568 569 // Write an tombstone intent at t=2s. 570 txn := &roachpb.Transaction{ 571 TxnMeta: enginepb.TxnMeta{ID: uuid.MakeV4(), WriteTimestamp: ts2}, 572 ReadTimestamp: ts2, 573 } 574 if err := MVCCDelete(ctx, engine, aggMS, key, txn.ReadTimestamp, txn); err != nil { 575 t.Fatal(err) 576 } 577 578 mValSize := int64((&enginepb.MVCCMetadata{ 579 Timestamp: hlc.LegacyTimestamp(ts1), 580 Deleted: true, 581 Txn: &txn.TxnMeta, 582 }).Size()) 583 require.EqualValues(t, mValSize, 46) 584 585 expMS = enginepb.MVCCStats{ 586 LastUpdateNanos: 2e9, 587 KeyBytes: mKeySize + 2*vKeySize, // 2+2*12 = 26 588 KeyCount: 1, 589 ValBytes: mValSize, // 44 590 ValCount: 2, 591 IntentCount: 1, 592 IntentBytes: vKeySize, // TBD 593 // The original non-transactional write (at 1s) has now aged one second. 594 GCBytesAge: 1 * vKeySize, 595 } 596 assertEq(t, engine, "after put", aggMS, &expMS) 597 598 // Now commit or abort the intent, respectively, but with a timestamp gap 599 // (i.e. this is a push-commit as it would happen for a SNAPSHOT txn). 600 t.Run("Commit", func(t *testing.T) { 601 aggMS := *aggMS 602 engine := engine.NewBatch() 603 defer engine.Close() 604 605 txnCommit := txn.Clone() 606 txnCommit.Status = roachpb.COMMITTED 607 txnCommit.WriteTimestamp.Forward(ts3) 608 if _, err := MVCCResolveWriteIntent(ctx, engine, &aggMS, 609 roachpb.MakeLockUpdate(txnCommit, roachpb.Span{Key: key}), 610 ); err != nil { 611 t.Fatal(err) 612 } 613 614 expAggMS := enginepb.MVCCStats{ 615 LastUpdateNanos: 3e9, 616 KeyBytes: mKeySize + 2*vKeySize, // 2+2*12 = 26 617 KeyCount: 1, 618 ValBytes: 0, 619 ValCount: 2, 620 IntentCount: 0, 621 IntentBytes: 0, 622 // The very first write picks up another second of age. Before a bug fix, 623 // this was failing to do so. 624 GCBytesAge: 2 * vKeySize, 625 } 626 627 assertEq(t, engine, "after committing", &aggMS, &expAggMS) 628 }) 629 t.Run("Abort", func(t *testing.T) { 630 aggMS := *aggMS 631 engine := engine.NewBatch() 632 defer engine.Close() 633 634 txnAbort := txn.Clone() 635 txnAbort.Status = roachpb.ABORTED 636 txnAbort.WriteTimestamp.Forward(ts3) 637 if _, err := MVCCResolveWriteIntent(ctx, engine, &aggMS, 638 roachpb.MakeLockUpdate(txnAbort, roachpb.Span{Key: key}), 639 ); err != nil { 640 t.Fatal(err) 641 } 642 643 expAggMS := enginepb.MVCCStats{ 644 LastUpdateNanos: 3e9, 645 KeyBytes: mKeySize + vKeySize, // 2+12 = 14 646 KeyCount: 1, 647 ValBytes: 0, 648 ValCount: 1, 649 IntentCount: 0, 650 IntentBytes: 0, 651 // We aborted our intent, but the value we first wrote was a tombstone, and 652 // so it's expected to retain its age. Since it's now the only value, it 653 // also contributes as a meta key. 654 GCBytesAge: 2 * (mKeySize + vKeySize), 655 } 656 657 assertEq(t, engine, "after aborting", &aggMS, &expAggMS) 658 }) 659 }) 660 } 661 } 662 663 // TestMVCCStatsPutDelPut is similar to TestMVCCStatsDelDelCommit, but its first 664 // non-transactional write is not a tombstone but a real value. This exercises a 665 // different code path as a tombstone starts accruing GCBytesAge at its own timestamp, 666 // but values only when the next value is written, which makes the computation tricky 667 // when that next value is an intent that changes its timestamp before committing. 668 // Finishing the sequence with a Put in particular exercises a case in which the 669 // final correction is done in the put path and not the commit path. 670 func TestMVCCStatsPutDelPutMovesTimestamp(t *testing.T) { 671 defer leaktest.AfterTest(t)() 672 for _, engineImpl := range mvccEngineImpls { 673 t.Run(engineImpl.name, func(t *testing.T) { 674 engine := engineImpl.create() 675 defer engine.Close() 676 677 ctx := context.Background() 678 aggMS := &enginepb.MVCCStats{} 679 680 assertEq(t, engine, "initially", aggMS, &enginepb.MVCCStats{}) 681 682 key := roachpb.Key("a") 683 684 ts1 := hlc.Timestamp{WallTime: 1e9} 685 ts2 := hlc.Timestamp{WallTime: 2e9} 686 ts3 := hlc.Timestamp{WallTime: 3e9} 687 688 // Write a non-transactional value at t=1s. 689 value := roachpb.MakeValueFromString("value") 690 if err := MVCCPut(ctx, engine, aggMS, key, ts1, value, nil /* txn */); err != nil { 691 t.Fatal(err) 692 } 693 694 mKeySize := int64(mvccKey(key).EncodedSize()) 695 require.EqualValues(t, mKeySize, 2) 696 697 vKeySize := MVCCVersionTimestampSize 698 require.EqualValues(t, vKeySize, 12) 699 700 vValSize := int64(len(value.RawBytes)) 701 require.EqualValues(t, vValSize, 10) 702 703 expMS := enginepb.MVCCStats{ 704 LastUpdateNanos: 1e9, 705 KeyBytes: mKeySize + vKeySize, 706 KeyCount: 1, 707 ValBytes: vValSize, 708 ValCount: 1, 709 LiveBytes: mKeySize + vKeySize + vValSize, 710 LiveCount: 1, 711 } 712 713 assertEq(t, engine, "after non-transactional put", aggMS, &expMS) 714 715 // Write a tombstone intent at t=2s. 716 txn := &roachpb.Transaction{ 717 TxnMeta: enginepb.TxnMeta{ID: uuid.MakeV4(), WriteTimestamp: ts2}, 718 ReadTimestamp: ts2, 719 } 720 if err := MVCCDelete(ctx, engine, aggMS, key, txn.ReadTimestamp, txn); err != nil { 721 t.Fatal(err) 722 } 723 724 mValSize := int64((&enginepb.MVCCMetadata{ 725 Timestamp: hlc.LegacyTimestamp(ts1), 726 Deleted: true, 727 Txn: &txn.TxnMeta, 728 }).Size()) 729 require.EqualValues(t, mValSize, 46) 730 731 expMS = enginepb.MVCCStats{ 732 LastUpdateNanos: 2e9, 733 KeyBytes: mKeySize + 2*vKeySize, // 2+2*12 = 26 734 KeyCount: 1, 735 ValBytes: mValSize + vValSize, // 44+10 = 56 736 ValCount: 2, 737 IntentCount: 1, 738 IntentBytes: vKeySize, // 12 739 // The original non-transactional write becomes non-live at 2s, so no age 740 // is accrued yet. 741 GCBytesAge: 0, 742 } 743 assertEq(t, engine, "after txn delete", aggMS, &expMS) 744 745 // Now commit or abort the intent, but with a timestamp gap (i.e. this is a push-commit as it 746 // would happen for a SNAPSHOT txn) 747 748 txn.WriteTimestamp.Forward(ts3) 749 txn.Sequence++ 750 751 // Annoyingly, the new meta value is actually a little larger thanks to the 752 // sequence number. 753 m2ValSize := int64((&enginepb.MVCCMetadata{ 754 Timestamp: hlc.LegacyTimestamp(ts3), 755 Txn: &txn.TxnMeta, 756 }).Size()) 757 758 require.EqualValues(t, m2ValSize, 48) 759 760 t.Run("Abort", func(t *testing.T) { 761 aggMS := *aggMS 762 engine := engine.NewBatch() 763 defer engine.Close() 764 765 txnAbort := txn.Clone() 766 txnAbort.Status = roachpb.ABORTED // doesn't change m2ValSize, fortunately 767 if _, err := MVCCResolveWriteIntent(ctx, engine, &aggMS, 768 roachpb.MakeLockUpdate(txnAbort, roachpb.Span{Key: key}), 769 ); err != nil { 770 t.Fatal(err) 771 } 772 773 expAggMS := enginepb.MVCCStats{ 774 LastUpdateNanos: 3e9, 775 KeyBytes: mKeySize + vKeySize, 776 KeyCount: 1, 777 ValBytes: vValSize, 778 ValCount: 1, 779 LiveCount: 1, 780 LiveBytes: mKeySize + vKeySize + vValSize, 781 IntentCount: 0, 782 IntentBytes: 0, 783 // The original value is visible again, so no GCBytesAge is present. Verifying this is the 784 // main point of this test (to prevent regression of a bug). 785 GCBytesAge: 0, 786 } 787 assertEq(t, engine, "after abort", &aggMS, &expAggMS) 788 }) 789 t.Run("Put", func(t *testing.T) { 790 aggMS := *aggMS 791 engine := engine.NewBatch() 792 defer engine.Close() 793 794 val2 := roachpb.MakeValueFromString("longvalue") 795 vVal2Size := int64(len(val2.RawBytes)) 796 require.EqualValues(t, vVal2Size, 14) 797 798 txn.WriteTimestamp.Forward(ts3) 799 if err := MVCCPut(ctx, engine, &aggMS, key, txn.ReadTimestamp, val2, txn); err != nil { 800 t.Fatal(err) 801 } 802 803 // Annoyingly, the new meta value is actually a little larger thanks to the 804 // sequence number. 805 m2ValSizeWithHistory := int64((&enginepb.MVCCMetadata{ 806 Timestamp: hlc.LegacyTimestamp(ts3), 807 Txn: &txn.TxnMeta, 808 IntentHistory: []enginepb.MVCCMetadata_SequencedIntent{ 809 {Sequence: 0, Value: []byte{}}, 810 }, 811 }).Size()) 812 813 require.EqualValues(t, m2ValSizeWithHistory, 54) 814 815 expAggMS := enginepb.MVCCStats{ 816 LastUpdateNanos: 3e9, 817 KeyBytes: mKeySize + 2*vKeySize, // 2+2*12 = 26 818 KeyCount: 1, 819 ValBytes: m2ValSizeWithHistory + vValSize + vVal2Size, 820 ValCount: 2, 821 LiveCount: 1, 822 LiveBytes: mKeySize + m2ValSizeWithHistory + vKeySize + vVal2Size, 823 IntentCount: 1, 824 IntentBytes: vKeySize + vVal2Size, 825 // The original write was previously non-live at 2s because that's where the 826 // intent originally lived. But the intent has moved to 3s, and so has the 827 // moment in time at which the shadowed put became non-live; it's now 3s as 828 // well, so there's no contribution yet. 829 GCBytesAge: 0, 830 } 831 assertEq(t, engine, "after txn put", &aggMS, &expAggMS) 832 }) 833 }) 834 } 835 } 836 837 // TestMVCCStatsDelDelGC prevents regression of a bug in MVCCGarbageCollect 838 // that was exercised by running two deletions followed by a specific GC. 839 func TestMVCCStatsDelDelGC(t *testing.T) { 840 defer leaktest.AfterTest(t)() 841 for _, engineImpl := range mvccEngineImpls { 842 t.Run(engineImpl.name, func(t *testing.T) { 843 engine := engineImpl.create() 844 defer engine.Close() 845 846 ctx := context.Background() 847 aggMS := &enginepb.MVCCStats{} 848 849 assertEq(t, engine, "initially", aggMS, &enginepb.MVCCStats{}) 850 851 key := roachpb.Key("a") 852 ts1 := hlc.Timestamp{WallTime: 1e9} 853 ts2 := hlc.Timestamp{WallTime: 2e9} 854 855 // Write tombstones at ts1 and ts2. 856 if err := MVCCDelete(ctx, engine, aggMS, key, ts1, nil); err != nil { 857 t.Fatal(err) 858 } 859 if err := MVCCDelete(ctx, engine, aggMS, key, ts2, nil); err != nil { 860 t.Fatal(err) 861 } 862 863 mKeySize := int64(mvccKey(key).EncodedSize()) // 2 864 vKeySize := MVCCVersionTimestampSize // 12 865 866 expMS := enginepb.MVCCStats{ 867 LastUpdateNanos: 2e9, 868 KeyBytes: mKeySize + 2*vKeySize, // 26 869 KeyCount: 1, 870 ValCount: 2, 871 GCBytesAge: 1 * vKeySize, // first tombstone, aged from ts1 to ts2 872 } 873 assertEq(t, engine, "after two puts", aggMS, &expMS) 874 875 // Run a GC invocation that clears it all. There used to be a bug here when 876 // we allowed limiting the number of deleted keys. Passing zero (i.e. remove 877 // one key and then bail) would mess up the stats, since the implementation 878 // would assume that the (implicit or explicit) meta entry was going to be 879 // removed, but this is only true when all values actually go away. 880 if err := MVCCGarbageCollect( 881 ctx, 882 engine, 883 aggMS, 884 []roachpb.GCRequest_GCKey{{ 885 Key: key, 886 Timestamp: ts2, 887 }}, 888 ts2, 889 ); err != nil { 890 t.Fatal(err) 891 } 892 893 expAggMS := enginepb.MVCCStats{ 894 LastUpdateNanos: 2e9, 895 } 896 897 assertEq(t, engine, "after GC", aggMS, &expAggMS) 898 }) 899 } 900 } 901 902 // TestMVCCStatsPutIntentTimestampNotPutTimestamp exercises a scenario in which 903 // an intent is rewritten to a lower timestamp. This formerly caused bugs 904 // because when computing the stats updates, there was an implicit assumption 905 // that the meta entries would always move forward in time. 906 // UPDATE: since there should be no way for a txn to write older intents, 907 // mvccPutInternal now makes sure that writes are always done at the most 908 // recent intent timestamp within the same txn. Note that this case occurs 909 // when the txn timestamp is moved forward due to a write too old condition, 910 // which writes the first intent at a higher timestamp. We don't allow the 911 // second intent to then be written at a lower timestamp, because that breaks 912 // the contract that the intent is always the newest version. 913 // This test now merely verifies that even when we try to write an older 914 // version, we're upgraded to write the MVCCMetadata.Timestamp. 915 func TestMVCCStatsPutIntentTimestampNotPutTimestamp(t *testing.T) { 916 defer leaktest.AfterTest(t)() 917 for _, engineImpl := range mvccEngineImpls { 918 t.Run(engineImpl.name, func(t *testing.T) { 919 engine := engineImpl.create() 920 defer engine.Close() 921 922 ctx := context.Background() 923 aggMS := &enginepb.MVCCStats{} 924 925 assertEq(t, engine, "initially", aggMS, &enginepb.MVCCStats{}) 926 927 key := roachpb.Key("a") 928 ts201 := hlc.Timestamp{WallTime: 2e9 + 1} 929 ts099 := hlc.Timestamp{WallTime: 1e9 - 1} 930 txn := &roachpb.Transaction{ 931 TxnMeta: enginepb.TxnMeta{ID: uuid.MakeV4(), WriteTimestamp: ts201}, 932 ReadTimestamp: ts099, 933 } 934 // Write an intent at 2s+1. 935 value := roachpb.MakeValueFromString("value") 936 if err := MVCCPut(ctx, engine, aggMS, key, txn.ReadTimestamp, value, txn); err != nil { 937 t.Fatal(err) 938 } 939 940 mKeySize := int64(mvccKey(key).EncodedSize()) // 2 941 m1ValSize := int64((&enginepb.MVCCMetadata{ // 44 942 Timestamp: hlc.LegacyTimestamp(ts201), 943 Txn: &txn.TxnMeta, 944 }).Size()) 945 vKeySize := MVCCVersionTimestampSize // 12 946 vValSize := int64(len(value.RawBytes)) // 10 947 948 expMS := enginepb.MVCCStats{ 949 LastUpdateNanos: 2e9 + 1, 950 LiveBytes: mKeySize + m1ValSize + vKeySize + vValSize, // 2+44+12+10 = 68 951 LiveCount: 1, 952 KeyBytes: mKeySize + vKeySize, // 14 953 KeyCount: 1, 954 ValBytes: m1ValSize + vValSize, // 44+10 = 54 955 ValCount: 1, 956 IntentCount: 1, 957 IntentBytes: vKeySize + vValSize, // 12+10 = 22 958 } 959 assertEq(t, engine, "after first put", aggMS, &expMS) 960 961 // Replace the intent with an identical one, but we write it at 1s-1 now. If 962 // you're confused, don't worry. There are two timestamps here: the one in 963 // the txn (which is, perhaps surprisingly, only really used when 964 // committing/aborting intents), and the timestamp passed directly to 965 // MVCCPut (which is where the intent will actually end up being written at, 966 // and which usually corresponds to txn.ReadTimestamp). 967 txn.Sequence++ 968 txn.WriteTimestamp = ts099 969 970 // Annoyingly, the new meta value is actually a little larger thanks to the 971 // sequence number. 972 m2ValSize := int64((&enginepb.MVCCMetadata{ // 46 973 Timestamp: hlc.LegacyTimestamp(ts201), 974 Txn: &txn.TxnMeta, 975 IntentHistory: []enginepb.MVCCMetadata_SequencedIntent{ 976 {Sequence: 0, Value: value.RawBytes}, 977 }, 978 }).Size()) 979 if err := MVCCPut(ctx, engine, aggMS, key, txn.ReadTimestamp, value, txn); err != nil { 980 t.Fatal(err) 981 } 982 983 expAggMS := enginepb.MVCCStats{ 984 // Even though we tried to put a new intent at an older timestamp, it 985 // will have been written at 2E9+1, so the age will be 0. 986 IntentAge: 0, 987 988 LastUpdateNanos: 2e9 + 1, 989 LiveBytes: mKeySize + m2ValSize + vKeySize + vValSize, // 2+46+12+10 = 70 990 LiveCount: 1, 991 KeyBytes: mKeySize + vKeySize, // 14 992 KeyCount: 1, 993 ValBytes: m2ValSize + vValSize, // 46+10 = 56 994 ValCount: 1, 995 IntentCount: 1, 996 IntentBytes: vKeySize + vValSize, // 12+10 = 22 997 } 998 999 assertEq(t, engine, "after second put", aggMS, &expAggMS) 1000 }) 1001 } 1002 } 1003 1004 // TestMVCCStatsPutWaitDeleteGC puts a value, deletes it, and runs a GC that 1005 // deletes the original write, but not the deletion tombstone. 1006 func TestMVCCStatsPutWaitDeleteGC(t *testing.T) { 1007 defer leaktest.AfterTest(t)() 1008 for _, engineImpl := range mvccEngineImpls { 1009 t.Run(engineImpl.name, func(t *testing.T) { 1010 engine := engineImpl.create() 1011 defer engine.Close() 1012 1013 ctx := context.Background() 1014 aggMS := &enginepb.MVCCStats{} 1015 1016 assertEq(t, engine, "initially", aggMS, &enginepb.MVCCStats{}) 1017 1018 key := roachpb.Key("a") 1019 1020 ts1 := hlc.Timestamp{WallTime: 1e9} 1021 ts2 := hlc.Timestamp{WallTime: 2e9} 1022 1023 // Write a value at ts1. 1024 val1 := roachpb.MakeValueFromString("value") 1025 if err := MVCCPut(ctx, engine, aggMS, key, ts1, val1, nil /* txn */); err != nil { 1026 t.Fatal(err) 1027 } 1028 1029 mKeySize := int64(mvccKey(key).EncodedSize()) 1030 require.EqualValues(t, mKeySize, 2) 1031 1032 vKeySize := MVCCVersionTimestampSize 1033 require.EqualValues(t, vKeySize, 12) 1034 1035 vValSize := int64(len(val1.RawBytes)) 1036 require.EqualValues(t, vValSize, 10) 1037 1038 expMS := enginepb.MVCCStats{ 1039 LastUpdateNanos: 1e9, 1040 KeyCount: 1, 1041 KeyBytes: mKeySize + vKeySize, // 2+12 = 14 1042 ValCount: 1, 1043 ValBytes: vValSize, // 10 1044 LiveCount: 1, 1045 LiveBytes: mKeySize + vKeySize + vValSize, // 2+12+10 = 24 1046 } 1047 assertEq(t, engine, "after first put", aggMS, &expMS) 1048 1049 // Delete the value at ts5. 1050 1051 if err := MVCCDelete(ctx, engine, aggMS, key, ts2, nil /* txn */); err != nil { 1052 t.Fatal(err) 1053 } 1054 1055 expMS = enginepb.MVCCStats{ 1056 LastUpdateNanos: 2e9, 1057 KeyCount: 1, 1058 KeyBytes: mKeySize + 2*vKeySize, // 2+2*12 = 26 1059 ValBytes: vValSize, // 10 1060 ValCount: 2, 1061 LiveBytes: 0, 1062 LiveCount: 0, 1063 GCBytesAge: 0, // before a fix, this was vKeySize + vValSize 1064 } 1065 1066 assertEq(t, engine, "after delete", aggMS, &expMS) 1067 1068 if err := MVCCGarbageCollect(ctx, engine, aggMS, []roachpb.GCRequest_GCKey{{ 1069 Key: key, 1070 Timestamp: ts1, 1071 }}, ts2); err != nil { 1072 t.Fatal(err) 1073 } 1074 1075 expMS = enginepb.MVCCStats{ 1076 LastUpdateNanos: 2e9, 1077 KeyCount: 1, 1078 KeyBytes: mKeySize + vKeySize, // 2+12 = 14 1079 ValBytes: 0, 1080 ValCount: 1, 1081 LiveBytes: 0, 1082 LiveCount: 0, 1083 GCBytesAge: 0, // before a fix, this was vKeySize + vValSize 1084 } 1085 1086 assertEq(t, engine, "after GC", aggMS, &expMS) 1087 }) 1088 } 1089 } 1090 1091 // TestMVCCStatsSysTxnPutPut prevents regression of a bug that, when rewriting an intent 1092 // on a sys key, would lead to overcounting `ms.SysBytes`. 1093 func TestMVCCStatsTxnSysPutPut(t *testing.T) { 1094 defer leaktest.AfterTest(t)() 1095 for _, engineImpl := range mvccEngineImpls { 1096 t.Run(engineImpl.name, func(t *testing.T) { 1097 engine := engineImpl.create() 1098 defer engine.Close() 1099 1100 ctx := context.Background() 1101 aggMS := &enginepb.MVCCStats{} 1102 1103 assertEq(t, engine, "initially", aggMS, &enginepb.MVCCStats{}) 1104 1105 key := keys.RangeDescriptorKey(roachpb.RKey("a")) 1106 1107 ts1 := hlc.Timestamp{WallTime: 1e9} 1108 ts2 := hlc.Timestamp{WallTime: 2e9} 1109 1110 txn := &roachpb.Transaction{ 1111 TxnMeta: enginepb.TxnMeta{ID: uuid.MakeV4(), WriteTimestamp: ts1}, 1112 ReadTimestamp: ts1, 1113 } 1114 1115 // Write an intent at ts1. 1116 val1 := roachpb.MakeValueFromString("value") 1117 if err := MVCCPut(ctx, engine, aggMS, key, txn.ReadTimestamp, val1, txn); err != nil { 1118 t.Fatal(err) 1119 } 1120 1121 mKeySize := int64(mvccKey(key).EncodedSize()) 1122 require.EqualValues(t, mKeySize, 11) 1123 1124 mValSize := int64((&enginepb.MVCCMetadata{ 1125 Timestamp: hlc.LegacyTimestamp(ts1), 1126 Deleted: false, 1127 Txn: &txn.TxnMeta, 1128 }).Size()) 1129 require.EqualValues(t, mValSize, 46) 1130 1131 vKeySize := MVCCVersionTimestampSize 1132 require.EqualValues(t, vKeySize, 12) 1133 1134 vVal1Size := int64(len(val1.RawBytes)) 1135 require.EqualValues(t, vVal1Size, 10) 1136 1137 val2 := roachpb.MakeValueFromString("longvalue") 1138 vVal2Size := int64(len(val2.RawBytes)) 1139 require.EqualValues(t, vVal2Size, 14) 1140 1141 expMS := enginepb.MVCCStats{ 1142 LastUpdateNanos: 1e9, 1143 SysBytes: mKeySize + mValSize + vKeySize + vVal1Size, // 11+44+12+10 = 77 1144 SysCount: 1, 1145 } 1146 assertEq(t, engine, "after first put", aggMS, &expMS) 1147 1148 // Rewrite the intent to ts2 with a different value. 1149 txn.WriteTimestamp.Forward(ts2) 1150 txn.Sequence++ 1151 1152 // The new meta value grows because we've bumped `txn.Sequence`. 1153 // The value also grows as the older value is part of the same 1154 // transaction and so contributes to the intent history. 1155 mVal2Size := int64((&enginepb.MVCCMetadata{ 1156 Timestamp: hlc.LegacyTimestamp(ts2), 1157 Deleted: false, 1158 Txn: &txn.TxnMeta, 1159 IntentHistory: []enginepb.MVCCMetadata_SequencedIntent{ 1160 {Sequence: 0, Value: val1.RawBytes}, 1161 }, 1162 }).Size()) 1163 require.EqualValues(t, mVal2Size, 64) 1164 1165 if err := MVCCPut(ctx, engine, aggMS, key, txn.ReadTimestamp, val2, txn); err != nil { 1166 t.Fatal(err) 1167 } 1168 1169 expMS = enginepb.MVCCStats{ 1170 LastUpdateNanos: 1e9, 1171 SysBytes: mKeySize + mVal2Size + vKeySize + vVal2Size, // 11+46+12+14 = 83 1172 SysCount: 1, 1173 } 1174 1175 assertEq(t, engine, "after intent rewrite", aggMS, &expMS) 1176 }) 1177 } 1178 } 1179 1180 // TestMVCCStatsTxnSysPutAbort prevents regression of a bug that, when aborting 1181 // an intent on a sys key, would lead to undercounting `ms.IntentBytes` and 1182 // `ms.IntentCount`. 1183 func TestMVCCStatsTxnSysPutAbort(t *testing.T) { 1184 defer leaktest.AfterTest(t)() 1185 for _, engineImpl := range mvccEngineImpls { 1186 t.Run(engineImpl.name, func(t *testing.T) { 1187 engine := engineImpl.create() 1188 defer engine.Close() 1189 1190 ctx := context.Background() 1191 aggMS := &enginepb.MVCCStats{} 1192 1193 assertEq(t, engine, "initially", aggMS, &enginepb.MVCCStats{}) 1194 1195 key := keys.RangeDescriptorKey(roachpb.RKey("a")) 1196 1197 ts1 := hlc.Timestamp{WallTime: 1e9} 1198 txn := &roachpb.Transaction{ 1199 TxnMeta: enginepb.TxnMeta{ID: uuid.MakeV4(), WriteTimestamp: ts1}, 1200 ReadTimestamp: ts1, 1201 } 1202 1203 // Write a system intent at ts1. 1204 val1 := roachpb.MakeValueFromString("value") 1205 if err := MVCCPut(ctx, engine, aggMS, key, txn.ReadTimestamp, val1, txn); err != nil { 1206 t.Fatal(err) 1207 } 1208 1209 mKeySize := int64(mvccKey(key).EncodedSize()) 1210 require.EqualValues(t, mKeySize, 11) 1211 1212 mValSize := int64((&enginepb.MVCCMetadata{ 1213 Timestamp: hlc.LegacyTimestamp(ts1), 1214 Deleted: false, 1215 Txn: &txn.TxnMeta, 1216 }).Size()) 1217 require.EqualValues(t, mValSize, 46) 1218 1219 vKeySize := MVCCVersionTimestampSize 1220 require.EqualValues(t, vKeySize, 12) 1221 1222 vVal1Size := int64(len(val1.RawBytes)) 1223 require.EqualValues(t, vVal1Size, 10) 1224 1225 val2 := roachpb.MakeValueFromString("longvalue") 1226 vVal2Size := int64(len(val2.RawBytes)) 1227 require.EqualValues(t, vVal2Size, 14) 1228 1229 expMS := enginepb.MVCCStats{ 1230 LastUpdateNanos: 1e9, 1231 SysBytes: mKeySize + mValSize + vKeySize + vVal1Size, // 11+44+12+10 = 77 1232 SysCount: 1, 1233 } 1234 assertEq(t, engine, "after first put", aggMS, &expMS) 1235 1236 // Now abort the intent. 1237 txn.Status = roachpb.ABORTED 1238 if _, err := MVCCResolveWriteIntent(ctx, engine, aggMS, 1239 roachpb.MakeLockUpdate(txn, roachpb.Span{Key: key}), 1240 ); err != nil { 1241 t.Fatal(err) 1242 } 1243 1244 expMS = enginepb.MVCCStats{ 1245 LastUpdateNanos: 1e9, 1246 } 1247 assertEq(t, engine, "after aborting", aggMS, &expMS) 1248 }) 1249 } 1250 } 1251 1252 // TestMVCCStatsSysPutPut prevents regression of a bug that, when writing a new 1253 // value on top of an existing system key, would undercount. 1254 func TestMVCCStatsSysPutPut(t *testing.T) { 1255 defer leaktest.AfterTest(t)() 1256 for _, engineImpl := range mvccEngineImpls { 1257 t.Run(engineImpl.name, func(t *testing.T) { 1258 engine := engineImpl.create() 1259 defer engine.Close() 1260 1261 ctx := context.Background() 1262 aggMS := &enginepb.MVCCStats{} 1263 1264 assertEq(t, engine, "initially", aggMS, &enginepb.MVCCStats{}) 1265 1266 key := keys.RangeDescriptorKey(roachpb.RKey("a")) 1267 1268 ts1 := hlc.Timestamp{WallTime: 1e9} 1269 ts2 := hlc.Timestamp{WallTime: 2e9} 1270 1271 // Write a value at ts1. 1272 val1 := roachpb.MakeValueFromString("value") 1273 if err := MVCCPut(ctx, engine, aggMS, key, ts1, val1, nil /* txn */); err != nil { 1274 t.Fatal(err) 1275 } 1276 1277 mKeySize := int64(mvccKey(key).EncodedSize()) 1278 require.EqualValues(t, mKeySize, 11) 1279 1280 vKeySize := MVCCVersionTimestampSize 1281 require.EqualValues(t, vKeySize, 12) 1282 1283 vVal1Size := int64(len(val1.RawBytes)) 1284 require.EqualValues(t, vVal1Size, 10) 1285 1286 val2 := roachpb.MakeValueFromString("longvalue") 1287 vVal2Size := int64(len(val2.RawBytes)) 1288 require.EqualValues(t, vVal2Size, 14) 1289 1290 expMS := enginepb.MVCCStats{ 1291 LastUpdateNanos: 1e9, 1292 SysBytes: mKeySize + vKeySize + vVal1Size, // 11+12+10 = 33 1293 SysCount: 1, 1294 } 1295 assertEq(t, engine, "after first put", aggMS, &expMS) 1296 1297 // Put another value at ts2. 1298 1299 if err := MVCCPut(ctx, engine, aggMS, key, ts2, val2, nil /* txn */); err != nil { 1300 t.Fatal(err) 1301 } 1302 1303 expMS = enginepb.MVCCStats{ 1304 LastUpdateNanos: 1e9, 1305 SysBytes: mKeySize + 2*vKeySize + vVal1Size + vVal2Size, 1306 SysCount: 1, 1307 } 1308 1309 assertEq(t, engine, "after second put", aggMS, &expMS) 1310 }) 1311 } 1312 } 1313 1314 var mvccStatsTests = []struct { 1315 name string 1316 fn func(Iterator, roachpb.Key, roachpb.Key, int64) (enginepb.MVCCStats, error) 1317 }{ 1318 { 1319 name: "ComputeStats", 1320 fn: func(iter Iterator, start, end roachpb.Key, nowNanos int64) (enginepb.MVCCStats, error) { 1321 return iter.ComputeStats(start, end, nowNanos) 1322 }, 1323 }, 1324 { 1325 name: "ComputeStatsGo", 1326 fn: func(iter Iterator, start, end roachpb.Key, nowNanos int64) (enginepb.MVCCStats, error) { 1327 return ComputeStatsGo(iter, start, end, nowNanos) 1328 }, 1329 }, 1330 } 1331 1332 type state struct { 1333 MS *enginepb.MVCCStats 1334 TS hlc.Timestamp 1335 Txn *roachpb.Transaction 1336 1337 eng Engine 1338 rng *rand.Rand 1339 key roachpb.Key 1340 } 1341 1342 func (s *state) intent(status roachpb.TransactionStatus) roachpb.LockUpdate { 1343 intent := roachpb.MakeLockUpdate(s.Txn, roachpb.Span{Key: s.key}) 1344 intent.Status = status 1345 return intent 1346 } 1347 1348 func (s *state) intentRange(status roachpb.TransactionStatus) roachpb.LockUpdate { 1349 intent := roachpb.MakeLockUpdate(s.Txn, roachpb.Span{Key: roachpb.KeyMin, EndKey: roachpb.KeyMax}) 1350 intent.Status = status 1351 return intent 1352 } 1353 1354 func (s *state) rngVal() roachpb.Value { 1355 return roachpb.MakeValueFromBytes(randutil.RandBytes(s.rng, int(s.rng.Int31n(128)))) 1356 } 1357 1358 type randomTest struct { 1359 state 1360 1361 inline bool 1362 actions map[string]func(*state) string 1363 actionNames []string // auto-populated 1364 cycle int 1365 } 1366 1367 func (s *randomTest) step(t *testing.T) { 1368 if !s.inline { 1369 // Jump up to a few seconds into the future. In ~1% of cases, jump 1370 // backwards instead (this exercises intactness on WriteTooOld, etc). 1371 s.TS = hlc.Timestamp{ 1372 WallTime: s.TS.WallTime + int64((s.state.rng.Float32()-0.01)*4e9), 1373 Logical: int32(s.rng.Intn(10)), 1374 } 1375 if s.TS.WallTime < 0 { 1376 // See TestMVCCStatsDocumentNegativeWrites. Negative MVCC timestamps 1377 // aren't something we're interested in, and besides, they corrupt 1378 // everything. 1379 s.TS.WallTime = 0 1380 } 1381 } else { 1382 s.TS = hlc.Timestamp{} 1383 } 1384 1385 restart := s.Txn != nil && s.rng.Intn(2) == 0 1386 if restart { 1387 // TODO(tschottdorf): experiment with s.TS != s.Txn.TS. Which of those 1388 // cases are reasonable and which should we catch and error out? 1389 // 1390 // Note that we already exercise txn.Timestamp > s.TS since we call 1391 // Forward() here (while s.TS may move backwards). 1392 s.Txn.Restart(0, 0, s.TS) 1393 } 1394 s.cycle++ 1395 1396 if s.actionNames == nil { 1397 for name := range s.actions { 1398 s.actionNames = append(s.actionNames, name) 1399 } 1400 sort.Strings(s.actionNames) 1401 } 1402 actName := s.actionNames[s.rng.Intn(len(s.actionNames))] 1403 1404 preTxn := s.Txn 1405 info := s.actions[actName](&s.state) 1406 1407 txnS := "<none>" 1408 if preTxn != nil { 1409 txnS = preTxn.WriteTimestamp.String() 1410 } 1411 1412 if info != "" { 1413 info = "\n\t" + info 1414 } 1415 log.Infof(context.Background(), "%10s %s txn=%s%s", s.TS, actName, txnS, info) 1416 1417 // Verify stats agree with recomputations. 1418 assertEq(t, s.eng, fmt.Sprintf("cycle %d", s.cycle), s.MS, s.MS) 1419 1420 if t.Failed() { 1421 t.FailNow() 1422 } 1423 } 1424 1425 func TestMVCCStatsRandomized(t *testing.T) { 1426 defer leaktest.AfterTest(t)() 1427 1428 ctx := context.Background() 1429 1430 // NB: no failure type ever required count five or more. When there is a result 1431 // found by this test, or any other MVCC code is changed, it's worth reducing 1432 // this first to two, three, ... and running the test for a minute to get a 1433 // good idea of minimally reproducing examples. 1434 const count = 200 1435 1436 actions := make(map[string]func(*state) string) 1437 1438 actions["Put"] = func(s *state) string { 1439 if err := MVCCPut(ctx, s.eng, s.MS, s.key, s.TS, s.rngVal(), s.Txn); err != nil { 1440 return err.Error() 1441 } 1442 return "" 1443 } 1444 actions["InitPut"] = func(s *state) string { 1445 failOnTombstones := (s.rng.Intn(2) == 0) 1446 desc := fmt.Sprintf("failOnTombstones=%t", failOnTombstones) 1447 if err := MVCCInitPut(ctx, s.eng, s.MS, s.key, s.TS, s.rngVal(), failOnTombstones, s.Txn); err != nil { 1448 return desc + ": " + err.Error() 1449 } 1450 return desc 1451 } 1452 actions["Del"] = func(s *state) string { 1453 if err := MVCCDelete(ctx, s.eng, s.MS, s.key, s.TS, s.Txn); err != nil { 1454 return err.Error() 1455 } 1456 return "" 1457 } 1458 actions["DelRange"] = func(s *state) string { 1459 returnKeys := (s.rng.Intn(2) == 0) 1460 max := s.rng.Int63n(5) 1461 desc := fmt.Sprintf("returnKeys=%t, max=%d", returnKeys, max) 1462 if _, _, _, err := MVCCDeleteRange(ctx, s.eng, s.MS, roachpb.KeyMin, roachpb.KeyMax, max, s.TS, s.Txn, returnKeys); err != nil { 1463 return desc + ": " + err.Error() 1464 } 1465 return desc 1466 } 1467 actions["EnsureTxn"] = func(s *state) string { 1468 if s.Txn == nil { 1469 txn := roachpb.MakeTransaction("test", nil, 0, s.TS, 0) 1470 s.Txn = &txn 1471 } 1472 return "" 1473 } 1474 1475 resolve := func(s *state, status roachpb.TransactionStatus) string { 1476 ranged := s.rng.Intn(2) == 0 1477 desc := fmt.Sprintf("ranged=%t", ranged) 1478 if s.Txn != nil { 1479 if !ranged { 1480 if _, err := MVCCResolveWriteIntent(ctx, s.eng, s.MS, s.intent(status)); err != nil { 1481 return desc + ": " + err.Error() 1482 } 1483 } else { 1484 max := s.rng.Int63n(5) 1485 desc += fmt.Sprintf(", max=%d", max) 1486 if _, _, err := MVCCResolveWriteIntentRange(ctx, s.eng, s.MS, s.intentRange(status), max); err != nil { 1487 return desc + ": " + err.Error() 1488 } 1489 } 1490 if status != roachpb.PENDING { 1491 s.Txn = nil 1492 } 1493 } 1494 return desc 1495 } 1496 1497 actions["Abort"] = func(s *state) string { 1498 return resolve(s, roachpb.ABORTED) 1499 } 1500 actions["Commit"] = func(s *state) string { 1501 return resolve(s, roachpb.COMMITTED) 1502 } 1503 actions["Push"] = func(s *state) string { 1504 return resolve(s, roachpb.PENDING) 1505 } 1506 actions["GC"] = func(s *state) string { 1507 // Sometimes GC everything, sometimes only older versions. 1508 gcTS := hlc.Timestamp{ 1509 WallTime: s.rng.Int63n(s.TS.WallTime + 1 /* avoid zero */), 1510 } 1511 if err := MVCCGarbageCollect( 1512 ctx, 1513 s.eng, 1514 s.MS, 1515 []roachpb.GCRequest_GCKey{{ 1516 Key: s.key, 1517 Timestamp: gcTS, 1518 }}, 1519 s.TS, 1520 ); err != nil { 1521 return err.Error() 1522 } 1523 return fmt.Sprint(gcTS) 1524 } 1525 1526 for _, engineImpl := range mvccEngineImpls { 1527 t.Run(engineImpl.name, func(t *testing.T) { 1528 for _, test := range []struct { 1529 name string 1530 key roachpb.Key 1531 seed int64 1532 }{ 1533 { 1534 name: "userspace", 1535 key: roachpb.Key("foo"), 1536 seed: randutil.NewPseudoSeed(), 1537 }, 1538 { 1539 name: "sys", 1540 key: keys.RangeDescriptorKey(roachpb.RKey("bar")), 1541 seed: randutil.NewPseudoSeed(), 1542 }, 1543 } { 1544 t.Run(test.name, func(t *testing.T) { 1545 testutils.RunTrueAndFalse(t, "inline", func(t *testing.T, inline bool) { 1546 t.Run(fmt.Sprintf("seed=%d", test.seed), func(t *testing.T) { 1547 eng := engineImpl.create() 1548 defer eng.Close() 1549 1550 s := &randomTest{ 1551 actions: actions, 1552 inline: inline, 1553 state: state{ 1554 rng: rand.New(rand.NewSource(test.seed)), 1555 eng: eng, 1556 key: test.key, 1557 MS: &enginepb.MVCCStats{}, 1558 }, 1559 } 1560 1561 for i := 0; i < count; i++ { 1562 s.step(t) 1563 } 1564 }) 1565 }) 1566 }) 1567 } 1568 }) 1569 } 1570 } 1571 1572 func TestMVCCComputeStatsError(t *testing.T) { 1573 defer leaktest.AfterTest(t)() 1574 for _, engineImpl := range mvccEngineImpls { 1575 t.Run(engineImpl.name, func(t *testing.T) { 1576 engine := engineImpl.create() 1577 defer engine.Close() 1578 1579 // Write a MVCC metadata key where the value is not an encoded MVCCMetadata 1580 // protobuf. 1581 if err := engine.Put(mvccKey(roachpb.Key("garbage")), []byte("garbage")); err != nil { 1582 t.Fatal(err) 1583 } 1584 1585 iter := engine.NewIterator(IterOptions{UpperBound: roachpb.KeyMax}) 1586 defer iter.Close() 1587 for _, mvccStatsTest := range mvccStatsTests { 1588 t.Run(mvccStatsTest.name, func(t *testing.T) { 1589 _, err := mvccStatsTest.fn(iter, roachpb.KeyMin, roachpb.KeyMax, 100) 1590 if e := "unable to decode MVCCMetadata"; !testutils.IsError(err, e) { 1591 t.Fatalf("expected %s, got %v", e, err) 1592 } 1593 }) 1594 } 1595 }) 1596 } 1597 }