vitess.io/vitess@v0.16.2/go/vt/vtadmin/internal/backoff/backoff.go (about) 1 /* 2 Copyright 2022 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package backoff implements different backoff strategies for retrying failed 18 // operations in VTAdmin. 19 // 20 // It is first a reimplementation of grpc-go's internal exponential backoff 21 // strategy, with one modification to prevent a jittered backoff from exceeding 22 // the MaxDelay specified in a config. 23 // 24 // Then, for other use-cases, it implements a linear backoff strategy, as well 25 // as a "none" strategy which is primarly intended for use in tests. 26 package backoff 27 28 import ( 29 "fmt" 30 "strings" 31 "time" 32 33 grpcbackoff "google.golang.org/grpc/backoff" 34 35 "vitess.io/vitess/go/vt/log" 36 vtrand "vitess.io/vitess/go/vt/vtadmin/internal/rand" 37 ) 38 39 // Strategy defines the interface for different backoff strategies. 40 type Strategy interface { 41 // Backoff returns the amount of time to backoff for the given retry count. 42 Backoff(retries int) time.Duration 43 } 44 45 var ( 46 DefaultExponential = Exponential{grpcbackoff.DefaultConfig} 47 DefaultLinear = Linear{grpcbackoff.DefaultConfig} 48 DefaultNone = None{} 49 ) 50 51 // Exponential implements an exponential backoff strategy with optional jitter. 52 type Exponential struct { 53 Config grpcbackoff.Config 54 } 55 56 // Backoff is part of the Strategy interface. 57 func (e Exponential) Backoff(retries int) time.Duration { 58 return backoffCommon(retries, e.Config, func(cur float64) float64 { return cur * e.Config.Multiplier }) 59 } 60 61 // Linear implements a linear backoff strategy with optional jitter. 62 type Linear struct { 63 Config grpcbackoff.Config 64 } 65 66 // Backoff is part of the Strategy interface. 67 func (l Linear) Backoff(retries int) time.Duration { 68 return backoffCommon(retries, l.Config, func(cur float64) float64 { return cur + l.Config.Multiplier }) 69 } 70 71 func backoffCommon(retries int, cfg grpcbackoff.Config, adjust func(cur float64) float64) time.Duration { 72 if retries == 0 { 73 return cfg.BaseDelay 74 } 75 76 backoff, max := float64(cfg.BaseDelay), float64(cfg.MaxDelay) 77 for backoff < max && retries > 0 { 78 backoff = adjust(backoff) 79 retries-- 80 } 81 if backoff > max { 82 backoff = max 83 } 84 // Randomize backoff delays so that if a cluster of requests start at 85 // the same time, they won't operate in lockstep. 86 backoff *= 1 + cfg.Jitter*(vtrand.Float64()*2-1) 87 if backoff < 0 { 88 return 0 89 } 90 91 // NOTE: We differ from grpc's exponential backoff here, which actually can 92 // jitter to a backoff that exceeds the config's MaxDelay, which in my (ajm188) 93 // opinion is a bug. 94 if backoff > max { 95 backoff = max 96 } 97 98 return time.Duration(backoff) 99 } 100 101 // None implements a "backoff" strategy that can be summarized as "don't". 102 type None struct{} 103 104 // Backoff is part of the Strategy interface. 105 func (None) Backoff(int) time.Duration { return 0 } 106 107 // Get returns a backoff Strategy for the specified strategy name, with the 108 // given config. Strategy lookup is case-insensitive, and the empty string 109 // defaults to an exponential strategy. It panics if an unsupported strategy 110 // name is specified. 111 // 112 // Currently-supported strategies are "exponential", "linear", and "none". 113 func Get(strategy string, cfg grpcbackoff.Config) Strategy { 114 switch strings.ToLower(strategy) { 115 case "": 116 log.Warningf("no backoff strategy specified; defaulting to exponential") 117 fallthrough 118 case "exponential": 119 return Exponential{cfg} 120 case "linear": 121 return Linear{cfg} 122 case "none": 123 return None{} 124 default: 125 panic(fmt.Sprintf("unknown backoff strategy: %s", strategy)) 126 } 127 }