github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/batcheval/cmd_truncate_log.go (about)

     1  // Copyright 2014 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/keys"
    17  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/batcheval/result"
    18  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvserverpb"
    19  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/spanset"
    20  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    21  	"github.com/cockroachdb/cockroach/pkg/storage"
    22  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    23  	"github.com/cockroachdb/cockroach/pkg/util/log"
    24  	"github.com/cockroachdb/errors"
    25  )
    26  
    27  func init() {
    28  	RegisterReadWriteCommand(roachpb.TruncateLog, declareKeysTruncateLog, TruncateLog)
    29  }
    30  
    31  func declareKeysTruncateLog(
    32  	_ *roachpb.RangeDescriptor,
    33  	header roachpb.Header,
    34  	req roachpb.Request,
    35  	latchSpans, _ *spanset.SpanSet,
    36  ) {
    37  	latchSpans.AddNonMVCC(spanset.SpanReadWrite, roachpb.Span{Key: keys.RaftTruncatedStateLegacyKey(header.RangeID)})
    38  	prefix := keys.RaftLogPrefix(header.RangeID)
    39  	latchSpans.AddNonMVCC(spanset.SpanReadWrite, roachpb.Span{Key: prefix, EndKey: prefix.PrefixEnd()})
    40  }
    41  
    42  // TruncateLog discards a prefix of the raft log. Truncating part of a log that
    43  // has already been truncated has no effect. If this range is not the one
    44  // specified within the request body, the request will also be ignored.
    45  func TruncateLog(
    46  	ctx context.Context, readWriter storage.ReadWriter, cArgs CommandArgs, resp roachpb.Response,
    47  ) (result.Result, error) {
    48  	args := cArgs.Args.(*roachpb.TruncateLogRequest)
    49  
    50  	// After a merge, it's possible that this request was sent to the wrong
    51  	// range based on the start key. This will cancel the request if this is not
    52  	// the range specified in the request body.
    53  	rangeID := cArgs.EvalCtx.GetRangeID()
    54  	if rangeID != args.RangeID {
    55  		log.Infof(ctx, "attempting to truncate raft logs for another range: r%d. Normally this is due to a merge and can be ignored.",
    56  			args.RangeID)
    57  		return result.Result{}, nil
    58  	}
    59  
    60  	var legacyTruncatedState roachpb.RaftTruncatedState
    61  	legacyKeyFound, err := storage.MVCCGetProto(
    62  		ctx, readWriter, keys.RaftTruncatedStateLegacyKey(cArgs.EvalCtx.GetRangeID()),
    63  		hlc.Timestamp{}, &legacyTruncatedState, storage.MVCCGetOptions{},
    64  	)
    65  	if err != nil {
    66  		return result.Result{}, err
    67  	}
    68  
    69  	firstIndex, err := cArgs.EvalCtx.GetFirstIndex()
    70  	if err != nil {
    71  		return result.Result{}, errors.Wrap(err, "getting first index")
    72  	}
    73  	// Have we already truncated this log? If so, just return without an error.
    74  	// Note that there may in principle be followers whose Raft log is longer
    75  	// than this node's, but to issue a truncation we also need the *term* for
    76  	// the new truncated state, which we can't obtain if we don't have the log
    77  	// entry ourselves.
    78  	//
    79  	// TODO(tbg): think about synthesizing a valid term. Can we use the next
    80  	// existing entry's term?
    81  	if firstIndex >= args.Index {
    82  		if log.V(3) {
    83  			log.Infof(ctx, "attempting to truncate previously truncated raft log. FirstIndex:%d, TruncateFrom:%d",
    84  				firstIndex, args.Index)
    85  		}
    86  		return result.Result{}, nil
    87  	}
    88  
    89  	// args.Index is the first index to keep.
    90  	term, err := cArgs.EvalCtx.GetTerm(args.Index - 1)
    91  	if err != nil {
    92  		return result.Result{}, errors.Wrap(err, "getting term")
    93  	}
    94  
    95  	// Compute the number of bytes freed by this truncation. Note that this will
    96  	// only make sense for the leaseholder as we base this off its own first
    97  	// index (other replicas may have other first indexes assuming we're not
    98  	// still using the legacy truncated state key). In principle, this could be
    99  	// off either way, though in practice we don't expect followers to have
   100  	// a first index smaller than the leaseholder's (see #34287), and most of
   101  	// the time everyone's first index should be the same.
   102  	start := keys.RaftLogKey(rangeID, firstIndex)
   103  	end := keys.RaftLogKey(rangeID, args.Index)
   104  
   105  	// Compute the stats delta that were to occur should the log entries be
   106  	// purged. We do this as a side effect of seeing a new TruncatedState,
   107  	// downstream of Raft.
   108  	//
   109  	// Note that any sideloaded payloads that may be removed by this truncation
   110  	// don't matter; they're not tracked in the raft log delta.
   111  	//
   112  	// TODO(tbg): it's difficult to prove that this computation doesn't have
   113  	// bugs that let it diverge. It might be easier to compute the stats
   114  	// from scratch, stopping when 4mb (defaultRaftLogTruncationThreshold)
   115  	// is reached as at that point we'll truncate aggressively anyway.
   116  	iter := readWriter.NewIterator(storage.IterOptions{UpperBound: end})
   117  	defer iter.Close()
   118  	// We can pass zero as nowNanos because we're only interested in SysBytes.
   119  	ms, err := iter.ComputeStats(start, end, 0 /* nowNanos */)
   120  	if err != nil {
   121  		return result.Result{}, errors.Wrap(err, "while computing stats of Raft log freed by truncation")
   122  	}
   123  	ms.SysBytes = -ms.SysBytes // simulate the deletion
   124  
   125  	tState := &roachpb.RaftTruncatedState{
   126  		Index: args.Index - 1,
   127  		Term:  term,
   128  	}
   129  
   130  	var pd result.Result
   131  	pd.Replicated.State = &kvserverpb.ReplicaState{
   132  		TruncatedState: tState,
   133  	}
   134  
   135  	pd.Replicated.RaftLogDelta = ms.SysBytes
   136  
   137  	if legacyKeyFound {
   138  		// Time to migrate by deleting the legacy key. The downstream-of-Raft
   139  		// code will atomically rewrite the truncated state (supplied via the
   140  		// side effect) into the new unreplicated key.
   141  		if err := storage.MVCCDelete(
   142  			ctx, readWriter, cArgs.Stats, keys.RaftTruncatedStateLegacyKey(cArgs.EvalCtx.GetRangeID()),
   143  			hlc.Timestamp{}, nil, /* txn */
   144  		); err != nil {
   145  			return result.Result{}, err
   146  		}
   147  	}
   148  
   149  	return pd, nil
   150  }