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  }