github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/batcheval/cmd_refresh_range_test.go (about) 1 // Copyright 2018 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 "context" 15 "testing" 16 17 "github.com/cockroachdb/cockroach/pkg/keys" 18 "github.com/cockroachdb/cockroach/pkg/roachpb" 19 "github.com/cockroachdb/cockroach/pkg/storage" 20 "github.com/cockroachdb/cockroach/pkg/storage/enginepb" 21 "github.com/cockroachdb/cockroach/pkg/util/hlc" 22 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 23 "github.com/cockroachdb/cockroach/pkg/util/uuid" 24 "github.com/stretchr/testify/require" 25 ) 26 27 // TestRefreshRangeTimeBoundIterator is a regression test for 28 // https://github.com/cockroachdb/cockroach/issues/31823. RefreshRange 29 // uses a time-bound iterator, which has a bug that can cause old 30 // resolved intents to incorrectly appear to be pending. This test 31 // constructs the necessary arrangement of sstables to reproduce the 32 // bug and ensures that the workaround (and later, the permanent fix) 33 // are effective. 34 // 35 // The bug is that resolving an intent does not contribute to the 36 // sstable's timestamp bounds, so that if there is no other 37 // timestamped data expanding the bounds, time-bound iterators may 38 // open fewer sstables than necessary and only see the intent, not its 39 // resolution. 40 // 41 // This test creates two sstables. The first contains a pending intent 42 // at ts1 and another key at ts4, giving it timestamp bounds 1-4 (and 43 // putting it in scope for transactions at timestamps higher than 44 // ts1). 45 func TestRefreshRangeTimeBoundIterator(t *testing.T) { 46 defer leaktest.AfterTest(t)() 47 48 ctx := context.Background() 49 k := roachpb.Key("a") 50 v := roachpb.MakeValueFromString("hi") 51 ts1 := hlc.Timestamp{WallTime: 1} 52 ts2 := hlc.Timestamp{WallTime: 2} 53 ts3 := hlc.Timestamp{WallTime: 3} 54 ts4 := hlc.Timestamp{WallTime: 4} 55 56 db := storage.NewDefaultInMem() 57 defer db.Close() 58 59 // Create an sstable containing an unresolved intent. 60 txn := &roachpb.Transaction{ 61 TxnMeta: enginepb.TxnMeta{ 62 Key: k, 63 ID: uuid.MakeV4(), 64 Epoch: 1, 65 WriteTimestamp: ts1, 66 }, 67 ReadTimestamp: ts1, 68 } 69 if err := storage.MVCCPut(ctx, db, nil, k, txn.ReadTimestamp, v, txn); err != nil { 70 t.Fatal(err) 71 } 72 if err := storage.MVCCPut(ctx, db, nil, roachpb.Key("unused1"), ts4, v, nil); err != nil { 73 t.Fatal(err) 74 } 75 if err := db.Flush(); err != nil { 76 t.Fatal(err) 77 } 78 if err := db.Compact(); err != nil { 79 t.Fatal(err) 80 } 81 82 // Create a second sstable containing the resolution of the intent 83 // (committed). The sstable also has a second write at a different (older) 84 // timestamp, because if it were empty other than the deletion tombstone, it 85 // would not have any timestamp bounds and would be selected for every read. 86 intent := roachpb.MakeLockUpdate(txn, roachpb.Span{Key: k}) 87 intent.Status = roachpb.COMMITTED 88 if _, err := storage.MVCCResolveWriteIntent(ctx, db, nil, intent); err != nil { 89 t.Fatal(err) 90 } 91 if err := storage.MVCCPut(ctx, db, nil, roachpb.Key("unused2"), ts1, v, nil); err != nil { 92 t.Fatal(err) 93 } 94 if err := db.Flush(); err != nil { 95 t.Fatal(err) 96 } 97 98 // TODO(peter): Make this work for Pebble as well. 99 if rocksDB, ok := db.(*storage.RocksDB); ok { 100 // Double-check that we've created the SSTs we intended to. 101 userProps, err := rocksDB.GetUserProperties() 102 if err != nil { 103 t.Fatal(err) 104 } 105 require.Len(t, userProps.Sst, 2) 106 require.Equal(t, userProps.Sst[0].TsMin, &ts1) 107 require.Equal(t, userProps.Sst[0].TsMax, &ts4) 108 require.Equal(t, userProps.Sst[1].TsMin, &ts1) 109 require.Equal(t, userProps.Sst[1].TsMax, &ts1) 110 } 111 112 // We should now have a committed value at k@ts1. Read it back to make sure. 113 // This represents real-world use of time-bound iterators where callers must 114 // have previously performed a consistent read at the lower time-bound to 115 // prove that there are no intents present that would be missed by the time- 116 // bound iterator. 117 if val, intent, err := storage.MVCCGet(ctx, db, k, ts1, storage.MVCCGetOptions{}); err != nil { 118 t.Fatal(err) 119 } else if intent != nil { 120 t.Fatalf("got unexpected intent: %v", intent) 121 } else if !val.EqualData(v) { 122 t.Fatalf("expected %v, got %v", v, val) 123 } 124 125 // Now the real test: a transaction at ts2 has been pushed to ts3 126 // and must refresh. It overlaps with our committed intent on k@ts1, 127 // which is fine because our timestamp is higher (but if that intent 128 // were still pending, the new txn would be blocked). Prior to 129 // https://github.com/cockroachdb/cockroach/pull/32211, a bug in the 130 // time-bound iterator meant that we would see the first sstable but 131 // not the second and incorrectly report the intent as pending, 132 // resulting in an error from RefreshRange. 133 var resp roachpb.RefreshRangeResponse 134 _, err := RefreshRange(ctx, db, CommandArgs{ 135 Args: &roachpb.RefreshRangeRequest{ 136 RequestHeader: roachpb.RequestHeader{ 137 Key: k, 138 EndKey: keys.MaxKey, 139 }, 140 RefreshFrom: ts2, 141 }, 142 Header: roachpb.Header{ 143 Txn: &roachpb.Transaction{ 144 TxnMeta: enginepb.TxnMeta{ 145 WriteTimestamp: ts3, 146 }, 147 ReadTimestamp: ts2, 148 }, 149 Timestamp: ts3, 150 }, 151 }, &resp) 152 if err != nil { 153 t.Fatal(err) 154 } 155 }