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 }