github.com/KinWaiYuen/client-go/v2@v2.5.4/txnkv/rangetask/delete_range.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  // NOTE: The code in this file is based on code from the
    16  // TiDB project, licensed under the Apache License v 2.0
    17  //
    18  // https://github.com/pingcap/tidb/tree/cc5e161ac06827589c4966674597c137cc9e809c/store/tikv/delete_range.go
    19  //
    20  
    21  // Copyright 2018 PingCAP, Inc.
    22  //
    23  // Licensed under the Apache License, Version 2.0 (the "License");
    24  // you may not use this file except in compliance with the License.
    25  // You may obtain a copy of the License at
    26  //
    27  //     http://www.apache.org/licenses/LICENSE-2.0
    28  //
    29  // Unless required by applicable law or agreed to in writing, software
    30  // distributed under the License is distributed on an "AS IS" BASIS,
    31  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    32  // See the License for the specific language governing permissions and
    33  // limitations under the License.
    34  
    35  package rangetask
    36  
    37  import (
    38  	"bytes"
    39  	"context"
    40  	"time"
    41  
    42  	tikverr "github.com/KinWaiYuen/client-go/v2/error"
    43  	"github.com/KinWaiYuen/client-go/v2/internal/client"
    44  	"github.com/KinWaiYuen/client-go/v2/internal/locate"
    45  	"github.com/KinWaiYuen/client-go/v2/internal/retry"
    46  	"github.com/KinWaiYuen/client-go/v2/kv"
    47  	"github.com/KinWaiYuen/client-go/v2/tikvrpc"
    48  	"github.com/pingcap/errors"
    49  	"github.com/pingcap/kvproto/pkg/kvrpcpb"
    50  )
    51  
    52  type storage interface {
    53  	// GetRegionCache gets the RegionCache.
    54  	GetRegionCache() *locate.RegionCache
    55  	// SendReq sends a request to TiKV.
    56  	SendReq(bo *retry.Backoffer, req *tikvrpc.Request, regionID locate.RegionVerID, timeout time.Duration) (*tikvrpc.Response, error)
    57  }
    58  
    59  // DeleteRangeTask is used to delete all keys in a range. After
    60  // performing DeleteRange, it keeps how many ranges it affects and
    61  // if the task was canceled or not.
    62  type DeleteRangeTask struct {
    63  	completedRegions int
    64  	store            storage
    65  	startKey         []byte
    66  	endKey           []byte
    67  	notifyOnly       bool
    68  	concurrency      int
    69  }
    70  
    71  // NewDeleteRangeTask creates a DeleteRangeTask. Deleting will be performed when `Execute` method is invoked.
    72  // Be careful while using this API. This API doesn't keep recent MVCC versions, but will delete all versions of all keys
    73  // in the range immediately. Also notice that frequent invocation to this API may cause performance problems to TiKV.
    74  func NewDeleteRangeTask(store storage, startKey []byte, endKey []byte, concurrency int) *DeleteRangeTask {
    75  	return &DeleteRangeTask{
    76  		completedRegions: 0,
    77  		store:            store,
    78  		startKey:         startKey,
    79  		endKey:           endKey,
    80  		notifyOnly:       false,
    81  		concurrency:      concurrency,
    82  	}
    83  }
    84  
    85  // NewNotifyDeleteRangeTask creates a task that sends delete range requests to all regions in the range, but with the
    86  // flag `notifyOnly` set. TiKV will not actually delete the range after receiving request, but it will be replicated via
    87  // raft. This is used to notify the involved regions before sending UnsafeDestroyRange requests.
    88  func NewNotifyDeleteRangeTask(store storage, startKey []byte, endKey []byte, concurrency int) *DeleteRangeTask {
    89  	task := NewDeleteRangeTask(store, startKey, endKey, concurrency)
    90  	task.notifyOnly = true
    91  	return task
    92  }
    93  
    94  // getRunnerName returns a name for RangeTaskRunner.
    95  func (t *DeleteRangeTask) getRunnerName() string {
    96  	if t.notifyOnly {
    97  		return "delete-range-notify"
    98  	}
    99  	return "delete-range"
   100  }
   101  
   102  // Execute performs the delete range operation.
   103  func (t *DeleteRangeTask) Execute(ctx context.Context) error {
   104  	runnerName := t.getRunnerName()
   105  
   106  	runner := NewRangeTaskRunner(runnerName, t.store, t.concurrency, t.sendReqOnRange)
   107  	err := runner.RunOnRange(ctx, t.startKey, t.endKey)
   108  	t.completedRegions = runner.CompletedRegions()
   109  
   110  	return err
   111  }
   112  
   113  const deleteRangeOneRegionMaxBackoff = 100000
   114  
   115  // Execute performs the delete range operation.
   116  func (t *DeleteRangeTask) sendReqOnRange(ctx context.Context, r kv.KeyRange) (TaskStat, error) {
   117  	startKey, rangeEndKey := r.StartKey, r.EndKey
   118  	var stat TaskStat
   119  	for {
   120  		select {
   121  		case <-ctx.Done():
   122  			return stat, errors.Trace(ctx.Err())
   123  		default:
   124  		}
   125  
   126  		if bytes.Compare(startKey, rangeEndKey) >= 0 {
   127  			break
   128  		}
   129  
   130  		bo := retry.NewBackofferWithVars(ctx, deleteRangeOneRegionMaxBackoff, nil)
   131  		loc, err := t.store.GetRegionCache().LocateKey(bo, startKey)
   132  		if err != nil {
   133  			return stat, errors.Trace(err)
   134  		}
   135  
   136  		// Delete to the end of the region, except if it's the last region overlapping the range
   137  		endKey := loc.EndKey
   138  		// If it is the last region
   139  		if loc.Contains(rangeEndKey) {
   140  			endKey = rangeEndKey
   141  		}
   142  
   143  		req := tikvrpc.NewRequest(tikvrpc.CmdDeleteRange, &kvrpcpb.DeleteRangeRequest{
   144  			StartKey:   startKey,
   145  			EndKey:     endKey,
   146  			NotifyOnly: t.notifyOnly,
   147  		})
   148  
   149  		resp, err := t.store.SendReq(bo, req, loc.Region, client.ReadTimeoutMedium)
   150  		if err != nil {
   151  			return stat, errors.Trace(err)
   152  		}
   153  		regionErr, err := resp.GetRegionError()
   154  		if err != nil {
   155  			return stat, errors.Trace(err)
   156  		}
   157  		if regionErr != nil {
   158  			err = bo.Backoff(retry.BoRegionMiss, errors.New(regionErr.String()))
   159  			if err != nil {
   160  				return stat, errors.Trace(err)
   161  			}
   162  			continue
   163  		}
   164  		if resp.Resp == nil {
   165  			return stat, errors.Trace(tikverr.ErrBodyMissing)
   166  		}
   167  		deleteRangeResp := resp.Resp.(*kvrpcpb.DeleteRangeResponse)
   168  		if err := deleteRangeResp.GetError(); err != "" {
   169  			return stat, errors.Errorf("unexpected delete range err: %v", err)
   170  		}
   171  		stat.CompletedRegions++
   172  		startKey = endKey
   173  	}
   174  
   175  	return stat, nil
   176  }
   177  
   178  // CompletedRegions returns the number of regions that are affected by this delete range task
   179  func (t *DeleteRangeTask) CompletedRegions() int {
   180  	return t.completedRegions
   181  }