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  }