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  }