github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cli/debug_check_store.go (about)

     1  // Copyright 2019 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 cli
    12  
    13  import (
    14  	"bytes"
    15  	"context"
    16  	"fmt"
    17  	"runtime"
    18  	"strings"
    19  
    20  	"github.com/cockroachdb/cockroach/pkg/keys"
    21  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver"
    22  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/rditer"
    23  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/stateloader"
    24  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    25  	"github.com/cockroachdb/cockroach/pkg/storage"
    26  	"github.com/cockroachdb/cockroach/pkg/storage/enginepb"
    27  	"github.com/cockroachdb/cockroach/pkg/util/encoding"
    28  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    29  	"github.com/cockroachdb/cockroach/pkg/util/stop"
    30  	"github.com/cockroachdb/errors"
    31  	"github.com/kr/pretty"
    32  	"github.com/spf13/cobra"
    33  	"go.etcd.io/etcd/raft/raftpb"
    34  	"golang.org/x/sync/errgroup"
    35  )
    36  
    37  var debugCheckStoreCmd = &cobra.Command{
    38  	Use:   "check-store <directory>",
    39  	Short: "consistency check for a single store",
    40  	Long: `
    41  Perform local consistency checks of a single store.
    42  
    43  Capable of detecting the following errors:
    44  * Raft logs that are inconsistent with their metadata
    45  * MVCC stats that are inconsistent with the data within the range
    46  `,
    47  	Args: cobra.ExactArgs(1),
    48  	RunE: MaybeDecorateGRPCError(runDebugCheckStoreCmd),
    49  }
    50  
    51  var errCheckFoundProblem = errors.New("check-store found problems")
    52  
    53  func runDebugCheckStoreCmd(cmd *cobra.Command, args []string) error {
    54  	ctx := context.Background()
    55  	dir := args[0]
    56  	foundProblem := false
    57  	// At time of writing, this takes around ~110s for 71GB (1k warehouse TPCC
    58  	// fully compacted) on local SSD. This is quite fast, well north of 600MB/s.
    59  	err := checkStoreRangeStats(ctx, dir, func(args ...interface{}) {
    60  		fmt.Println(args...)
    61  	})
    62  	foundProblem = foundProblem || err != nil
    63  	if err != nil && !errors.Is(err, errCheckFoundProblem) {
    64  		_, _ = fmt.Println(err)
    65  	}
    66  	// This is not optimized at all, but for the same data set as above, it
    67  	// returns instantly, so we won't need to optimize it for quite some time.
    68  	err = checkStoreRaftState(ctx, dir, func(format string, args ...interface{}) {
    69  		_, _ = fmt.Printf(format, args...)
    70  	})
    71  	foundProblem = foundProblem || err != nil
    72  	if err != nil && !errors.Is(err, errCheckFoundProblem) {
    73  		fmt.Println(err)
    74  	}
    75  	if foundProblem {
    76  		return errCheckFoundProblem
    77  	}
    78  	return nil
    79  }
    80  
    81  type replicaCheckInfo struct {
    82  	truncatedIndex uint64
    83  	appliedIndex   uint64
    84  	firstIndex     uint64
    85  	lastIndex      uint64
    86  	committedIndex uint64
    87  }
    88  
    89  type checkInput struct {
    90  	eng  storage.Engine
    91  	desc *roachpb.RangeDescriptor
    92  	sl   stateloader.StateLoader
    93  }
    94  
    95  type checkResult struct {
    96  	desc           *roachpb.RangeDescriptor
    97  	err            error
    98  	claimMS, actMS enginepb.MVCCStats
    99  }
   100  
   101  func (cr *checkResult) Error() error {
   102  	var err error
   103  	if cr.err != nil {
   104  		err = cr.err
   105  	}
   106  	if !cr.actMS.Equal(enginepb.MVCCStats{}) && !cr.actMS.Equal(cr.claimMS) && cr.claimMS.ContainsEstimates <= 0 {
   107  		thisErr := errors.Newf(
   108  			"stats inconsistency:\n- stored:\n%+v\n- recomputed:\n%+v\n- diff:\n%s",
   109  			cr.claimMS, cr.actMS, strings.Join(pretty.Diff(cr.claimMS, cr.actMS), ","),
   110  		)
   111  		err = errors.CombineErrors(err, thisErr)
   112  	}
   113  	if err != nil {
   114  		if cr.desc != nil {
   115  			err = errors.Wrapf(err, "%s", cr.desc)
   116  		}
   117  	}
   118  	return err
   119  }
   120  
   121  func worker(ctx context.Context, in checkInput) checkResult {
   122  	desc, eng := in.desc, in.eng
   123  
   124  	res := checkResult{desc: desc}
   125  	claimedMS, err := in.sl.LoadMVCCStats(ctx, eng)
   126  	if err != nil {
   127  		res.err = err
   128  		return res
   129  	}
   130  	ms, err := rditer.ComputeStatsForRange(desc, eng, claimedMS.LastUpdateNanos)
   131  	if err != nil {
   132  		res.err = err
   133  		return res
   134  	}
   135  	res.claimMS = claimedMS
   136  	res.actMS = ms
   137  	return res
   138  }
   139  
   140  func checkStoreRangeStats(
   141  	ctx context.Context,
   142  	dir string, // the store directory
   143  	println func(...interface{}), // fmt.Println outside of tests
   144  ) error {
   145  	stopper := stop.NewStopper()
   146  	defer stopper.Stop(ctx)
   147  
   148  	eng, err := OpenExistingStore(dir, stopper, true /* readOnly */)
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	inCh := make(chan checkInput)
   154  	outCh := make(chan checkResult, 1000)
   155  
   156  	n := runtime.NumCPU()
   157  	var g errgroup.Group
   158  	for i := 0; i < n; i++ {
   159  		g.Go(func() error {
   160  			for in := range inCh {
   161  				outCh <- worker(ctx, in)
   162  			}
   163  			return nil
   164  		})
   165  	}
   166  
   167  	go func() {
   168  		if err := kvserver.IterateRangeDescriptors(ctx, eng,
   169  			func(desc roachpb.RangeDescriptor) (bool, error) {
   170  				inCh <- checkInput{eng: eng, desc: &desc, sl: stateloader.Make(desc.RangeID)}
   171  				return false, nil
   172  			}); err != nil {
   173  			outCh <- checkResult{err: err}
   174  		}
   175  		close(inCh) // we were the only writer
   176  		if err := g.Wait(); err != nil {
   177  			outCh <- checkResult{err: err}
   178  		}
   179  		close(outCh) // all writers done due to Wait()
   180  	}()
   181  
   182  	foundProblem := false
   183  	var total enginepb.MVCCStats
   184  	var cR, cE int
   185  	for res := range outCh {
   186  		cR++
   187  		if err := res.Error(); err != nil {
   188  			foundProblem = true
   189  			errS := err.Error()
   190  			println(errS)
   191  		} else {
   192  			if res.claimMS.ContainsEstimates > 0 {
   193  				cE++
   194  			}
   195  			total.Add(res.actMS)
   196  		}
   197  	}
   198  
   199  	println(fmt.Sprintf("scanned %d ranges (%d with estimates), total stats %s", cR, cE, &total))
   200  
   201  	if foundProblem {
   202  		// The details were already emitted.
   203  		return errCheckFoundProblem
   204  	}
   205  	return nil
   206  }
   207  
   208  func checkStoreRaftState(
   209  	ctx context.Context,
   210  	dir string, // the store directory
   211  	printf func(string, ...interface{}), // fmt.Printf outside of tests
   212  ) error {
   213  	foundProblem := false
   214  	goldenPrintf := printf
   215  	printf = func(format string, args ...interface{}) {
   216  		foundProblem = true
   217  		goldenPrintf(format, args...)
   218  	}
   219  	stopper := stop.NewStopper()
   220  	defer stopper.Stop(context.Background())
   221  
   222  	db, err := OpenExistingStore(dir, stopper, true /* readOnly */)
   223  	if err != nil {
   224  		return err
   225  	}
   226  
   227  	// Iterate over the entire range-id-local space.
   228  	start := roachpb.Key(keys.LocalRangeIDPrefix)
   229  	end := start.PrefixEnd()
   230  
   231  	replicaInfo := map[roachpb.RangeID]*replicaCheckInfo{}
   232  	getReplicaInfo := func(rangeID roachpb.RangeID) *replicaCheckInfo {
   233  		if info, ok := replicaInfo[rangeID]; ok {
   234  			return info
   235  		}
   236  		replicaInfo[rangeID] = &replicaCheckInfo{}
   237  		return replicaInfo[rangeID]
   238  	}
   239  
   240  	if _, err := storage.MVCCIterate(ctx, db, start, end, hlc.MaxTimestamp,
   241  		storage.MVCCScanOptions{Inconsistent: true}, func(kv roachpb.KeyValue) (bool, error) {
   242  			rangeID, _, suffix, detail, err := keys.DecodeRangeIDKey(kv.Key)
   243  			if err != nil {
   244  				return false, err
   245  			}
   246  
   247  			switch {
   248  			case bytes.Equal(suffix, keys.LocalRaftHardStateSuffix):
   249  				var hs raftpb.HardState
   250  				if err := kv.Value.GetProto(&hs); err != nil {
   251  					return false, err
   252  				}
   253  				getReplicaInfo(rangeID).committedIndex = hs.Commit
   254  			case bytes.Equal(suffix, keys.LocalRaftTruncatedStateLegacySuffix):
   255  				var trunc roachpb.RaftTruncatedState
   256  				if err := kv.Value.GetProto(&trunc); err != nil {
   257  					return false, err
   258  				}
   259  				getReplicaInfo(rangeID).truncatedIndex = trunc.Index
   260  			case bytes.Equal(suffix, keys.LocalRangeAppliedStateSuffix):
   261  				var state enginepb.RangeAppliedState
   262  				if err := kv.Value.GetProto(&state); err != nil {
   263  					return false, err
   264  				}
   265  				getReplicaInfo(rangeID).appliedIndex = state.RaftAppliedIndex
   266  			case bytes.Equal(suffix, keys.LocalRaftAppliedIndexLegacySuffix):
   267  				idx, err := kv.Value.GetInt()
   268  				if err != nil {
   269  					return false, err
   270  				}
   271  				getReplicaInfo(rangeID).appliedIndex = uint64(idx)
   272  			case bytes.Equal(suffix, keys.LocalRaftLogSuffix):
   273  				_, index, err := encoding.DecodeUint64Ascending(detail)
   274  				if err != nil {
   275  					return false, err
   276  				}
   277  				ri := getReplicaInfo(rangeID)
   278  				if ri.firstIndex == 0 {
   279  					ri.firstIndex = index
   280  					ri.lastIndex = index
   281  				} else {
   282  					if index != ri.lastIndex+1 {
   283  						printf("range %s: log index anomaly: %v followed by %v\n",
   284  							rangeID, ri.lastIndex, index)
   285  					}
   286  					ri.lastIndex = index
   287  				}
   288  			}
   289  
   290  			return false, nil
   291  		}); err != nil {
   292  		return err
   293  	}
   294  
   295  	for rangeID, info := range replicaInfo {
   296  		if info.truncatedIndex != 0 && info.truncatedIndex != info.firstIndex-1 {
   297  			printf("range %s: truncated index %v should equal first index %v - 1\n",
   298  				rangeID, info.truncatedIndex, info.firstIndex)
   299  		}
   300  		if info.firstIndex > info.lastIndex {
   301  			printf("range %s: [first index, last index] is [%d, %d]\n",
   302  				rangeID, info.firstIndex, info.lastIndex)
   303  		}
   304  		if info.appliedIndex < info.firstIndex || info.appliedIndex > info.lastIndex {
   305  			printf("range %s: applied index %v should be between first index %v and last index %v\n",
   306  				rangeID, info.appliedIndex, info.firstIndex, info.lastIndex)
   307  		}
   308  		if info.appliedIndex > info.committedIndex {
   309  			printf("range %s: committed index %d must not trail applied index %d\n",
   310  				rangeID, info.committedIndex, info.appliedIndex)
   311  		}
   312  		if info.committedIndex > info.lastIndex {
   313  			printf("range %s: committed index %d ahead of last index  %d\n",
   314  				rangeID, info.committedIndex, info.lastIndex)
   315  		}
   316  	}
   317  	if foundProblem {
   318  		return errCheckFoundProblem
   319  	}
   320  
   321  	return nil
   322  }