func checkRangeDescriptorKey(key engine.MVCCKey) error { _, suffix, _, err := keys.DecodeRangeKey(key.Key) if err != nil { return err } if !bytes.Equal(suffix, keys.LocalRangeDescriptorSuffix) { return fmt.Errorf("wrong suffix: %s", suffix) } return nil }
func runDebugGCCmd(cmd *cobra.Command, args []string) error { stopper := stop.NewStopper() defer stopper.Stop() if len(args) != 1 { return errors.New("one argument required: dir") } var rangeID roachpb.RangeID if len(args) == 2 { var err error if rangeID, err = parseRangeID(args[1]); err != nil { return err } } db, err := openStore(cmd, args[0], stopper) if err != nil { return err } start := keys.RangeDescriptorKey(roachpb.RKeyMin) end := keys.RangeDescriptorKey(roachpb.RKeyMax) var descs []roachpb.RangeDescriptor if _, err := engine.MVCCIterate(context.Background(), db, start, end, hlc.MaxTimestamp, false /* !consistent */, nil, /* txn */ false /* !reverse */, func(kv roachpb.KeyValue) (bool, error) { var desc roachpb.RangeDescriptor _, suffix, _, err := keys.DecodeRangeKey(kv.Key) if err != nil { return false, err } if !bytes.Equal(suffix, keys.LocalRangeDescriptorSuffix) { return false, nil } if err := kv.Value.GetProto(&desc); err != nil { return false, err } if desc.RangeID == rangeID || rangeID == 0 { descs = append(descs, desc) } return desc.RangeID == rangeID, nil }); err != nil { return err } if len(descs) == 0 { return fmt.Errorf("no range matching the criteria found") } for _, desc := range descs { snap := db.NewSnapshot() defer snap.Close() _, info, err := storage.RunGC(context.Background(), &desc, snap, hlc.Timestamp{WallTime: timeutil.Now().UnixNano()}, config.GCPolicy{TTLSeconds: 24 * 60 * 60 /* 1 day */}, func(_ hlc.Timestamp, _ *roachpb.Transaction, _ roachpb.PushTxnType) { }, func(_ []roachpb.Intent, _, _ bool) error { return nil }) if err != nil { return err } fmt.Printf("RangeID: %d [%s, %s):\n", desc.RangeID, desc.StartKey, desc.EndKey) _, _ = pretty.Println(info) } return nil }
// processLocalKeyRange scans the local range key entries, consisting of // transaction records, queue last processed timestamps, and range descriptors. // // - Transaction entries: updates txnMap with those transactions which // are old and either PENDING or with intents registered. In the // first case we want to push the transaction so that it is aborted, // and in the second case we may have to resolve the intents // success- fully before GCing the entry. The transaction records // which can be gc'ed are returned separately and are not added to // txnMap nor intentSpanMap. // // - Queue last processed times: cleanup any entries which don't match // this range's start key. This can happen on range merges. func processLocalKeyRange( ctx context.Context, snap engine.Reader, desc *roachpb.RangeDescriptor, txnMap map[uuid.UUID]*roachpb.Transaction, cutoff hlc.Timestamp, infoMu *lockableGCInfo, resolveIntents resolveFunc, ) ([]roachpb.GCRequest_GCKey, error) { infoMu.Lock() defer infoMu.Unlock() var gcKeys []roachpb.GCRequest_GCKey handleOneTransaction := func(kv roachpb.KeyValue) error { var txn roachpb.Transaction if err := kv.Value.GetProto(&txn); err != nil { return err } infoMu.TransactionSpanTotal++ if !txn.LastActive().Less(cutoff) { return nil } txnID := *txn.ID // The transaction record should be considered for removal. switch txn.Status { case roachpb.PENDING: // Marked as running, so we need to push it to abort it but won't // try to GC it in this cycle (for convenience). // TODO(tschottdorf): refactor so that we can GC PENDING entries // in the same cycle, but keeping the calls to pushTxn in a central // location (keeping it easy to batch them up in the future). infoMu.TransactionSpanGCPending++ txnMap[txnID] = &txn return nil case roachpb.ABORTED: // If we remove this transaction, it effectively still counts as // ABORTED (by design). So this can be GC'ed even if we can't // resolve the intents. // Note: Most aborted transaction weren't aborted by their client, // but instead by the coordinator - those will not have any intents // persisted, though they still might exist in the system. infoMu.TransactionSpanGCAborted++ func() { infoMu.Unlock() // intentional defer infoMu.Lock() if err := resolveIntents(roachpb.AsIntents(txn.Intents, &txn), true /* wait */, false /* !poison */); err != nil { log.Warningf(ctx, "failed to resolve intents of aborted txn on gc: %s", err) } }() case roachpb.COMMITTED: // It's committed, so it doesn't need a push but we can only // GC it after its intents are resolved. if err := func() error { infoMu.Unlock() // intentional defer infoMu.Lock() return resolveIntents(roachpb.AsIntents(txn.Intents, &txn), true /* wait */, false /* !poison */) }(); err != nil { log.Warningf(ctx, "unable to resolve intents of committed txn on gc: %s", err) // Returning the error here would abort the whole GC run, and // we don't want that. Instead, we simply don't GC this entry. return nil } infoMu.TransactionSpanGCCommitted++ default: panic(fmt.Sprintf("invalid transaction state: %s", txn)) } gcKeys = append(gcKeys, roachpb.GCRequest_GCKey{Key: kv.Key}) // zero timestamp return nil } handleOneQueueLastProcessed := func(kv roachpb.KeyValue, rangeKey roachpb.RKey) error { if !rangeKey.Equal(desc.StartKey) { // Garbage collect the last processed timestamp if it doesn't match start key. gcKeys = append(gcKeys, roachpb.GCRequest_GCKey{Key: kv.Key}) // zero timestamp } return nil } handleOne := func(kv roachpb.KeyValue) error { rangeKey, suffix, _, err := keys.DecodeRangeKey(kv.Key) if err != nil { return err } if suffix.Equal(keys.LocalTransactionSuffix.AsRawKey()) { if err := handleOneTransaction(kv); err != nil { return err } } else if suffix.Equal(keys.LocalQueueLastProcessedSuffix.AsRawKey()) { if err := handleOneQueueLastProcessed(kv, roachpb.RKey(rangeKey)); err != nil { return err } } return nil } startKey := keys.MakeRangeKeyPrefix(desc.StartKey) endKey := keys.MakeRangeKeyPrefix(desc.EndKey) _, err := engine.MVCCIterate(ctx, snap, startKey, endKey, hlc.ZeroTimestamp, true /* consistent */, nil, /* txn */ false /* !reverse */, func(kv roachpb.KeyValue) (bool, error) { return false, handleOne(kv) }) return gcKeys, err }