github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/batcheval/cmd_revert_range_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
    12  
    13  import (
    14  	"bytes"
    15  	"context"
    16  	"crypto/sha256"
    17  	"fmt"
    18  	"testing"
    19  	"time"
    20  
    21  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    22  	"github.com/cockroachdb/cockroach/pkg/storage"
    23  	"github.com/cockroachdb/cockroach/pkg/storage/enginepb"
    24  	"github.com/cockroachdb/cockroach/pkg/testutils"
    25  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    26  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    27  )
    28  
    29  func hashRange(t *testing.T, reader storage.Reader, start, end roachpb.Key) []byte {
    30  	t.Helper()
    31  	h := sha256.New()
    32  	if err := reader.Iterate(start, end,
    33  		func(kv storage.MVCCKeyValue) (bool, error) {
    34  			h.Write(kv.Key.Key)
    35  			h.Write(kv.Value)
    36  			return false, nil
    37  		},
    38  	); err != nil {
    39  		t.Fatal(err)
    40  	}
    41  	return h.Sum(nil)
    42  }
    43  
    44  func getStats(t *testing.T, reader storage.Reader) enginepb.MVCCStats {
    45  	t.Helper()
    46  	iter := reader.NewIterator(storage.IterOptions{UpperBound: roachpb.KeyMax})
    47  	defer iter.Close()
    48  	s, err := storage.ComputeStatsGo(iter, roachpb.KeyMin, roachpb.KeyMax, 1100)
    49  	if err != nil {
    50  		t.Fatalf("%+v", err)
    51  	}
    52  	return s
    53  }
    54  
    55  // createTestRocksDBEngine returns a new in-memory RocksDB engine with 1MB of
    56  // storage capacity.
    57  func createTestRocksDBEngine(ctx context.Context) storage.Engine {
    58  	return storage.NewInMem(ctx, enginepb.EngineTypeRocksDB, roachpb.Attributes{}, 1<<20)
    59  }
    60  
    61  // createTestPebbleEngine returns a new in-memory Pebble storage engine.
    62  func createTestPebbleEngine(ctx context.Context) storage.Engine {
    63  	return storage.NewInMem(ctx, enginepb.EngineTypePebble, roachpb.Attributes{}, 1<<20)
    64  }
    65  
    66  var engineImpls = []struct {
    67  	name   string
    68  	create func(context.Context) storage.Engine
    69  }{
    70  	{"rocksdb", createTestRocksDBEngine},
    71  	{"pebble", createTestPebbleEngine},
    72  }
    73  
    74  func TestCmdRevertRange(t *testing.T) {
    75  	defer leaktest.AfterTest(t)()
    76  
    77  	startKey := roachpb.Key("0000")
    78  	endKey := roachpb.Key("9999")
    79  	const keyCount = 10
    80  
    81  	ctx := context.Background()
    82  
    83  	// Run this test on both RocksDB and Pebble. Regression test for:
    84  	// https://github.com/cockroachdb/cockroach/pull/42386
    85  	for _, engineImpl := range engineImpls {
    86  		t.Run(engineImpl.name, func(t *testing.T) {
    87  			eng := engineImpl.create(ctx)
    88  			defer eng.Close()
    89  
    90  			baseTime := hlc.Timestamp{WallTime: 1000}
    91  
    92  			// Lay down some keys to be the starting point to which we'll revert later.
    93  			var stats enginepb.MVCCStats
    94  			for i := 0; i < keyCount; i++ {
    95  				key := roachpb.Key(fmt.Sprintf("%04d", i))
    96  				var value roachpb.Value
    97  				value.SetString(fmt.Sprintf("%d", i))
    98  				if err := storage.MVCCPut(ctx, eng, &stats, key, baseTime.Add(int64(i%10), 0), value, nil); err != nil {
    99  					t.Fatal(err)
   100  				}
   101  			}
   102  
   103  			tsA := baseTime.Add(100, 0)
   104  			sumA := hashRange(t, eng, startKey, endKey)
   105  
   106  			// Lay down some more keys that we'll revert later, with some of them
   107  			// shadowing existing keys and some as new keys.
   108  			for i := 5; i < keyCount+5; i++ {
   109  				key := roachpb.Key(fmt.Sprintf("%04d", i))
   110  				var value roachpb.Value
   111  				value.SetString(fmt.Sprintf("%d-rev-a", i))
   112  				if err := storage.MVCCPut(ctx, eng, &stats, key, tsA.Add(int64(i%5), 1), value, nil); err != nil {
   113  					t.Fatal(err)
   114  				}
   115  			}
   116  
   117  			sumB := hashRange(t, eng, startKey, endKey)
   118  			tsB := tsA.Add(10, 0)
   119  
   120  			// Lay down more keys, this time shadowing some of our earlier shadows too.
   121  			for i := 7; i < keyCount+7; i++ {
   122  				key := roachpb.Key(fmt.Sprintf("%04d", i))
   123  				var value roachpb.Value
   124  				value.SetString(fmt.Sprintf("%d-rev-b", i))
   125  				if err := storage.MVCCPut(ctx, eng, &stats, key, tsB.Add(1, int32(i%5)), value, nil); err != nil {
   126  					t.Fatal(err)
   127  				}
   128  			}
   129  
   130  			sumC := hashRange(t, eng, startKey, endKey)
   131  			tsC := tsB.Add(10, 0)
   132  
   133  			desc := roachpb.RangeDescriptor{RangeID: 99,
   134  				StartKey: roachpb.RKey(startKey),
   135  				EndKey:   roachpb.RKey(endKey),
   136  			}
   137  			cArgs := CommandArgs{Header: roachpb.Header{RangeID: desc.RangeID, Timestamp: tsC, MaxSpanRequestKeys: 2}}
   138  			evalCtx := &MockEvalCtx{Desc: &desc, Clock: hlc.NewClock(hlc.UnixNano, time.Nanosecond), Stats: stats}
   139  			cArgs.EvalCtx = evalCtx.EvalContext()
   140  			afterStats := getStats(t, eng)
   141  			for _, tc := range []struct {
   142  				name     string
   143  				ts       hlc.Timestamp
   144  				expected []byte
   145  				resumes  int
   146  			}{
   147  				{"revert revert to time A", tsA, sumA, 4},
   148  				{"revert revert to time B", tsB, sumB, 4},
   149  				{"revert revert to time C (nothing)", tsC, sumC, 0},
   150  			} {
   151  				t.Run(tc.name, func(t *testing.T) {
   152  					batch := &wrappedBatch{Batch: eng.NewBatch()}
   153  					defer batch.Close()
   154  
   155  					req := roachpb.RevertRangeRequest{
   156  						RequestHeader: roachpb.RequestHeader{Key: startKey, EndKey: endKey}, TargetTime: tc.ts,
   157  					}
   158  					cArgs.Stats = &enginepb.MVCCStats{}
   159  					cArgs.Args = &req
   160  					var resumes int
   161  					for {
   162  						var reply roachpb.RevertRangeResponse
   163  						if _, err := RevertRange(ctx, batch, cArgs, &reply); err != nil {
   164  							t.Fatal(err)
   165  						}
   166  						if reply.ResumeSpan == nil {
   167  							break
   168  						}
   169  						resumes++
   170  						req.RequestHeader.Key = reply.ResumeSpan.Key
   171  					}
   172  					if resumes != tc.resumes {
   173  						// NB: since ClearTimeRange buffers keys until it hits one that is not
   174  						// going to be cleared, and thus may exceed the max batch size by up to
   175  						// the buffer size (64) when it flushes after breaking out of the loop,
   176  						// expected resumes isn't *quite* a simple num_cleared_keys/batch_size.
   177  						t.Fatalf("expected %d resumes, got %d", tc.resumes, resumes)
   178  					}
   179  					if reverted := hashRange(t, batch, startKey, endKey); !bytes.Equal(reverted, tc.expected) {
   180  						t.Error("expected reverted keys to match checksum")
   181  					}
   182  					evalStats := afterStats
   183  					evalStats.Add(*cArgs.Stats)
   184  					if realStats := getStats(t, batch); !evalStats.Equal(evalStats) {
   185  						t.Fatalf("stats mismatch:\npre-revert\t%+v\nevaled:\t%+v\neactual\t%+v", afterStats, evalStats, realStats)
   186  					}
   187  				})
   188  			}
   189  
   190  			t.Run("checks gc threshold", func(t *testing.T) {
   191  				batch := &wrappedBatch{Batch: eng.NewBatch()}
   192  				defer batch.Close()
   193  				evalCtx.GCThreshold = tsB
   194  				cArgs.Args = &roachpb.RevertRangeRequest{
   195  					RequestHeader: roachpb.RequestHeader{Key: startKey, EndKey: endKey}, TargetTime: tsB,
   196  				}
   197  				if _, err := RevertRange(ctx, batch, cArgs, &roachpb.RevertRangeResponse{}); !testutils.IsError(err, "replica GC threshold") {
   198  					t.Fatal(err)
   199  				}
   200  			})
   201  
   202  			txn := roachpb.MakeTransaction("test", nil, roachpb.NormalUserPriority, tsC, 1)
   203  			if err := storage.MVCCPut(
   204  				ctx, eng, &stats, []byte("0012"), tsC, roachpb.MakeValueFromBytes([]byte("i")), &txn,
   205  			); err != nil {
   206  				t.Fatal(err)
   207  			}
   208  			sumCIntent := hashRange(t, eng, startKey, endKey)
   209  
   210  			// Lay down more revisions (skipping even keys to avoid our intent on 0012).
   211  			for i := 7; i < keyCount+7; i += 2 {
   212  				key := roachpb.Key(fmt.Sprintf("%04d", i))
   213  				var value roachpb.Value
   214  				value.SetString(fmt.Sprintf("%d-rev-b", i))
   215  				if err := storage.MVCCPut(ctx, eng, &stats, key, tsC.Add(10, int32(i%5)), value, nil); err != nil {
   216  					t.Fatalf("writing key %s: %+v", key, err)
   217  				}
   218  			}
   219  			tsD := tsC.Add(100, 0)
   220  			sumD := hashRange(t, eng, startKey, endKey)
   221  
   222  			cArgs.Header.Timestamp = tsD
   223  			// Re-set EvalCtx to pick up revised stats.
   224  			cArgs.EvalCtx = (&MockEvalCtx{Desc: &desc, Clock: hlc.NewClock(hlc.UnixNano, time.Nanosecond), Stats: stats}).EvalContext()
   225  			for _, tc := range []struct {
   226  				name        string
   227  				ts          hlc.Timestamp
   228  				expectErr   bool
   229  				expectedSum []byte
   230  				resumes     int
   231  			}{
   232  				{"hit intent", tsB, true, nil, 2},
   233  				{"hit intent exactly", tsC, false, sumCIntent, 2},
   234  				{"clear above intent", tsC.Add(0, 1), false, sumCIntent, 2},
   235  				{"clear nothing above intent", tsD, false, sumD, 0},
   236  			} {
   237  				t.Run(tc.name, func(t *testing.T) {
   238  					batch := &wrappedBatch{Batch: eng.NewBatch()}
   239  					defer batch.Close()
   240  					cArgs.Stats = &enginepb.MVCCStats{}
   241  					req := roachpb.RevertRangeRequest{
   242  						RequestHeader: roachpb.RequestHeader{Key: startKey, EndKey: endKey}, TargetTime: tc.ts,
   243  					}
   244  					cArgs.Args = &req
   245  					var resumes int
   246  					var err error
   247  					for {
   248  						var reply roachpb.RevertRangeResponse
   249  						_, err = RevertRange(ctx, batch, cArgs, &reply)
   250  						if err != nil || reply.ResumeSpan == nil {
   251  							break
   252  						}
   253  						req.RequestHeader.Key = reply.ResumeSpan.Key
   254  						resumes++
   255  					}
   256  					if resumes != tc.resumes {
   257  						t.Fatalf("expected %d resumes, got %d", tc.resumes, resumes)
   258  					}
   259  
   260  					if tc.expectErr {
   261  						if !testutils.IsError(err, "intents") {
   262  							t.Fatalf("expected write intent error; got: %T %+v", err, err)
   263  						}
   264  					} else {
   265  						if err != nil {
   266  							t.Fatal(err)
   267  						}
   268  						if reverted := hashRange(t, batch, startKey, endKey); !bytes.Equal(reverted, tc.expectedSum) {
   269  							t.Error("expected reverted keys to match checksum")
   270  						}
   271  					}
   272  				})
   273  			}
   274  		})
   275  	}
   276  }