github.com/KinWaiYuen/client-go/v2@v2.5.4/tikv/gc.go (about)

     1  // Copyright 2021 TiKV Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package tikv
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"sync"
    21  	"time"
    22  
    23  	tikverr "github.com/KinWaiYuen/client-go/v2/error"
    24  	"github.com/KinWaiYuen/client-go/v2/internal/locate"
    25  	"github.com/KinWaiYuen/client-go/v2/internal/logutil"
    26  	"github.com/KinWaiYuen/client-go/v2/internal/retry"
    27  	"github.com/KinWaiYuen/client-go/v2/kv"
    28  	"github.com/KinWaiYuen/client-go/v2/metrics"
    29  	"github.com/KinWaiYuen/client-go/v2/tikvrpc"
    30  	"github.com/KinWaiYuen/client-go/v2/txnkv/rangetask"
    31  	"github.com/KinWaiYuen/client-go/v2/txnkv/txnlock"
    32  	"github.com/pingcap/errors"
    33  	"github.com/pingcap/kvproto/pkg/kvrpcpb"
    34  	"github.com/pingcap/kvproto/pkg/metapb"
    35  	zap "go.uber.org/zap"
    36  )
    37  
    38  // GC does garbage collection (GC) of the TiKV cluster.
    39  // GC deletes MVCC records whose timestamp is lower than the given `safepoint`. We must guarantee
    40  //  that all transactions started before this timestamp had committed. We can keep an active
    41  // transaction list in application to decide which is the minimal start timestamp of them.
    42  //
    43  // For each key, the last mutation record (unless it's a deletion) before `safepoint` is retained.
    44  //
    45  // GC is performed by:
    46  // 1. resolving all locks with timestamp <= `safepoint`
    47  // 2. updating PD's known safepoint
    48  //
    49  // GC is a simplified version of [GC in TiDB](https://docs.pingcap.com/tidb/stable/garbage-collection-overview).
    50  // We skip the second step "delete ranges" which is an optimization for TiDB.
    51  func (s *KVStore) GC(ctx context.Context, safepoint uint64) (newSafePoint uint64, err error) {
    52  	err = s.resolveLocks(ctx, safepoint, 8)
    53  	if err != nil {
    54  		return
    55  	}
    56  
    57  	return s.pdClient.UpdateGCSafePoint(ctx, safepoint)
    58  }
    59  
    60  func (s *KVStore) resolveLocks(ctx context.Context, safePoint uint64, concurrency int) error {
    61  	handler := func(ctx context.Context, r kv.KeyRange) (rangetask.TaskStat, error) {
    62  		return s.resolveLocksForRange(ctx, safePoint, r.StartKey, r.EndKey)
    63  	}
    64  
    65  	runner := rangetask.NewRangeTaskRunner("resolve-locks-runner", s, concurrency, handler)
    66  	// Run resolve lock on the whole TiKV cluster. Empty keys means the range is unbounded.
    67  	err := runner.RunOnRange(ctx, []byte(""), []byte(""))
    68  	if err != nil {
    69  		return errors.Trace(err)
    70  	}
    71  	return nil
    72  }
    73  
    74  // We don't want gc to sweep out the cached info belong to other processes, like coprocessor.
    75  const gcScanLockLimit = txnlock.ResolvedCacheSize / 2
    76  
    77  func (s *KVStore) resolveLocksForRange(ctx context.Context, safePoint uint64, startKey []byte, endKey []byte) (rangetask.TaskStat, error) {
    78  	// for scan lock request, we must return all locks even if they are generated
    79  	// by the same transaction. because gc worker need to make sure all locks have been
    80  	// cleaned.
    81  
    82  	var stat rangetask.TaskStat
    83  	key := startKey
    84  	bo := NewGcResolveLockMaxBackoffer(ctx)
    85  	for {
    86  		select {
    87  		case <-ctx.Done():
    88  			return stat, errors.New("[gc worker] gc job canceled")
    89  		default:
    90  		}
    91  
    92  		locks, loc, err := s.scanLocksInRegionWithStartKey(bo, key, safePoint, gcScanLockLimit)
    93  		if err != nil {
    94  			return stat, err
    95  		}
    96  
    97  		resolvedLocation, err1 := s.batchResolveLocksInARegion(bo, locks, loc)
    98  		if err1 != nil {
    99  			return stat, errors.Trace(err1)
   100  		}
   101  		// resolve locks failed since the locks are not in one region anymore, need retry.
   102  		if resolvedLocation == nil {
   103  			continue
   104  		}
   105  		if len(locks) < gcScanLockLimit {
   106  			stat.CompletedRegions++
   107  			key = loc.EndKey
   108  			logutil.Logger(ctx).Info("[gc worker] one region finshed ",
   109  				zap.Int("regionID", int(resolvedLocation.Region.GetID())),
   110  				zap.Int("resolvedLocksNum", len(locks)))
   111  		} else {
   112  			logutil.Logger(ctx).Info("[gc worker] region has more than limit locks",
   113  				zap.Int("regionID", int(resolvedLocation.Region.GetID())),
   114  				zap.Int("resolvedLocksNum", len(locks)),
   115  				zap.Int("scan lock limit", gcScanLockLimit))
   116  			key = locks[len(locks)-1].Key
   117  		}
   118  
   119  		if len(key) == 0 || (len(endKey) != 0 && bytes.Compare(key, endKey) >= 0) {
   120  			break
   121  		}
   122  		bo = NewGcResolveLockMaxBackoffer(ctx)
   123  	}
   124  	return stat, nil
   125  }
   126  
   127  func (s *KVStore) scanLocksInRegionWithStartKey(bo *retry.Backoffer, startKey []byte, maxVersion uint64, limit uint32) (locks []*txnlock.Lock, loc *locate.KeyLocation, err error) {
   128  	for {
   129  		loc, err := s.GetRegionCache().LocateKey(bo, startKey)
   130  		if err != nil {
   131  			return nil, loc, errors.Trace(err)
   132  		}
   133  		req := tikvrpc.NewRequest(tikvrpc.CmdScanLock, &kvrpcpb.ScanLockRequest{
   134  			MaxVersion: maxVersion,
   135  			Limit:      gcScanLockLimit,
   136  			StartKey:   startKey,
   137  			EndKey:     loc.EndKey,
   138  		})
   139  		resp, err := s.SendReq(bo, req, loc.Region, ReadTimeoutMedium)
   140  		if err != nil {
   141  			return nil, loc, errors.Trace(err)
   142  		}
   143  		regionErr, err := resp.GetRegionError()
   144  		if err != nil {
   145  			return nil, loc, errors.Trace(err)
   146  		}
   147  		if regionErr != nil {
   148  			err = bo.Backoff(BoRegionMiss(), errors.New(regionErr.String()))
   149  			if err != nil {
   150  				return nil, loc, errors.Trace(err)
   151  			}
   152  			continue
   153  		}
   154  		if resp.Resp == nil {
   155  			return nil, loc, errors.Trace(tikverr.ErrBodyMissing)
   156  		}
   157  		locksResp := resp.Resp.(*kvrpcpb.ScanLockResponse)
   158  		if locksResp.GetError() != nil {
   159  			return nil, loc, errors.Errorf("unexpected scanlock error: %s", locksResp)
   160  		}
   161  		locksInfo := locksResp.GetLocks()
   162  		locks = make([]*txnlock.Lock, len(locksInfo))
   163  		for i := range locksInfo {
   164  			locks[i] = txnlock.NewLock(locksInfo[i])
   165  		}
   166  		return locks, loc, nil
   167  	}
   168  }
   169  
   170  // batchResolveLocksInARegion resolves locks in a region.
   171  // It returns the real location of the resolved locks if resolve locks success.
   172  // It returns error when meet an unretryable error.
   173  // When the locks are not in one region, resolve locks should be failed, it returns with nil resolveLocation and nil err.
   174  // Used it in gcworker only!
   175  func (s *KVStore) batchResolveLocksInARegion(bo *Backoffer, locks []*txnlock.Lock, expectedLoc *locate.KeyLocation) (resolvedLocation *locate.KeyLocation, err error) {
   176  	resolvedLocation = expectedLoc
   177  	for {
   178  		ok, err := s.GetLockResolver().BatchResolveLocks(bo, locks, resolvedLocation.Region)
   179  		if ok {
   180  			return resolvedLocation, nil
   181  		}
   182  		if err != nil {
   183  			return nil, err
   184  		}
   185  		err = bo.Backoff(retry.BoTxnLock, errors.Errorf("remain locks: %d", len(locks)))
   186  		if err != nil {
   187  			return nil, errors.Trace(err)
   188  		}
   189  		region, err1 := s.GetRegionCache().LocateKey(bo, locks[0].Key)
   190  		if err1 != nil {
   191  			return nil, errors.Trace(err1)
   192  		}
   193  		if !region.Contains(locks[len(locks)-1].Key) {
   194  			// retry scan since the locks are not in the same region anymore.
   195  			return nil, nil
   196  		}
   197  		resolvedLocation = region
   198  	}
   199  }
   200  
   201  const unsafeDestroyRangeTimeout = 5 * time.Minute
   202  
   203  // UnsafeDestroyRange Cleans up all keys in a range[startKey,endKey) and quickly free the disk space.
   204  // The range might span over multiple regions, and the `ctx` doesn't indicate region. The request will be done directly
   205  // on RocksDB, bypassing the Raft layer. User must promise that, after calling `UnsafeDestroyRange`,
   206  // the range will never be accessed any more. However, `UnsafeDestroyRange` is allowed to be called
   207  // multiple times on an single range.
   208  func (s *KVStore) UnsafeDestroyRange(ctx context.Context, startKey []byte, endKey []byte) error {
   209  	// Get all stores every time deleting a region. So the store list is less probably to be stale.
   210  	stores, err := s.listStoresForUnsafeDestory(ctx)
   211  	if err != nil {
   212  		metrics.TiKVUnsafeDestroyRangeFailuresCounterVec.WithLabelValues("get_stores").Inc()
   213  		return errors.Trace(err)
   214  	}
   215  
   216  	req := tikvrpc.NewRequest(tikvrpc.CmdUnsafeDestroyRange, &kvrpcpb.UnsafeDestroyRangeRequest{
   217  		StartKey: startKey,
   218  		EndKey:   endKey,
   219  	})
   220  
   221  	var wg sync.WaitGroup
   222  	errChan := make(chan error, len(stores))
   223  
   224  	for _, store := range stores {
   225  		address := store.Address
   226  		storeID := store.Id
   227  		wg.Add(1)
   228  		go func() {
   229  			defer wg.Done()
   230  
   231  			resp, err1 := s.GetTiKVClient().SendRequest(ctx, address, req, unsafeDestroyRangeTimeout)
   232  			if err1 == nil {
   233  				if resp == nil || resp.Resp == nil {
   234  					err1 = errors.Errorf("[unsafe destroy range] returns nil response from store %v", storeID)
   235  				} else {
   236  					errStr := (resp.Resp.(*kvrpcpb.UnsafeDestroyRangeResponse)).Error
   237  					if len(errStr) > 0 {
   238  						err1 = errors.Errorf("[unsafe destroy range] range failed on store %v: %s", storeID, errStr)
   239  					}
   240  				}
   241  			}
   242  
   243  			if err1 != nil {
   244  				metrics.TiKVUnsafeDestroyRangeFailuresCounterVec.WithLabelValues("send").Inc()
   245  			}
   246  			errChan <- err1
   247  		}()
   248  	}
   249  
   250  	var errs []string
   251  	for range stores {
   252  		err1 := <-errChan
   253  		if err1 != nil {
   254  			errs = append(errs, err1.Error())
   255  		}
   256  	}
   257  
   258  	wg.Wait()
   259  
   260  	if len(errs) > 0 {
   261  		return errors.Errorf("[unsafe destroy range] destroy range finished with errors: %v", errs)
   262  	}
   263  
   264  	return nil
   265  }
   266  
   267  func (s *KVStore) listStoresForUnsafeDestory(ctx context.Context) ([]*metapb.Store, error) {
   268  	stores, err := s.pdClient.GetAllStores(ctx)
   269  	if err != nil {
   270  		return nil, errors.Trace(err)
   271  	}
   272  
   273  	upStores := make([]*metapb.Store, 0, len(stores))
   274  	for _, store := range stores {
   275  		if store.State == metapb.StoreState_Tombstone {
   276  			continue
   277  		}
   278  		if tikvrpc.GetStoreTypeByMeta(store) == tikvrpc.TiFlash {
   279  			continue
   280  		}
   281  		upStores = append(upStores, store)
   282  	}
   283  	return upStores, nil
   284  }