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  }