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 }