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  }