github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/tscache/cache.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 tscache provides a timestamp cache structure that records the maximum
    12  // timestamp that key ranges were read from and written to.
    13  package tscache
    14  
    15  import (
    16  	"fmt"
    17  	"time"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    20  	"github.com/cockroachdb/cockroach/pkg/util/envutil"
    21  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    22  	"github.com/cockroachdb/cockroach/pkg/util/uuid"
    23  )
    24  
    25  // MinRetentionWindow specifies the minimum duration to hold entries in the
    26  // cache before allowing eviction. After this window expires, transactions
    27  // writing to this node with timestamps lagging by more than MinRetentionWindow
    28  // will necessarily have to advance their commit timestamp.
    29  const MinRetentionWindow = 10 * time.Second
    30  
    31  // Cache is a bounded in-memory cache that records the maximum timestamp that
    32  // key ranges were read from and written to. The structure serves to protect
    33  // against violations of Snapshot Isolation, which requires that the outcome of
    34  // reads must be preserved even in the presence of read-write conflicts (i.e. a
    35  // write to a key at a lower timestamp than a previous read must not succeed).
    36  // Cache corresponds to the "status oracle" discussed in Yabandeh's A Critique
    37  // of Snapshot Isolation.
    38  //
    39  // The cache is updated after the completion of each read operation with the
    40  // range of all keys that the request was predicated upon. It is then consulted
    41  // for each write operation, allowing them to detect read-write violations that
    42  // would allow them to write "under" a read that has already been performed.
    43  //
    44  // The cache is size-limited, so to prevent read-write conflicts for arbitrarily
    45  // old requests, it pessimistically maintains a “low water mark”. This value
    46  // always ratchets with monotonic increases and is equivalent to the earliest
    47  // timestamp of any key range that is present in the cache. If a write operation
    48  // writes to a key not present in the cache, the “low water mark” is consulted
    49  // instead to determine read-write conflicts. The low water mark is initialized
    50  // to the current system time plus the maximum clock offset.
    51  //
    52  // All Cache implementations are safe for concurrent use by multiple goroutines.
    53  type Cache interface {
    54  	// Add adds the specified timestamp to the cache covering the range of keys
    55  	// from start to end. If end is nil, the range covers the start key only.
    56  	// txnID is nil for no transaction.
    57  	Add(start, end roachpb.Key, ts hlc.Timestamp, txnID uuid.UUID)
    58  	// SetLowWater sets the low water mark of the cache for the specified span
    59  	// to the provided timestamp.
    60  	SetLowWater(start, end roachpb.Key, ts hlc.Timestamp)
    61  
    62  	// GetMax returns the maximum timestamp which overlaps the interval spanning
    63  	// from start to end. If that maximum timestamp belongs to a single
    64  	// transaction, that transaction's ID is returned. Otherwise, if that maximum
    65  	// is shared between multiple transactions, no transaction ID is returned.
    66  	// Finally, if no part of the specified range is overlapped by timestamp
    67  	// intervals from any transactions in the cache, the low water timestamp is
    68  	// returned for the read timestamps.
    69  	GetMax(start, end roachpb.Key) (hlc.Timestamp, uuid.UUID)
    70  
    71  	// Metrics returns the Cache's metrics struct.
    72  	Metrics() Metrics
    73  
    74  	// The following methods are used for testing within this package:
    75  	//
    76  	// clear clears the cache and resets the low-water mark.
    77  	clear(lowWater hlc.Timestamp)
    78  	// getLowWater return the low water mark.
    79  	getLowWater() hlc.Timestamp
    80  }
    81  
    82  // New returns a new timestamp cache with the supplied hybrid-logical clock.
    83  func New(clock *hlc.Clock) Cache {
    84  	if envutil.EnvOrDefaultBool("COCKROACH_USE_TREE_TSCACHE", false) {
    85  		return newTreeImpl(clock)
    86  	}
    87  	return newSklImpl(clock)
    88  }
    89  
    90  // cacheValue combines a timestamp with an optional txnID. It is shared between
    91  // multiple Cache implementations.
    92  type cacheValue struct {
    93  	ts    hlc.Timestamp
    94  	txnID uuid.UUID
    95  }
    96  
    97  // noTxnID is used when a cacheValue has no corresponding TxnID.
    98  var noTxnID uuid.UUID
    99  
   100  func (v cacheValue) String() string {
   101  	var txnIDStr string
   102  	switch v.txnID {
   103  	case noTxnID:
   104  		txnIDStr = "none"
   105  	default:
   106  		txnIDStr = v.txnID.String()
   107  	}
   108  	return fmt.Sprintf("{ts: %s, txnID: %s}", v.ts, txnIDStr)
   109  }
   110  
   111  // ratchetValue returns the cacheValue that results from ratcheting the provided
   112  // old and new cacheValues. It also returns flags reflecting whether the value
   113  // was updated.
   114  //
   115  // This ratcheting policy is shared across all Cache implementations, even if
   116  // they do not use this function directly.
   117  func ratchetValue(old, new cacheValue) (cacheValue, bool) {
   118  	if old.ts.Less(new.ts) {
   119  		// Ratchet to new value.
   120  		return new, true
   121  	} else if new.ts.Less(old.ts) {
   122  		// Nothing to update.
   123  		return old, false
   124  	} else if new.txnID != old.txnID {
   125  		// old.ts == new.ts but the values have different txnIDs. Remove the
   126  		// transaction ID from the value so that it is no longer owned by any
   127  		// transaction.
   128  		new.txnID = noTxnID
   129  		return new, old.txnID != noTxnID
   130  	}
   131  	// old == new.
   132  	return old, false
   133  }