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 }