github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/client_protectedts_test.go (about) 1 // Copyright 2019 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 kvserver_test 12 13 import ( 14 "context" 15 "regexp" 16 "testing" 17 "time" 18 19 "github.com/cockroachdb/cockroach/pkg/base" 20 "github.com/cockroachdb/cockroach/pkg/kv/kvserver" 21 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/protectedts/ptpb" 22 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/protectedts/ptstorage" 23 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/protectedts/ptverifier" 24 "github.com/cockroachdb/cockroach/pkg/roachpb" 25 "github.com/cockroachdb/cockroach/pkg/sql" 26 "github.com/cockroachdb/cockroach/pkg/testutils" 27 "github.com/cockroachdb/cockroach/pkg/testutils/testcluster" 28 "github.com/cockroachdb/cockroach/pkg/util" 29 "github.com/cockroachdb/cockroach/pkg/util/hlc" 30 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 31 "github.com/cockroachdb/cockroach/pkg/util/randutil" 32 "github.com/cockroachdb/cockroach/pkg/util/tracing" 33 "github.com/cockroachdb/cockroach/pkg/util/uuid" 34 "github.com/cockroachdb/errors" 35 "github.com/stretchr/testify/require" 36 ) 37 38 // TestProtectedTimestamps is an end-to-end test for protected timestamps. 39 // It works by writing a lot of data and waiting for the GC heuristic to allow 40 // for GC. Because of this, it's very slow and expensive. It should 41 // potentially be made cheaper by injecting hooks to force GC. 42 // 43 // Probably this test should always be skipped until it is made cheaper, 44 // nevertheless it's a useful test. 45 func TestProtectedTimestamps(t *testing.T) { 46 defer leaktest.AfterTest(t)() 47 ctx := context.Background() 48 49 if util.RaceEnabled || testing.Short() { 50 t.Skip("this test is too slow to run with race") 51 } 52 53 args := base.TestClusterArgs{} 54 args.ServerArgs.Knobs.Store = &kvserver.StoreTestingKnobs{DisableGCQueue: true} 55 tc := testcluster.StartTestCluster(t, 3, args) 56 defer tc.Stopper().Stop(ctx) 57 s0 := tc.Server(0) 58 59 conn := tc.ServerConn(0) 60 _, err := conn.Exec("CREATE TABLE foo (k INT PRIMARY KEY, v BYTES)") 61 require.NoError(t, err) 62 63 _, err = conn.Exec("SET CLUSTER SETTING kv.protectedts.poll_interval = '10ms';") 64 require.NoError(t, err) 65 66 _, err = conn.Exec("ALTER TABLE foo CONFIGURE ZONE USING " + 67 "gc.ttlseconds = 1, range_max_bytes = 1<<18, range_min_bytes = 1<<10;") 68 require.NoError(t, err) 69 70 rRand, _ := randutil.NewPseudoRand() 71 upsertUntilBackpressure := func() { 72 for { 73 _, err := conn.Exec("UPSERT INTO foo VALUES (1, $1)", 74 randutil.RandBytes(rRand, 1<<15)) 75 if testutils.IsError(err, "backpressure") { 76 break 77 } 78 require.NoError(t, err) 79 } 80 } 81 const processedPattern = `(?s)shouldQueue=true.*processing replica.*GC score after GC` 82 processedRegexp := regexp.MustCompile(processedPattern) 83 84 getTableStartKey := func(table string) roachpb.Key { 85 row := conn.QueryRow( 86 "SELECT start_key "+ 87 "FROM crdb_internal.ranges_no_leases "+ 88 "WHERE table_name = $1 "+ 89 "AND database_name = current_database() "+ 90 "ORDER BY start_key ASC "+ 91 "LIMIT 1", 92 "foo") 93 94 var startKey roachpb.Key 95 require.NoError(t, row.Scan(&startKey)) 96 return startKey 97 } 98 99 getStoreAndReplica := func() (*kvserver.Store, *kvserver.Replica) { 100 startKey := getTableStartKey("foo") 101 // Okay great now we have a key and can go find replicas and stores and what not. 102 r := tc.LookupRangeOrFatal(t, startKey) 103 l, _, err := tc.FindRangeLease(r, nil) 104 require.NoError(t, err) 105 106 lhServer := tc.Server(int(l.Replica.NodeID) - 1) 107 return getFirstStoreReplica(t, lhServer, startKey) 108 } 109 110 gcSoon := func() { 111 testutils.SucceedsSoon(t, func() error { 112 upsertUntilBackpressure() 113 s, repl := getStoreAndReplica() 114 trace, _, err := s.ManuallyEnqueue(ctx, "gc", repl, false) 115 require.NoError(t, err) 116 if !processedRegexp.MatchString(trace.String()) { 117 return errors.Errorf("%q does not match %q", trace.String(), processedRegexp) 118 } 119 return nil 120 }) 121 } 122 123 thresholdRE := regexp.MustCompile(`(?s).*Threshold:(?P<threshold>[^\s]*)`) 124 thresholdFromTrace := func(trace tracing.Recording) hlc.Timestamp { 125 threshStr := string(thresholdRE.ExpandString(nil, "$threshold", 126 trace.String(), thresholdRE.FindStringSubmatchIndex(trace.String()))) 127 thresh, err := hlc.ParseTimestamp(threshStr) 128 require.NoError(t, err) 129 return thresh 130 } 131 132 beforeWrites := s0.Clock().Now() 133 gcSoon() 134 135 pts := ptstorage.New(s0.ClusterSettings(), s0.InternalExecutor().(*sql.InternalExecutor)) 136 ptsWithDB := ptstorage.WithDatabase(pts, s0.DB()) 137 startKey := getTableStartKey("foo") 138 ptsRec := ptpb.Record{ 139 ID: uuid.MakeV4(), 140 Timestamp: s0.Clock().Now(), 141 Mode: ptpb.PROTECT_AFTER, 142 Spans: []roachpb.Span{ 143 { 144 Key: startKey, 145 EndKey: startKey.PrefixEnd(), 146 }, 147 }, 148 } 149 require.NoError(t, ptsWithDB.Protect(ctx, nil /* txn */, &ptsRec)) 150 upsertUntilBackpressure() 151 // We need to be careful choosing a time. We're a little limited because the 152 // ttl is defined in seconds and we need to wait for the threshold to be 153 // 2x the threshold with the scale factor as time since threshold. The 154 // gc threshold we'll be able to set precedes this timestamp which we'll 155 // put in the record below. 156 afterWrites := s0.Clock().Now().Add(2*time.Second.Nanoseconds(), 0) 157 s, repl := getStoreAndReplica() 158 // The protectedts record will prevent us from aging the MVCC garbage bytes 159 // past the oldest record so shouldQueue should be false. Verify that. 160 trace, _, err := s.ManuallyEnqueue(ctx, "gc", repl, false /* skipShouldQueue */) 161 require.NoError(t, err) 162 require.Regexp(t, "(?s)shouldQueue=false", trace.String()) 163 164 // If we skipShouldQueue then gc will run but it should only run up to the 165 // timestamp of our record at the latest. 166 trace, _, err = s.ManuallyEnqueue(ctx, "gc", repl, true /* skipShouldQueue */) 167 require.NoError(t, err) 168 require.Regexp(t, "(?s)done with GC evaluation for 0 keys", trace.String()) 169 thresh := thresholdFromTrace(trace) 170 require.Truef(t, thresh.Less(ptsRec.Timestamp), "threshold: %v, protected %v %q", thresh, ptsRec.Timestamp, trace) 171 172 // Verify that the record indeed did apply as far as the replica is concerned. 173 ptv := ptverifier.New(s0.DB(), pts) 174 require.NoError(t, ptv.Verify(ctx, ptsRec.ID)) 175 ptsRecVerified, err := ptsWithDB.GetRecord(ctx, nil /* txn */, ptsRec.ID) 176 require.NoError(t, err) 177 require.True(t, ptsRecVerified.Verified) 178 179 // Make a new record that is doomed to fail. 180 failedRec := ptsRec 181 failedRec.ID = uuid.MakeV4() 182 failedRec.Timestamp = beforeWrites 183 failedRec.Timestamp.Logical = 0 184 require.NoError(t, ptsWithDB.Protect(ctx, nil /* txn */, &failedRec)) 185 _, err = ptsWithDB.GetRecord(ctx, nil /* txn */, failedRec.ID) 186 require.NoError(t, err) 187 188 // Verify that it indeed did fail. 189 verifyErr := ptv.Verify(ctx, failedRec.ID) 190 require.Regexp(t, "failed to verify protection", verifyErr) 191 192 // Add a new record that is after the old record. 193 laterRec := ptsRec 194 laterRec.ID = uuid.MakeV4() 195 laterRec.Timestamp = afterWrites 196 laterRec.Timestamp.Logical = 0 197 require.NoError(t, ptsWithDB.Protect(ctx, nil /* txn */, &laterRec)) 198 require.NoError(t, ptv.Verify(ctx, laterRec.ID)) 199 200 // Release the record that had succeeded and ensure that GC eventually 201 // happens up to the protected timestamp of the new record. 202 require.NoError(t, ptsWithDB.Release(ctx, nil, ptsRec.ID)) 203 testutils.SucceedsSoon(t, func() error { 204 trace, _, err = s.ManuallyEnqueue(ctx, "gc", repl, false) 205 require.NoError(t, err) 206 if !processedRegexp.MatchString(trace.String()) { 207 return errors.Errorf("%q does not match %q", trace.String(), processedRegexp) 208 } 209 thresh := thresholdFromTrace(trace) 210 require.Truef(t, ptsRec.Timestamp.Less(thresh), "%v >= %v", 211 ptsRec.Timestamp, thresh) 212 require.Truef(t, thresh.Less(laterRec.Timestamp), "%v >= %v", 213 thresh, laterRec.Timestamp) 214 return nil 215 }) 216 217 // Release the failed record. 218 require.NoError(t, ptsWithDB.Release(ctx, nil, failedRec.ID)) 219 require.NoError(t, ptsWithDB.Release(ctx, nil, laterRec.ID)) 220 state, err := ptsWithDB.GetState(ctx, nil) 221 require.NoError(t, err) 222 require.Len(t, state.Records, 0) 223 require.Equal(t, int(state.NumRecords), len(state.Records)) 224 }