github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/batcheval/cmd_add_sstable_test.go (about) 1 // Copyright 2017 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 batcheval_test 12 13 import ( 14 "bytes" 15 "context" 16 "os" 17 "regexp" 18 "sort" 19 "strings" 20 "testing" 21 22 "github.com/cockroachdb/cockroach/pkg/base" 23 "github.com/cockroachdb/cockroach/pkg/keys" 24 "github.com/cockroachdb/cockroach/pkg/kv" 25 "github.com/cockroachdb/cockroach/pkg/kv/kvserver" 26 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/batcheval" 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/testutils/serverutils" 32 "github.com/cockroachdb/cockroach/pkg/util/hlc" 33 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 34 "github.com/cockroachdb/cockroach/pkg/util/tracing" 35 "github.com/cockroachdb/errors" 36 "github.com/kr/pretty" 37 ) 38 39 // createTestRocksDBEngine returns a new in-memory RocksDB engine with 1MB of 40 // storage capacity. 41 func createTestRocksDBEngine() storage.Engine { 42 return storage.NewInMem(context.Background(), 43 enginepb.EngineTypeRocksDB, roachpb.Attributes{}, 1<<20) 44 } 45 46 // createTestPebbleEngine returns a new in-memory Pebble storage engine. 47 func createTestPebbleEngine() storage.Engine { 48 return storage.NewInMem(context.Background(), 49 enginepb.EngineTypePebble, roachpb.Attributes{}, 1<<20) 50 } 51 52 var engineImpls = []struct { 53 name string 54 create func() storage.Engine 55 }{ 56 {"rocksdb", createTestRocksDBEngine}, 57 {"pebble", createTestPebbleEngine}, 58 } 59 60 func singleKVSSTable(key storage.MVCCKey, value []byte) ([]byte, error) { 61 sstFile := &storage.MemFile{} 62 sst := storage.MakeBackupSSTWriter(sstFile) 63 defer sst.Close() 64 if err := sst.Put(key, value); err != nil { 65 return nil, err 66 } 67 if err := sst.Finish(); err != nil { 68 return nil, err 69 } 70 return sstFile.Data(), nil 71 } 72 73 func TestDBAddSSTable(t *testing.T) { 74 defer leaktest.AfterTest(t)() 75 t.Run("store=in-memory", func(t *testing.T) { 76 s, _, db := serverutils.StartServer(t, base.TestServerArgs{Insecure: true}) 77 ctx := context.Background() 78 defer s.Stopper().Stop(ctx) 79 runTestDBAddSSTable(ctx, t, db, nil) 80 }) 81 t.Run("store=on-disk", func(t *testing.T) { 82 dir, dirCleanupFn := testutils.TempDir(t) 83 defer dirCleanupFn() 84 85 storeSpec := base.DefaultTestStoreSpec 86 storeSpec.InMemory = false 87 storeSpec.Path = dir 88 s, _, db := serverutils.StartServer(t, base.TestServerArgs{ 89 Insecure: true, 90 StoreSpecs: []base.StoreSpec{storeSpec}, 91 }) 92 ctx := context.Background() 93 defer s.Stopper().Stop(ctx) 94 store, err := s.GetStores().(*kvserver.Stores).GetStore(s.GetFirstStoreID()) 95 if err != nil { 96 t.Fatal(err) 97 } 98 runTestDBAddSSTable(ctx, t, db, store) 99 }) 100 } 101 102 // if store != nil, assume it is on-disk and check ingestion semantics. 103 func runTestDBAddSSTable(ctx context.Context, t *testing.T, db *kv.DB, store *kvserver.Store) { 104 { 105 key := storage.MVCCKey{Key: []byte("bb"), Timestamp: hlc.Timestamp{WallTime: 2}} 106 data, err := singleKVSSTable(key, roachpb.MakeValueFromString("1").RawBytes) 107 if err != nil { 108 t.Fatalf("%+v", err) 109 } 110 111 // Key is before the range in the request span. 112 if err := db.AddSSTable( 113 ctx, "d", "e", data, false /* disallowShadowing */, nil /* stats */, false, /* ingestAsWrites */ 114 ); !testutils.IsError(err, "not in request range") { 115 t.Fatalf("expected request range error got: %+v", err) 116 } 117 // Key is after the range in the request span. 118 if err := db.AddSSTable( 119 ctx, "a", "b", data, false /* disallowShadowing */, nil /* stats */, false, /* ingestAsWrites */ 120 ); !testutils.IsError(err, "not in request range") { 121 t.Fatalf("expected request range error got: %+v", err) 122 } 123 124 // Do an initial ingest. 125 ingestCtx, collect, cancel := tracing.ContextWithRecordingSpan(ctx, "test-recording") 126 defer cancel() 127 if err := db.AddSSTable( 128 ingestCtx, "b", "c", data, false /* disallowShadowing */, nil /* stats */, false, /* ingestAsWrites */ 129 ); err != nil { 130 t.Fatalf("%+v", err) 131 } 132 formatted := collect().String() 133 if err := testutils.MatchInOrder(formatted, 134 "evaluating AddSSTable", 135 "sideloadable proposal detected", 136 "ingested SSTable at index", 137 ); err != nil { 138 t.Fatal(err) 139 } 140 141 if store != nil { 142 // Look for the ingested path and verify it still exists. 143 re := regexp.MustCompile(`ingested SSTable at index \d+, term \d+: (\S+)`) 144 match := re.FindStringSubmatch(formatted) 145 if len(match) != 2 { 146 t.Fatalf("failed to extract ingested path from message %q,\n got: %v", formatted, match) 147 } 148 // The on-disk paths have `.ingested` appended unlike in-memory. 149 suffix := ".ingested" 150 if _, err := os.Stat(strings.TrimSuffix(match[1], suffix)); err != nil { 151 t.Fatalf("%q file missing after ingest: %+v", match[1], err) 152 } 153 } 154 if r, err := db.Get(ctx, "bb"); err != nil { 155 t.Fatalf("%+v", err) 156 } else if expected := []byte("1"); !bytes.Equal(expected, r.ValueBytes()) { 157 t.Errorf("expected %q, got %q", expected, r.ValueBytes()) 158 } 159 } 160 161 // Check that ingesting a key with an earlier mvcc timestamp doesn't affect 162 // the value returned by Get. 163 { 164 key := storage.MVCCKey{Key: []byte("bb"), Timestamp: hlc.Timestamp{WallTime: 1}} 165 data, err := singleKVSSTable(key, roachpb.MakeValueFromString("2").RawBytes) 166 if err != nil { 167 t.Fatalf("%+v", err) 168 } 169 170 if err := db.AddSSTable( 171 ctx, "b", "c", data, false /* disallowShadowing */, nil /* stats */, false, /* ingestAsWrites */ 172 ); err != nil { 173 t.Fatalf("%+v", err) 174 } 175 if r, err := db.Get(ctx, "bb"); err != nil { 176 t.Fatalf("%+v", err) 177 } else if expected := []byte("1"); !bytes.Equal(expected, r.ValueBytes()) { 178 t.Errorf("expected %q, got %q", expected, r.ValueBytes()) 179 } 180 if store != nil { 181 metrics := store.Metrics() 182 if expected, got := int64(2), metrics.AddSSTableApplications.Count(); expected != got { 183 t.Fatalf("expected %d sst ingestions, got %d", expected, got) 184 } 185 } 186 } 187 188 // Key range in request span is not empty. First time through a different 189 // key is present. Second time through checks the idempotency. 190 { 191 key := storage.MVCCKey{Key: []byte("bc"), Timestamp: hlc.Timestamp{WallTime: 1}} 192 data, err := singleKVSSTable(key, roachpb.MakeValueFromString("3").RawBytes) 193 if err != nil { 194 t.Fatalf("%+v", err) 195 } 196 197 var metrics *kvserver.StoreMetrics 198 var before int64 199 if store != nil { 200 metrics = store.Metrics() 201 before = metrics.AddSSTableApplicationCopies.Count() 202 } 203 for i := 0; i < 2; i++ { 204 ingestCtx, collect, cancel := tracing.ContextWithRecordingSpan(ctx, "test-recording") 205 defer cancel() 206 207 if err := db.AddSSTable( 208 ingestCtx, "b", "c", data, false /* disallowShadowing */, nil /* stats */, false, /* ingestAsWrites */ 209 ); err != nil { 210 t.Fatalf("%+v", err) 211 } 212 if err := testutils.MatchInOrder(collect().String(), 213 "evaluating AddSSTable", 214 "sideloadable proposal detected", 215 "ingested SSTable at index", 216 ); err != nil { 217 t.Fatal(err) 218 } 219 220 if r, err := db.Get(ctx, "bb"); err != nil { 221 t.Fatalf("%+v", err) 222 } else if expected := []byte("1"); !bytes.Equal(expected, r.ValueBytes()) { 223 t.Errorf("expected %q, got %q", expected, r.ValueBytes()) 224 } 225 if r, err := db.Get(ctx, "bc"); err != nil { 226 t.Fatalf("%+v", err) 227 } else if expected := []byte("3"); !bytes.Equal(expected, r.ValueBytes()) { 228 t.Errorf("expected %q, got %q", expected, r.ValueBytes()) 229 } 230 } 231 if store != nil { 232 if expected, got := int64(4), metrics.AddSSTableApplications.Count(); expected != got { 233 t.Fatalf("expected %d sst ingestions, got %d", expected, got) 234 } 235 // The second time though we had to make a copy of the SST since rocks saw 236 // existing data (from the first time), and rejected the no-modification 237 // attempt. 238 if after := metrics.AddSSTableApplicationCopies.Count(); before != after { 239 t.Fatalf("expected sst copies not to increase, %d before %d after", before, after) 240 } 241 } 242 } 243 244 // ... and doing the same thing but via write-batch works the same. 245 { 246 key := storage.MVCCKey{Key: []byte("bd"), Timestamp: hlc.Timestamp{WallTime: 1}} 247 data, err := singleKVSSTable(key, roachpb.MakeValueFromString("3").RawBytes) 248 if err != nil { 249 t.Fatalf("%+v", err) 250 } 251 252 var metrics *kvserver.StoreMetrics 253 var before int64 254 if store != nil { 255 metrics = store.Metrics() 256 before = metrics.AddSSTableApplications.Count() 257 } 258 for i := 0; i < 2; i++ { 259 ingestCtx, collect, cancel := tracing.ContextWithRecordingSpan(ctx, "test-recording") 260 defer cancel() 261 262 if err := db.AddSSTable( 263 ingestCtx, "b", "c", data, false /* disallowShadowing */, nil /* stats */, true, /* ingestAsWrites */ 264 ); err != nil { 265 t.Fatalf("%+v", err) 266 } 267 if err := testutils.MatchInOrder(collect().String(), 268 "evaluating AddSSTable", 269 "via regular write batch", 270 ); err != nil { 271 t.Fatal(err) 272 } 273 274 if r, err := db.Get(ctx, "bb"); err != nil { 275 t.Fatalf("%+v", err) 276 } else if expected := []byte("1"); !bytes.Equal(expected, r.ValueBytes()) { 277 t.Errorf("expected %q, got %q", expected, r.ValueBytes()) 278 } 279 if r, err := db.Get(ctx, "bd"); err != nil { 280 t.Fatalf("%+v", err) 281 } else if expected := []byte("3"); !bytes.Equal(expected, r.ValueBytes()) { 282 t.Errorf("expected %q, got %q", expected, r.ValueBytes()) 283 } 284 } 285 if store != nil { 286 if expected, got := before, metrics.AddSSTableApplications.Count(); expected != got { 287 t.Fatalf("expected %d sst ingestions, got %d", expected, got) 288 } 289 } 290 } 291 292 // Invalid key/value entry checksum. 293 { 294 key := storage.MVCCKey{Key: []byte("bb"), Timestamp: hlc.Timestamp{WallTime: 1}} 295 value := roachpb.MakeValueFromString("1") 296 value.InitChecksum([]byte("foo")) 297 data, err := singleKVSSTable(key, value.RawBytes) 298 if err != nil { 299 t.Fatalf("%+v", err) 300 } 301 302 if err := db.AddSSTable( 303 ctx, "b", "c", data, false /* disallowShadowing */, nil /* stats */, false, /* ingestAsWrites */ 304 ); !testutils.IsError(err, "invalid checksum") { 305 t.Fatalf("expected 'invalid checksum' error got: %+v", err) 306 } 307 } 308 } 309 310 type strKv struct { 311 k string 312 ts int64 313 v string 314 } 315 316 func mvccKVsFromStrs(in []strKv) []storage.MVCCKeyValue { 317 kvs := make([]storage.MVCCKeyValue, len(in)) 318 for i := range kvs { 319 kvs[i].Key.Key = []byte(in[i].k) 320 kvs[i].Key.Timestamp.WallTime = in[i].ts 321 if in[i].v != "" { 322 kvs[i].Value = roachpb.MakeValueFromBytes([]byte(in[i].v)).RawBytes 323 } else { 324 kvs[i].Value = nil 325 } 326 } 327 sort.Slice(kvs, func(i, j int) bool { return kvs[i].Key.Less(kvs[j].Key) }) 328 return kvs 329 } 330 331 func TestAddSSTableMVCCStats(t *testing.T) { 332 defer leaktest.AfterTest(t)() 333 334 ctx := context.Background() 335 for _, engineImpl := range engineImpls { 336 t.Run(engineImpl.name, func(t *testing.T) { 337 e := engineImpl.create() 338 defer e.Close() 339 340 for _, kv := range mvccKVsFromStrs([]strKv{ 341 {"A", 1, "A"}, 342 {"a", 1, "a"}, 343 {"a", 6, ""}, 344 {"b", 5, "bb"}, 345 {"c", 6, "ccccccccccccccccccccccccccccccccccccccccccccc"}, // key 4b, 50b, live 64b 346 {"d", 1, "d"}, 347 {"d", 2, ""}, 348 {"e", 1, "e"}, 349 {"z", 2, "zzzzzz"}, 350 }) { 351 if err := e.Put(kv.Key, kv.Value); err != nil { 352 t.Fatalf("%+v", err) 353 } 354 } 355 356 sstKVs := mvccKVsFromStrs([]strKv{ 357 {"a", 2, "aa"}, // mvcc-shadowed within SST. 358 {"a", 4, "aaaaaa"}, // mvcc-shadowed by existing delete. 359 {"c", 6, "ccc"}, // same TS as existing, LSM-shadows existing. 360 {"d", 4, "dddd"}, // mvcc-shadow existing deleted d. 361 {"e", 4, "eeee"}, // mvcc-shadow existing 1b. 362 {"j", 2, "jj"}, // no colission – via MVCC or LSM – with existing. 363 }) 364 var delta enginepb.MVCCStats 365 // the sst will think it added 4 keys here, but a, c, and e shadow or are shadowed. 366 delta.LiveCount = -3 367 delta.LiveBytes = -109 368 // the sst will think it added 5 keys, but only j is new so 4 are over-counted. 369 delta.KeyCount = -4 370 delta.KeyBytes = -20 371 // the sst will think it added 6 values, but since one was a perfect (key+ts) 372 // collision, it *replaced* the existing value and is over-counted. 373 delta.ValCount = -1 374 delta.ValBytes = -50 375 376 // Add in a random metadata key. 377 ts := hlc.Timestamp{WallTime: 7} 378 txn := roachpb.MakeTransaction( 379 "test", 380 nil, // baseKey 381 roachpb.NormalUserPriority, 382 ts, 383 base.DefaultMaxClockOffset.Nanoseconds(), 384 ) 385 if err := storage.MVCCPut( 386 ctx, e, nil, []byte("i"), ts, 387 roachpb.MakeValueFromBytes([]byte("it")), 388 &txn, 389 ); err != nil { 390 if !errors.HasType(err, (*roachpb.WriteIntentError)(nil)) { 391 t.Fatalf("%+v", err) 392 } 393 } 394 395 // After EvalAddSSTable, cArgs.Stats contains a diff to the existing 396 // stats. Make sure recomputing from scratch gets the same answer as 397 // applying the diff to the stats 398 beforeStats := func() enginepb.MVCCStats { 399 iter := e.NewIterator(storage.IterOptions{UpperBound: roachpb.KeyMax}) 400 defer iter.Close() 401 beforeStats, err := storage.ComputeStatsGo(iter, roachpb.KeyMin, roachpb.KeyMax, 10) 402 if err != nil { 403 t.Fatalf("%+v", err) 404 } 405 return beforeStats 406 }() 407 408 mkSST := func(kvs []storage.MVCCKeyValue) []byte { 409 sstFile := &storage.MemFile{} 410 sst := storage.MakeBackupSSTWriter(sstFile) 411 defer sst.Close() 412 for _, kv := range kvs { 413 if err := sst.Put(kv.Key, kv.Value); err != nil { 414 t.Fatalf("%+v", err) 415 } 416 } 417 if err := sst.Finish(); err != nil { 418 t.Fatalf("%+v", err) 419 } 420 return sstFile.Data() 421 } 422 423 sstBytes := mkSST(sstKVs) 424 425 cArgs := batcheval.CommandArgs{ 426 Header: roachpb.Header{ 427 Timestamp: hlc.Timestamp{WallTime: 7}, 428 }, 429 Args: &roachpb.AddSSTableRequest{ 430 RequestHeader: roachpb.RequestHeader{Key: keys.MinKey, EndKey: keys.MaxKey}, 431 Data: sstBytes, 432 }, 433 Stats: &enginepb.MVCCStats{}, 434 } 435 if _, err := batcheval.EvalAddSSTable(ctx, e, cArgs, nil); err != nil { 436 t.Fatalf("%+v", err) 437 } 438 439 evaledStats := beforeStats 440 evaledStats.Add(*cArgs.Stats) 441 442 if err := e.WriteFile("sst", sstBytes); err != nil { 443 t.Fatalf("%+v", err) 444 } 445 if err := e.IngestExternalFiles(ctx, []string{"sst"}); err != nil { 446 t.Fatalf("%+v", err) 447 } 448 449 afterStats := func() enginepb.MVCCStats { 450 iter := e.NewIterator(storage.IterOptions{UpperBound: roachpb.KeyMax}) 451 defer iter.Close() 452 afterStats, err := storage.ComputeStatsGo(iter, roachpb.KeyMin, roachpb.KeyMax, 10) 453 if err != nil { 454 t.Fatalf("%+v", err) 455 } 456 return afterStats 457 }() 458 evaledStats.Add(delta) 459 evaledStats.ContainsEstimates = 0 460 if !afterStats.Equal(evaledStats) { 461 t.Errorf("mvcc stats mismatch: diff(expected, actual): %s", pretty.Diff(afterStats, evaledStats)) 462 } 463 464 cArgsWithStats := batcheval.CommandArgs{ 465 Header: roachpb.Header{Timestamp: hlc.Timestamp{WallTime: 7}}, 466 Args: &roachpb.AddSSTableRequest{ 467 RequestHeader: roachpb.RequestHeader{Key: keys.MinKey, EndKey: keys.MaxKey}, 468 Data: mkSST([]storage.MVCCKeyValue{{ 469 Key: storage.MVCCKey{Key: roachpb.Key("zzzzzzz"), Timestamp: ts}, 470 Value: roachpb.MakeValueFromBytes([]byte("zzz")).RawBytes, 471 }}), 472 MVCCStats: &enginepb.MVCCStats{KeyCount: 10}, 473 }, 474 Stats: &enginepb.MVCCStats{}, 475 } 476 if _, err := batcheval.EvalAddSSTable(ctx, e, cArgsWithStats, nil); err != nil { 477 t.Fatalf("%+v", err) 478 } 479 expected := enginepb.MVCCStats{ContainsEstimates: 1, KeyCount: 10} 480 if got := *cArgsWithStats.Stats; got != expected { 481 t.Fatalf("expected %v got %v", expected, got) 482 } 483 }) 484 } 485 } 486 487 func TestAddSSTableDisallowShadowing(t *testing.T) { 488 defer leaktest.AfterTest(t)() 489 490 ctx := context.Background() 491 for _, engineImpl := range engineImpls { 492 t.Run(engineImpl.name, func(t *testing.T) { 493 e := engineImpl.create() 494 defer e.Close() 495 496 for _, kv := range mvccKVsFromStrs([]strKv{ 497 {"a", 2, "aa"}, 498 {"b", 1, "bb"}, 499 {"b", 6, ""}, 500 {"g", 5, "gg"}, 501 {"r", 1, "rr"}, 502 {"y", 1, "yy"}, 503 {"y", 2, ""}, 504 {"y", 5, "yyy"}, 505 {"z", 2, "zz"}, 506 }) { 507 if err := e.Put(kv.Key, kv.Value); err != nil { 508 t.Fatalf("%+v", err) 509 } 510 } 511 512 getSSTBytes := func(sstKVs []storage.MVCCKeyValue) []byte { 513 sstFile := &storage.MemFile{} 514 sst := storage.MakeBackupSSTWriter(sstFile) 515 defer sst.Close() 516 for _, kv := range sstKVs { 517 if err := sst.Put(kv.Key, kv.Value); err != nil { 518 t.Fatalf("%+v", err) 519 } 520 } 521 if err := sst.Finish(); err != nil { 522 t.Fatalf("%+v", err) 523 } 524 return sstFile.Data() 525 } 526 527 getStats := func(startKey, endKey roachpb.Key, data []byte) enginepb.MVCCStats { 528 dataIter, err := storage.NewMemSSTIterator(data, true) 529 if err != nil { 530 return enginepb.MVCCStats{} 531 } 532 defer dataIter.Close() 533 534 stats, err := storage.ComputeStatsGo(dataIter, startKey, endKey, 0) 535 if err != nil { 536 t.Fatalf("%+v", err) 537 } 538 return stats 539 } 540 541 // Test key collision when ingesting a key in the start of existing data, and 542 // SST. The colliding key is also equal to the header start key. 543 { 544 sstKVs := mvccKVsFromStrs([]strKv{ 545 {"a", 7, "aa"}, // colliding key has a higher timestamp than existing version. 546 }) 547 548 sstBytes := getSSTBytes(sstKVs) 549 stats := getStats(roachpb.Key("a"), roachpb.Key("b"), sstBytes) 550 cArgs := batcheval.CommandArgs{ 551 Header: roachpb.Header{ 552 Timestamp: hlc.Timestamp{WallTime: 7}, 553 }, 554 Args: &roachpb.AddSSTableRequest{ 555 RequestHeader: roachpb.RequestHeader{Key: roachpb.Key("a"), EndKey: roachpb.Key("b")}, 556 Data: sstBytes, 557 DisallowShadowing: true, 558 MVCCStats: &stats, 559 }, 560 Stats: &enginepb.MVCCStats{}, 561 } 562 563 _, err := batcheval.EvalAddSSTable(ctx, e, cArgs, nil) 564 if !testutils.IsError(err, "ingested key collides with an existing one: \"a\"") { 565 t.Fatalf("%+v", err) 566 } 567 } 568 569 // Test key collision when ingesting a key in the middle of existing data, and 570 // start of the SST. The key is equal to the header start key. 571 { 572 sstKVs := mvccKVsFromStrs([]strKv{ 573 {"g", 4, "ggg"}, // colliding key has a lower timestamp than existing version. 574 }) 575 576 sstBytes := getSSTBytes(sstKVs) 577 cArgs := batcheval.CommandArgs{ 578 Header: roachpb.Header{ 579 Timestamp: hlc.Timestamp{WallTime: 7}, 580 }, 581 Args: &roachpb.AddSSTableRequest{ 582 RequestHeader: roachpb.RequestHeader{Key: roachpb.Key("g"), EndKey: roachpb.Key("h")}, 583 Data: sstBytes, 584 DisallowShadowing: true, 585 }, 586 Stats: &enginepb.MVCCStats{}, 587 } 588 589 _, err := batcheval.EvalAddSSTable(ctx, e, cArgs, nil) 590 if !testutils.IsError(err, "ingested key collides with an existing one: \"g\"") { 591 t.Fatalf("%+v", err) 592 } 593 } 594 595 // Test key collision when ingesting a key at the end of the existing data and 596 // SST. The colliding key is not equal to header start key. 597 { 598 sstKVs := mvccKVsFromStrs([]strKv{ 599 {"f", 2, "f"}, 600 {"h", 4, "h"}, 601 {"s", 1, "s"}, 602 {"z", 3, "z"}, // colliding key has a higher timestamp than existing version. 603 }) 604 605 sstBytes := getSSTBytes(sstKVs) 606 cArgs := batcheval.CommandArgs{ 607 Header: roachpb.Header{ 608 Timestamp: hlc.Timestamp{WallTime: 7}, 609 }, 610 Args: &roachpb.AddSSTableRequest{ 611 RequestHeader: roachpb.RequestHeader{Key: roachpb.Key("f"), EndKey: roachpb.Key("zz")}, 612 Data: sstBytes, 613 DisallowShadowing: true, 614 }, 615 } 616 617 _, err := batcheval.EvalAddSSTable(ctx, e, cArgs, nil) 618 if !testutils.IsError(err, "ingested key collides with an existing one: \"z\"") { 619 t.Fatalf("%+v", err) 620 } 621 } 622 623 // Test for no key collision where the key range being ingested into is 624 // non-empty. The header start and end keys are not existing keys. 625 { 626 sstKVs := mvccKVsFromStrs([]strKv{ 627 {"c", 2, "bb"}, 628 {"h", 6, "hh"}, 629 }) 630 631 sstBytes := getSSTBytes(sstKVs) 632 stats := getStats(roachpb.Key("c"), roachpb.Key("i"), sstBytes) 633 cArgs := batcheval.CommandArgs{ 634 Header: roachpb.Header{ 635 Timestamp: hlc.Timestamp{WallTime: 7}, 636 }, 637 Args: &roachpb.AddSSTableRequest{ 638 RequestHeader: roachpb.RequestHeader{Key: roachpb.Key("c"), EndKey: roachpb.Key("i")}, 639 Data: sstBytes, 640 DisallowShadowing: true, 641 MVCCStats: &stats, 642 }, 643 Stats: &enginepb.MVCCStats{}, 644 } 645 646 _, err := batcheval.EvalAddSSTable(ctx, e, cArgs, nil) 647 if err != nil { 648 t.Fatalf("%+v", err) 649 } 650 } 651 652 // Test that a collision is not reported when ingesting a key for which we 653 // find a tombstone from an MVCC delete, and the sst key has a ts >= tombstone 654 // ts. Also test that iteration continues from the next key in the existing 655 // data after skipping over all the versions of the deleted key. 656 { 657 sstKVs := mvccKVsFromStrs([]strKv{ 658 {"b", 7, "bb"}, // colliding key has a higher timestamp than its deleted version. 659 {"b", 1, "bbb"}, // older version of deleted key (should be skipped over). 660 {"f", 3, "ff"}, 661 {"y", 3, "yyyy"}, // colliding key. 662 }) 663 664 sstBytes := getSSTBytes(sstKVs) 665 cArgs := batcheval.CommandArgs{ 666 Header: roachpb.Header{ 667 Timestamp: hlc.Timestamp{WallTime: 7}, 668 }, 669 Args: &roachpb.AddSSTableRequest{ 670 RequestHeader: roachpb.RequestHeader{Key: roachpb.Key("b"), EndKey: roachpb.Key("z")}, 671 Data: sstBytes, 672 DisallowShadowing: true, 673 }, 674 Stats: &enginepb.MVCCStats{}, 675 } 676 677 _, err := batcheval.EvalAddSSTable(ctx, e, cArgs, nil) 678 if !testutils.IsError(err, "ingested key collides with an existing one: \"y\"") { 679 t.Fatalf("%+v", err) 680 } 681 } 682 683 // Test that a collision is reported when ingesting a key for which we find a 684 // tombstone from an MVCC delete, but the sst key has a ts < tombstone ts. 685 { 686 sstKVs := mvccKVsFromStrs([]strKv{ 687 {"b", 4, "bb"}, // colliding key has a lower timestamp than its deleted version. 688 {"f", 3, "ff"}, 689 {"y", 3, "yyyy"}, 690 }) 691 692 sstBytes := getSSTBytes(sstKVs) 693 cArgs := batcheval.CommandArgs{ 694 Header: roachpb.Header{ 695 Timestamp: hlc.Timestamp{WallTime: 7}, 696 }, 697 Args: &roachpb.AddSSTableRequest{ 698 RequestHeader: roachpb.RequestHeader{Key: roachpb.Key("b"), EndKey: roachpb.Key("z")}, 699 Data: sstBytes, 700 DisallowShadowing: true, 701 }, 702 Stats: &enginepb.MVCCStats{}, 703 } 704 705 _, err := batcheval.EvalAddSSTable(ctx, e, cArgs, nil) 706 if !testutils.IsError(err, "ingested key collides with an existing one: \"b\"") { 707 t.Fatalf("%+v", err) 708 } 709 } 710 711 // Test key collision when ingesting a key which has been deleted, and readded 712 // in the middle of the existing data. The colliding key is in the middle of 713 // the SST, and is the earlier of the two possible collisions. 714 { 715 sstKVs := mvccKVsFromStrs([]strKv{ 716 {"f", 2, "ff"}, 717 {"y", 4, "yyy"}, // colliding key has a lower timestamp than the readded version. 718 {"z", 3, "zzz"}, 719 }) 720 721 sstBytes := getSSTBytes(sstKVs) 722 cArgs := batcheval.CommandArgs{ 723 Header: roachpb.Header{ 724 Timestamp: hlc.Timestamp{WallTime: 7}, 725 }, 726 Args: &roachpb.AddSSTableRequest{ 727 RequestHeader: roachpb.RequestHeader{Key: roachpb.Key("f"), EndKey: roachpb.Key("zz")}, 728 Data: sstBytes, 729 DisallowShadowing: true, 730 }, 731 Stats: &enginepb.MVCCStats{}, 732 } 733 734 _, err := batcheval.EvalAddSSTable(ctx, e, cArgs, nil) 735 if !testutils.IsError(err, "ingested key collides with an existing one: \"y\"") { 736 t.Fatalf("%+v", err) 737 } 738 } 739 740 // Test key collision when ingesting a key which has a write intent in the 741 // existing data. 742 { 743 sstKVs := mvccKVsFromStrs([]strKv{ 744 {"f", 2, "ff"}, 745 {"q", 4, "qq"}, 746 {"t", 3, "ttt"}, // has a write intent in the existing data. 747 }) 748 749 // Add in a write intent. 750 ts := hlc.Timestamp{WallTime: 7} 751 txn := roachpb.MakeTransaction( 752 "test", 753 nil, // baseKey 754 roachpb.NormalUserPriority, 755 ts, 756 base.DefaultMaxClockOffset.Nanoseconds(), 757 ) 758 if err := storage.MVCCPut( 759 ctx, e, nil, []byte("t"), ts, 760 roachpb.MakeValueFromBytes([]byte("tt")), 761 &txn, 762 ); err != nil { 763 if !errors.HasType(err, (*roachpb.WriteIntentError)(nil)) { 764 t.Fatalf("%+v", err) 765 } 766 } 767 768 sstBytes := getSSTBytes(sstKVs) 769 cArgs := batcheval.CommandArgs{ 770 Header: roachpb.Header{ 771 Timestamp: hlc.Timestamp{WallTime: 7}, 772 }, 773 Args: &roachpb.AddSSTableRequest{ 774 RequestHeader: roachpb.RequestHeader{Key: roachpb.Key("f"), EndKey: roachpb.Key("u")}, 775 Data: sstBytes, 776 DisallowShadowing: true, 777 }, 778 Stats: &enginepb.MVCCStats{}, 779 } 780 781 _, err := batcheval.EvalAddSSTable(ctx, e, cArgs, nil) 782 if !testutils.IsError(err, "conflicting intents on \"t") { 783 t.Fatalf("%+v", err) 784 } 785 } 786 787 // Test key collision when ingesting a key which has an inline value in the 788 // existing data. 789 { 790 sstKVs := mvccKVsFromStrs([]strKv{ 791 {"f", 2, "ff"}, 792 {"i", 4, "ii"}, // has an inline value in existing data. 793 {"j", 3, "jj"}, 794 }) 795 796 // Add in an inline value. 797 ts := hlc.Timestamp{} 798 if err := storage.MVCCPut( 799 ctx, e, nil, []byte("i"), ts, 800 roachpb.MakeValueFromBytes([]byte("i")), 801 nil, 802 ); err != nil { 803 t.Fatalf("%+v", err) 804 } 805 806 sstBytes := getSSTBytes(sstKVs) 807 cArgs := batcheval.CommandArgs{ 808 Header: roachpb.Header{ 809 Timestamp: hlc.Timestamp{WallTime: 7}, 810 }, 811 Args: &roachpb.AddSSTableRequest{ 812 RequestHeader: roachpb.RequestHeader{Key: roachpb.Key("f"), EndKey: roachpb.Key("k")}, 813 Data: sstBytes, 814 DisallowShadowing: true, 815 }, 816 Stats: &enginepb.MVCCStats{}, 817 } 818 819 _, err := batcheval.EvalAddSSTable(ctx, e, cArgs, nil) 820 if !testutils.IsError(err, "inline values are unsupported when checking for key collisions") { 821 t.Fatalf("%+v", err) 822 } 823 } 824 825 // Test ingesting a key with the same timestamp and value. This should not 826 // trigger a collision error. 827 { 828 sstKVs := mvccKVsFromStrs([]strKv{ 829 {"e", 4, "ee"}, 830 {"f", 2, "ff"}, 831 {"y", 5, "yyy"}, // key has the same timestamp and value as the one present in the existing data. 832 }) 833 834 sstBytes := getSSTBytes(sstKVs) 835 stats := getStats(roachpb.Key("e"), roachpb.Key("zz"), sstBytes) 836 cArgs := batcheval.CommandArgs{ 837 Header: roachpb.Header{ 838 Timestamp: hlc.Timestamp{WallTime: 7}, 839 }, 840 Args: &roachpb.AddSSTableRequest{ 841 RequestHeader: roachpb.RequestHeader{Key: roachpb.Key("e"), EndKey: roachpb.Key("zz")}, 842 Data: sstBytes, 843 DisallowShadowing: true, 844 MVCCStats: &stats, 845 }, 846 Stats: &enginepb.MVCCStats{}, 847 } 848 849 _, err := batcheval.EvalAddSSTable(ctx, e, cArgs, nil) 850 if err != nil { 851 t.Fatalf("%+v", err) 852 } 853 } 854 855 // Test ingesting a key with different timestamp but same value. This should 856 // trigger a collision error. 857 { 858 sstKVs := mvccKVsFromStrs([]strKv{ 859 {"f", 2, "ff"}, 860 {"y", 6, "yyy"}, // key has a higher timestamp but same value as the one present in the existing data. 861 {"z", 3, "zzz"}, 862 }) 863 864 sstBytes := getSSTBytes(sstKVs) 865 cArgs := batcheval.CommandArgs{ 866 Header: roachpb.Header{ 867 Timestamp: hlc.Timestamp{WallTime: 7}, 868 }, 869 Args: &roachpb.AddSSTableRequest{ 870 RequestHeader: roachpb.RequestHeader{Key: roachpb.Key("f"), EndKey: roachpb.Key("zz")}, 871 Data: sstBytes, 872 DisallowShadowing: true, 873 }, 874 Stats: &enginepb.MVCCStats{}, 875 } 876 877 _, err := batcheval.EvalAddSSTable(ctx, e, cArgs, nil) 878 if !testutils.IsError(err, "ingested key collides with an existing one: \"y\"") { 879 t.Fatalf("%+v", err) 880 } 881 } 882 883 // Test ingesting a key with the same timestamp but different value. This should 884 // trigger a collision error. 885 { 886 sstKVs := mvccKVsFromStrs([]strKv{ 887 {"f", 2, "ff"}, 888 {"y", 5, "yyyy"}, // key has the same timestamp but different value as the one present in the existing data. 889 {"z", 3, "zzz"}, 890 }) 891 892 sstBytes := getSSTBytes(sstKVs) 893 cArgs := batcheval.CommandArgs{ 894 Header: roachpb.Header{ 895 Timestamp: hlc.Timestamp{WallTime: 7}, 896 }, 897 Args: &roachpb.AddSSTableRequest{ 898 RequestHeader: roachpb.RequestHeader{Key: roachpb.Key("f"), EndKey: roachpb.Key("zz")}, 899 Data: sstBytes, 900 DisallowShadowing: true, 901 }, 902 Stats: &enginepb.MVCCStats{}, 903 } 904 905 _, err := batcheval.EvalAddSSTable(ctx, e, cArgs, nil) 906 if !testutils.IsError(err, "ingested key collides with an existing one: \"y\"") { 907 t.Fatalf("%+v", err) 908 } 909 } 910 911 // Test that a collision after a key with the same timestamp and value causes 912 // a collision error. 913 { 914 sstKVs := mvccKVsFromStrs([]strKv{ 915 {"f", 2, "ff"}, 916 {"y", 5, "yyy"}, // key has the same timestamp and value as the one present in the existing data - not a collision. 917 {"z", 3, "zzz"}, // shadow key 918 }) 919 920 sstBytes := getSSTBytes(sstKVs) 921 cArgs := batcheval.CommandArgs{ 922 Header: roachpb.Header{ 923 Timestamp: hlc.Timestamp{WallTime: 7}, 924 }, 925 Args: &roachpb.AddSSTableRequest{ 926 RequestHeader: roachpb.RequestHeader{Key: roachpb.Key("e"), EndKey: roachpb.Key("zz")}, 927 Data: sstBytes, 928 DisallowShadowing: true, 929 }, 930 Stats: &enginepb.MVCCStats{}, 931 } 932 933 _, err := batcheval.EvalAddSSTable(ctx, e, cArgs, nil) 934 if !testutils.IsError(err, "ingested key collides with an existing one: \"z\"") { 935 t.Fatalf("%+v", err) 936 } 937 } 938 939 // This test ensures accuracy of MVCCStats in the situation that successive 940 // SSTs being ingested via AddSSTable have "perfectly shadowing" keys (same ts 941 // and value). Such KVs are not considered as collisions and so while they are 942 // skipped during ingestion, their stats would previously be double counted. 943 // To mitigate this problem we now return the stats of such skipped KVs while 944 // evaluating the AddSSTable command, and accumulate accurate stats in the 945 // CommandArgs Stats field by using: 946 // cArgs.Stats + ingested_stats - skipped_stats. 947 { 948 // Successfully evaluate the first SST as there are no key collisions. 949 sstKVs := mvccKVsFromStrs([]strKv{ 950 {"c", 2, "bb"}, 951 {"h", 6, "hh"}, 952 }) 953 954 sstBytes := getSSTBytes(sstKVs) 955 stats := getStats(roachpb.Key("c"), roachpb.Key("i"), sstBytes) 956 957 // Accumulate stats across SST ingestion. 958 commandStats := enginepb.MVCCStats{} 959 960 cArgs := batcheval.CommandArgs{ 961 Header: roachpb.Header{ 962 Timestamp: hlc.Timestamp{WallTime: 7}, 963 }, 964 Args: &roachpb.AddSSTableRequest{ 965 RequestHeader: roachpb.RequestHeader{Key: roachpb.Key("c"), EndKey: roachpb.Key("i")}, 966 Data: sstBytes, 967 DisallowShadowing: true, 968 MVCCStats: &stats, 969 }, 970 Stats: &commandStats, 971 } 972 _, err := batcheval.EvalAddSSTable(ctx, e, cArgs, nil) 973 if err != nil { 974 t.Fatalf("%+v", err) 975 } 976 firstSSTStats := commandStats 977 978 // Insert KV entries so that we can correctly identify keys to skip when 979 // ingesting the perfectly shadowing KVs (same ts and same value) in the 980 // second SST. 981 for _, kv := range sstKVs { 982 if err := e.Put(kv.Key, kv.Value); err != nil { 983 t.Fatalf("%+v", err) 984 } 985 } 986 987 // Evaluate the second SST. Both the KVs are perfectly shadowing and should 988 // not contribute to the stats. 989 secondSSTKVs := mvccKVsFromStrs([]strKv{ 990 {"c", 2, "bb"}, // key has the same timestamp and value as the one present in the existing data. 991 {"h", 6, "hh"}, // key has the same timestamp and value as the one present in the existing data. 992 }) 993 secondSSTBytes := getSSTBytes(secondSSTKVs) 994 secondStats := getStats(roachpb.Key("c"), roachpb.Key("i"), secondSSTBytes) 995 996 cArgs.Args = &roachpb.AddSSTableRequest{ 997 RequestHeader: roachpb.RequestHeader{Key: roachpb.Key("c"), EndKey: roachpb.Key("i")}, 998 Data: secondSSTBytes, 999 DisallowShadowing: true, 1000 MVCCStats: &secondStats, 1001 } 1002 _, err = batcheval.EvalAddSSTable(ctx, e, cArgs, nil) 1003 if err != nil { 1004 t.Fatalf("%+v", err) 1005 } 1006 1007 // Check that there has been no double counting of stats. 1008 if !firstSSTStats.Equal(*cArgs.Stats) { 1009 t.Errorf("mvcc stats should not have changed as all keys in second SST are shadowing: %s", 1010 pretty.Diff(firstSSTStats, *cArgs.Stats)) 1011 } 1012 1013 // Evaluate the third SST. Two of the three KVs are perfectly shadowing, but 1014 // there is one valid KV which should contribute to the stats. 1015 thirdSSTKVs := mvccKVsFromStrs([]strKv{ 1016 {"c", 2, "bb"}, // key has the same timestamp and value as the one present in the existing data. 1017 {"e", 2, "ee"}, 1018 {"h", 6, "hh"}, // key has the same timestamp and value as the one present in the existing data. 1019 }) 1020 thirdSSTBytes := getSSTBytes(thirdSSTKVs) 1021 thirdStats := getStats(roachpb.Key("c"), roachpb.Key("i"), thirdSSTBytes) 1022 1023 cArgs.Args = &roachpb.AddSSTableRequest{ 1024 RequestHeader: roachpb.RequestHeader{Key: roachpb.Key("c"), EndKey: roachpb.Key("i")}, 1025 Data: thirdSSTBytes, 1026 DisallowShadowing: true, 1027 MVCCStats: &thirdStats, 1028 } 1029 _, err = batcheval.EvalAddSSTable(ctx, e, cArgs, nil) 1030 if err != nil { 1031 t.Fatalf("%+v", err) 1032 } 1033 1034 // This is the stats contribution of the KV {"e", 2, "ee"}. This should be 1035 // the only addition to the cumulative stats, as the other two KVs are 1036 // perfect shadows of existing data. 1037 var delta enginepb.MVCCStats 1038 delta.LiveCount = 1 1039 delta.LiveBytes = 21 1040 delta.KeyCount = 1 1041 delta.KeyBytes = 14 1042 delta.ValCount = 1 1043 delta.ValBytes = 7 1044 1045 // Check that there has been no double counting of stats. 1046 firstSSTStats.Add(delta) 1047 if !firstSSTStats.Equal(*cArgs.Stats) { 1048 t.Errorf("mvcc stats are not accurate: %s", 1049 pretty.Diff(firstSSTStats, *cArgs.Stats)) 1050 } 1051 } 1052 }) 1053 } 1054 }