github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/batcheval/cmd_query_intent.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  
    16  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/batcheval/result"
    17  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/spanset"
    18  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    19  	"github.com/cockroachdb/cockroach/pkg/storage"
    20  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    21  	"github.com/cockroachdb/cockroach/pkg/util/log"
    22  )
    23  
    24  func init() {
    25  	RegisterReadOnlyCommand(roachpb.QueryIntent, declareKeysQueryIntent, QueryIntent)
    26  }
    27  
    28  func declareKeysQueryIntent(
    29  	_ *roachpb.RangeDescriptor,
    30  	header roachpb.Header,
    31  	req roachpb.Request,
    32  	latchSpans, _ *spanset.SpanSet,
    33  ) {
    34  	// QueryIntent requests read the specified keys at the maximum timestamp in
    35  	// order to read any intent present, if one exists, regardless of the
    36  	// timestamp it was written at.
    37  	latchSpans.AddNonMVCC(spanset.SpanReadOnly, req.Header().Span())
    38  }
    39  
    40  // QueryIntent checks if an intent exists for the specified transaction at the
    41  // given key. If the intent is missing, the request prevents the intent from
    42  // ever being written at the specified timestamp (but the actual prevention
    43  // happens during the timestamp cache update).
    44  //
    45  // QueryIntent returns an error if the intent is missing and its ErrorIfMissing
    46  // field is set to true. This error is typically an IntentMissingError, but the
    47  // request is special-cased to return a SERIALIZABLE retry error if a transaction
    48  // queries its own intent and finds it has been pushed.
    49  func QueryIntent(
    50  	ctx context.Context, reader storage.Reader, cArgs CommandArgs, resp roachpb.Response,
    51  ) (result.Result, error) {
    52  	args := cArgs.Args.(*roachpb.QueryIntentRequest)
    53  	h := cArgs.Header
    54  	reply := resp.(*roachpb.QueryIntentResponse)
    55  
    56  	// Read at the specified key at the maximum timestamp. This ensures that we
    57  	// see an intent if one exists, regardless of what timestamp it is written
    58  	// at.
    59  	_, intent, err := storage.MVCCGet(ctx, reader, args.Key, hlc.MaxTimestamp, storage.MVCCGetOptions{
    60  		// Perform an inconsistent read so that intents are returned instead of
    61  		// causing WriteIntentErrors.
    62  		Inconsistent: true,
    63  		// Even if the request header contains a txn, perform the engine lookup
    64  		// without a transaction so that intents for a matching transaction are
    65  		// not returned as values (i.e. we don't want to see our own writes).
    66  		Txn: nil,
    67  	})
    68  	if err != nil {
    69  		return result.Result{}, err
    70  	}
    71  
    72  	// Determine if the request is querying an intent in its own transaction.
    73  	ownTxn := h.Txn != nil && h.Txn.ID == args.Txn.ID
    74  
    75  	var curIntentPushed bool
    76  	if intent != nil {
    77  		// See comment on QueryIntentRequest.Txn for an explanation of this
    78  		// comparison.
    79  		// TODO(nvanbenschoten): Now that we have a full intent history,
    80  		// we can look at the exact sequence! That won't serve as much more
    81  		// than an assertion that QueryIntent is being used correctly.
    82  		reply.FoundIntent = (args.Txn.ID == intent.Txn.ID) &&
    83  			(args.Txn.Epoch == intent.Txn.Epoch) &&
    84  			(args.Txn.Sequence <= intent.Txn.Sequence)
    85  
    86  		// If we found a matching intent, check whether the intent was pushed
    87  		// past its expected timestamp.
    88  		if reply.FoundIntent {
    89  			// If the request is querying an intent for its own transaction, forward
    90  			// the timestamp we compare against to the provisional commit timestamp
    91  			// in the batch header.
    92  			cmpTS := args.Txn.WriteTimestamp
    93  			if ownTxn {
    94  				cmpTS.Forward(h.Txn.WriteTimestamp)
    95  			}
    96  			if cmpTS.Less(intent.Txn.WriteTimestamp) {
    97  				// The intent matched but was pushed to a later timestamp. Consider a
    98  				// pushed intent a missing intent.
    99  				curIntentPushed = true
   100  				log.VEventf(ctx, 2, "found pushed intent")
   101  				reply.FoundIntent = false
   102  
   103  				// If the request was querying an intent in its own transaction, update
   104  				// the response transaction.
   105  				if ownTxn {
   106  					reply.Txn = h.Txn.Clone()
   107  					reply.Txn.WriteTimestamp.Forward(intent.Txn.WriteTimestamp)
   108  				}
   109  			}
   110  		}
   111  	}
   112  
   113  	if !reply.FoundIntent && args.ErrorIfMissing {
   114  		if ownTxn && curIntentPushed {
   115  			// If the transaction's own intent was pushed, go ahead and
   116  			// return a TransactionRetryError immediately with an updated
   117  			// transaction proto. This is an optimization that can help
   118  			// the txn use refresh spans more effectively.
   119  			return result.Result{}, roachpb.NewTransactionRetryError(roachpb.RETRY_SERIALIZABLE, "intent pushed")
   120  		}
   121  		return result.Result{}, roachpb.NewIntentMissingError(args.Key, intent)
   122  	}
   123  	return result.Result{}, nil
   124  }