github.com/KinWaiYuen/client-go/v2@v2.5.4/internal/retry/config.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/retry/config.go
    19  //
    20  
    21  // Copyright 2021 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 retry
    36  
    37  import (
    38  	"context"
    39  	"math"
    40  	"math/rand"
    41  	"strings"
    42  	"time"
    43  
    44  	tikverr "github.com/KinWaiYuen/client-go/v2/error"
    45  	"github.com/KinWaiYuen/client-go/v2/internal/logutil"
    46  	"github.com/KinWaiYuen/client-go/v2/kv"
    47  	"github.com/KinWaiYuen/client-go/v2/metrics"
    48  	"github.com/prometheus/client_golang/prometheus"
    49  	"go.uber.org/zap"
    50  )
    51  
    52  // Config is the configuration of the Backoff function.
    53  type Config struct {
    54  	name   string
    55  	metric *prometheus.Observer
    56  	fnCfg  *BackoffFnCfg
    57  	err    error
    58  }
    59  
    60  // backoffFn is the backoff function which compute the sleep time and do sleep.
    61  type backoffFn func(ctx context.Context, maxSleepMs int) int
    62  
    63  func (c *Config) createBackoffFn(vars *kv.Variables) backoffFn {
    64  	if strings.EqualFold(c.name, txnLockFastName) {
    65  		return newBackoffFn(vars.BackoffLockFast, c.fnCfg.cap, c.fnCfg.jitter)
    66  	}
    67  	return newBackoffFn(c.fnCfg.base, c.fnCfg.cap, c.fnCfg.jitter)
    68  }
    69  
    70  // BackoffFnCfg is the configuration for the backoff func which implements exponential backoff with
    71  // optional jitters.
    72  // See http://www.awsarchitectureblog.com/2015/03/backoff.html
    73  type BackoffFnCfg struct {
    74  	base   int
    75  	cap    int
    76  	jitter int
    77  }
    78  
    79  // NewBackoffFnCfg creates the config for BackoffFn.
    80  func NewBackoffFnCfg(base, cap, jitter int) *BackoffFnCfg {
    81  	return &BackoffFnCfg{
    82  		base,
    83  		cap,
    84  		jitter,
    85  	}
    86  }
    87  
    88  // NewConfig creates a new Config for the Backoff operation.
    89  func NewConfig(name string, metric *prometheus.Observer, backoffFnCfg *BackoffFnCfg, err error) *Config {
    90  	return &Config{
    91  		name:   name,
    92  		metric: metric,
    93  		fnCfg:  backoffFnCfg,
    94  		err:    err,
    95  	}
    96  }
    97  
    98  func (c *Config) String() string {
    99  	return c.name
   100  }
   101  
   102  // SetErrors sets a more detailed error instead of the default bo config.
   103  func (c *Config) SetErrors(err error) {
   104  	c.err = err
   105  }
   106  
   107  const txnLockFastName = "txnLockFast"
   108  
   109  // Backoff Config variables.
   110  var (
   111  	// TODO: distinguish tikv and tiflash in metrics
   112  	BoTiKVRPC    = NewConfig("tikvRPC", &metrics.BackoffHistogramRPC, NewBackoffFnCfg(100, 2000, EqualJitter), tikverr.ErrTiKVServerTimeout)
   113  	BoTiFlashRPC = NewConfig("tiflashRPC", &metrics.BackoffHistogramRPC, NewBackoffFnCfg(100, 2000, EqualJitter), tikverr.ErrTiFlashServerTimeout)
   114  	BoTxnLock    = NewConfig("txnLock", &metrics.BackoffHistogramLock, NewBackoffFnCfg(200, 3000, EqualJitter), tikverr.ErrResolveLockTimeout)
   115  	BoPDRPC      = NewConfig("pdRPC", &metrics.BackoffHistogramPD, NewBackoffFnCfg(500, 3000, EqualJitter), tikverr.NewErrPDServerTimeout(""))
   116  	// change base time to 2ms, because it may recover soon.
   117  	BoRegionMiss              = NewConfig("regionMiss", &metrics.BackoffHistogramRegionMiss, NewBackoffFnCfg(2, 500, NoJitter), tikverr.ErrRegionUnavailable)
   118  	BoRegionScheduling        = NewConfig("regionScheduling", &metrics.BackoffHistogramRegionScheduling, NewBackoffFnCfg(2, 500, NoJitter), tikverr.ErrRegionUnavailable)
   119  	BoTiKVServerBusy          = NewConfig("tikvServerBusy", &metrics.BackoffHistogramServerBusy, NewBackoffFnCfg(2000, 10000, EqualJitter), tikverr.ErrTiKVServerBusy)
   120  	BoTiKVDiskFull            = NewConfig("tikvDiskFull", &metrics.BackoffHistogramTiKVDiskFull, NewBackoffFnCfg(500, 5000, NoJitter), tikverr.ErrTiKVDiskFull)
   121  	BoTiFlashServerBusy       = NewConfig("tiflashServerBusy", &metrics.BackoffHistogramServerBusy, NewBackoffFnCfg(2000, 10000, EqualJitter), tikverr.ErrTiFlashServerBusy)
   122  	BoTxnNotFound             = NewConfig("txnNotFound", &metrics.BackoffHistogramEmpty, NewBackoffFnCfg(2, 500, NoJitter), tikverr.ErrResolveLockTimeout)
   123  	BoStaleCmd                = NewConfig("staleCommand", &metrics.BackoffHistogramStaleCmd, NewBackoffFnCfg(2, 1000, NoJitter), tikverr.ErrTiKVStaleCommand)
   124  	BoMaxTsNotSynced          = NewConfig("maxTsNotSynced", &metrics.BackoffHistogramEmpty, NewBackoffFnCfg(2, 500, NoJitter), tikverr.ErrTiKVMaxTimestampNotSynced)
   125  	BoMaxDataNotReady         = NewConfig("dataNotReady", &metrics.BackoffHistogramDataNotReady, NewBackoffFnCfg(100, 2000, NoJitter), tikverr.ErrRegionDataNotReady)
   126  	BoMaxRegionNotInitialized = NewConfig("regionNotInitialized", &metrics.BackoffHistogramEmpty, NewBackoffFnCfg(2, 1000, NoJitter), tikverr.ErrRegionNotInitialized)
   127  	// TxnLockFast's `base` load from vars.BackoffLockFast when create BackoffFn.
   128  	BoTxnLockFast = NewConfig(txnLockFastName, &metrics.BackoffHistogramLockFast, NewBackoffFnCfg(2, 3000, EqualJitter), tikverr.ErrResolveLockTimeout)
   129  )
   130  
   131  var isSleepExcluded = map[*Config]struct{}{
   132  	BoTiKVServerBusy: {},
   133  	// add BoTiFlashServerBusy if appropriate
   134  }
   135  
   136  const (
   137  	// NoJitter makes the backoff sequence strict exponential.
   138  	NoJitter = 1 + iota
   139  	// FullJitter applies random factors to strict exponential.
   140  	FullJitter
   141  	// EqualJitter is also randomized, but prevents very short sleeps.
   142  	EqualJitter
   143  	// DecorrJitter increases the maximum jitter based on the last random value.
   144  	DecorrJitter
   145  )
   146  
   147  // newBackoffFn creates a backoff func which implements exponential backoff with
   148  // optional jitters.
   149  // See http://www.awsarchitectureblog.com/2015/03/backoff.html
   150  func newBackoffFn(base, cap, jitter int) backoffFn {
   151  	if base < 2 {
   152  		// Top prevent panic in 'rand.Intn'.
   153  		base = 2
   154  	}
   155  	attempts := 0
   156  	lastSleep := base
   157  	return func(ctx context.Context, maxSleepMs int) int {
   158  		var sleep int
   159  		switch jitter {
   160  		case NoJitter:
   161  			sleep = expo(base, cap, attempts)
   162  		case FullJitter:
   163  			v := expo(base, cap, attempts)
   164  			sleep = rand.Intn(v)
   165  		case EqualJitter:
   166  			v := expo(base, cap, attempts)
   167  			sleep = v/2 + rand.Intn(v/2)
   168  		case DecorrJitter:
   169  			sleep = int(math.Min(float64(cap), float64(base+rand.Intn(lastSleep*3-base))))
   170  		}
   171  		logutil.BgLogger().Debug("backoff",
   172  			zap.Int("base", base),
   173  			zap.Int("sleep", sleep),
   174  			zap.Int("attempts", attempts))
   175  
   176  		realSleep := sleep
   177  		// when set maxSleepMs >= 0 in `tikv.BackoffWithMaxSleep` will force sleep maxSleepMs milliseconds.
   178  		if maxSleepMs >= 0 && realSleep > maxSleepMs {
   179  			realSleep = maxSleepMs
   180  		}
   181  		select {
   182  		case <-time.After(time.Duration(realSleep) * time.Millisecond):
   183  			attempts++
   184  			lastSleep = sleep
   185  			return realSleep
   186  		case <-ctx.Done():
   187  			return 0
   188  		}
   189  	}
   190  }
   191  
   192  func expo(base, cap, n int) int {
   193  	return int(math.Min(float64(cap), float64(base)*math.Pow(2.0, float64(n))))
   194  }