github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/batcheval/cmd_recompute_stats.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 batcheval
    12  
    13  import (
    14  	"context"
    15  
    16  	"github.com/cockroachdb/cockroach/pkg/clusterversion"
    17  	"github.com/cockroachdb/cockroach/pkg/keys"
    18  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/batcheval/result"
    19  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/rditer"
    20  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/spanset"
    21  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    22  	"github.com/cockroachdb/cockroach/pkg/storage"
    23  	"github.com/cockroachdb/cockroach/pkg/storage/enginepb"
    24  	"github.com/cockroachdb/cockroach/pkg/util/uuid"
    25  	"github.com/cockroachdb/errors"
    26  )
    27  
    28  func init() {
    29  	RegisterReadOnlyCommand(roachpb.RecomputeStats, declareKeysRecomputeStats, RecomputeStats)
    30  }
    31  
    32  func declareKeysRecomputeStats(
    33  	desc *roachpb.RangeDescriptor,
    34  	header roachpb.Header,
    35  	req roachpb.Request,
    36  	latchSpans, _ *spanset.SpanSet,
    37  ) {
    38  	// We don't declare any user key in the range. This is OK since all we're doing is computing a
    39  	// stats delta, and applying this delta commutes with other operations on the same key space.
    40  	//
    41  	// But we want two additional properties:
    42  	// 1) prevent interleaving with splits, and
    43  	// 2) prevent interleaving between different incarnations of `RecomputeStats`.
    44  	//
    45  	// This is achieved by declaring
    46  	// 1) a read on the range descriptor key (thus blocking splits) and
    47  	// 2) a write on a transaction anchored at the range descriptor key (thus blocking any other
    48  	// incarnation of RecomputeStats).
    49  	//
    50  	// Note that we're also accessing the range stats key, but we don't declare it for the same
    51  	// reasons as above.
    52  	rdKey := keys.RangeDescriptorKey(desc.StartKey)
    53  	latchSpans.AddNonMVCC(spanset.SpanReadOnly, roachpb.Span{Key: rdKey})
    54  	latchSpans.AddNonMVCC(spanset.SpanReadWrite, roachpb.Span{Key: keys.TransactionKey(rdKey, uuid.Nil)})
    55  }
    56  
    57  // RecomputeStats recomputes the MVCCStats stored for this range and adjust them accordingly,
    58  // returning the MVCCStats delta obtained in the process.
    59  func RecomputeStats(
    60  	ctx context.Context, _ storage.Reader, cArgs CommandArgs, resp roachpb.Response,
    61  ) (result.Result, error) {
    62  	desc := cArgs.EvalCtx.Desc()
    63  	args := cArgs.Args.(*roachpb.RecomputeStatsRequest)
    64  	if !desc.StartKey.AsRawKey().Equal(args.Key) {
    65  		return result.Result{}, errors.New("descriptor mismatch; range likely merged")
    66  	}
    67  	dryRun := args.DryRun
    68  
    69  	args = nil // avoid accidental use below
    70  
    71  	// Open a snapshot from which we will read everything (including the
    72  	// MVCCStats). This is necessary because a batch does not provide us
    73  	// with a consistent view of the data -- reading from the batch, we
    74  	// could see skew between the stats recomputation and the MVCCStats
    75  	// we read from the range state if concurrent writes are inflight[1].
    76  	//
    77  	// Note that in doing so, we also circumvent the assertions (present in both
    78  	// the EvalContext and the batch in some builds) which check that all reads
    79  	// were previously declared. See the comment in `declareKeysRecomputeStats`
    80  	// for details on this.
    81  	//
    82  	// [1]: see engine.TestBatchReadLaterWrite.
    83  	snap := cArgs.EvalCtx.Engine().NewSnapshot()
    84  	defer snap.Close()
    85  
    86  	actualMS, err := rditer.ComputeStatsForRange(desc, snap, cArgs.Header.Timestamp.WallTime)
    87  	if err != nil {
    88  		return result.Result{}, err
    89  	}
    90  
    91  	currentStats, err := MakeStateLoader(cArgs.EvalCtx).LoadMVCCStats(ctx, snap)
    92  	if err != nil {
    93  		return result.Result{}, err
    94  	}
    95  
    96  	delta := actualMS
    97  	delta.Subtract(currentStats)
    98  
    99  	if !dryRun {
   100  		// TODO(tschottdorf): do we not want to run at all if we have estimates in
   101  		// this range? I think we want to as this would give us much more realistic
   102  		// stats for timeseries ranges (which go cold and the approximate stats are
   103  		// wildly overcounting) and this is paced by the consistency checker, but it
   104  		// means some extra engine churn.
   105  		if !cArgs.EvalCtx.ClusterSettings().Version.IsActive(ctx, clusterversion.VersionContainsEstimatesCounter) {
   106  			// We are running with the older version of MVCCStats.ContainsEstimates
   107  			// which was a boolean, so we should keep it in {0,1} and not reset it
   108  			// to avoid racing with another command that sets it to true.
   109  			delta.ContainsEstimates = currentStats.ContainsEstimates
   110  		}
   111  		cArgs.Stats.Add(delta)
   112  	}
   113  
   114  	resp.(*roachpb.RecomputeStatsResponse).AddedDelta = enginepb.MVCCStatsDelta(delta)
   115  	return result.Result{}, nil
   116  }