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 }