vitess.io/vitess@v0.16.2/go/vt/vtgate/buffer/flags.go (about)

     1  /*
     2  Copyright 2019 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 buffer
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/spf13/pflag"
    26  
    27  	"vitess.io/vitess/go/vt/log"
    28  	"vitess.io/vitess/go/vt/servenv"
    29  	"vitess.io/vitess/go/vt/topo/topoproto"
    30  )
    31  
    32  var (
    33  	bufferEnabled       bool
    34  	bufferEnabledDryRun bool
    35  
    36  	bufferWindow                  = 10 * time.Second
    37  	bufferSize                    = 1000
    38  	bufferMaxFailoverDuration     = 20 * time.Second
    39  	bufferMinTimeBetweenFailovers = time.Minute
    40  
    41  	bufferDrainConcurrency = 1
    42  	bufferKeyspaceShards   string
    43  )
    44  
    45  func registerFlags(fs *pflag.FlagSet) {
    46  	fs.BoolVar(&bufferEnabled, "enable_buffer", false, "Enable buffering (stalling) of primary traffic during failovers.")
    47  	fs.BoolVar(&bufferEnabledDryRun, "enable_buffer_dry_run", false, "Detect and log failover events, but do not actually buffer requests.")
    48  
    49  	fs.DurationVar(&bufferWindow, "buffer_window", 10*time.Second, "Duration for how long a request should be buffered at most.")
    50  	fs.IntVar(&bufferSize, "buffer_size", 1000, "Maximum number of buffered requests in flight (across all ongoing failovers).")
    51  	fs.DurationVar(&bufferMaxFailoverDuration, "buffer_max_failover_duration", 20*time.Second, "Stop buffering completely if a failover takes longer than this duration.")
    52  	fs.DurationVar(&bufferMinTimeBetweenFailovers, "buffer_min_time_between_failovers", 1*time.Minute, "Minimum time between the end of a failover and the start of the next one (tracked per shard). Faster consecutive failovers will not trigger buffering.")
    53  
    54  	fs.IntVar(&bufferDrainConcurrency, "buffer_drain_concurrency", 1, "Maximum number of requests retried simultaneously. More concurrency will increase the load on the PRIMARY vttablet when draining the buffer.")
    55  	fs.StringVar(&bufferKeyspaceShards, "buffer_keyspace_shards", "", "If not empty, limit buffering to these entries (comma separated). Entry format: keyspace or keyspace/shard. Requires --enable_buffer=true.")
    56  }
    57  
    58  func init() {
    59  	servenv.OnParseFor("vtgate", registerFlags)
    60  	servenv.OnParseFor("vtcombo", registerFlags)
    61  }
    62  
    63  func verifyFlags() error {
    64  	if bufferWindow < 1*time.Second {
    65  		return fmt.Errorf("--buffer_window must be >= 1s (specified value: %v)", bufferWindow)
    66  	}
    67  	if bufferWindow > bufferMaxFailoverDuration {
    68  		return fmt.Errorf("--buffer_window must be <= --buffer_max_failover_duration: %v vs. %v", bufferWindow, bufferMaxFailoverDuration)
    69  	}
    70  	if bufferSize < 1 {
    71  		return fmt.Errorf("--buffer_size must be >= 1 (specified value: %d)", bufferSize)
    72  	}
    73  	if bufferMinTimeBetweenFailovers < bufferMaxFailoverDuration*time.Duration(2) {
    74  		return fmt.Errorf("--buffer_min_time_between_failovers should be at least twice the length of --buffer_max_failover_duration: %v vs. %v", bufferMinTimeBetweenFailovers, bufferMaxFailoverDuration)
    75  	}
    76  
    77  	if bufferDrainConcurrency < 1 {
    78  		return fmt.Errorf("--buffer_drain_concurrency must be >= 1 (specified value: %d)", bufferDrainConcurrency)
    79  	}
    80  
    81  	if bufferKeyspaceShards != "" && !bufferEnabled {
    82  		return fmt.Errorf("--buffer_keyspace_shards=%v also requires that --enable_buffer is set", bufferKeyspaceShards)
    83  	}
    84  	if bufferEnabled && bufferEnabledDryRun && bufferKeyspaceShards == "" {
    85  		return errors.New("both the dry-run mode and actual buffering is enabled. To avoid ambiguity, keyspaces and shards for actual buffering must be explicitly listed in --buffer_keyspace_shards")
    86  	}
    87  
    88  	keyspaces, shards := keyspaceShardsToSets(bufferKeyspaceShards)
    89  	for s := range shards {
    90  		keyspace, _, err := topoproto.ParseKeyspaceShard(s)
    91  		if err != nil {
    92  			return err
    93  		}
    94  		if keyspaces[keyspace] {
    95  			return fmt.Errorf("--buffer_keyspace_shards has overlapping entries (keyspace only vs. keyspace/shard): %v vs. %v Please remove one or the other", keyspace, s)
    96  		}
    97  	}
    98  
    99  	return nil
   100  }
   101  
   102  // keyspaceShardsToSets converts a comma separated list of keyspace[/shard]
   103  // entries to two sets: keyspaces (if the shard is not specified) and shards (if
   104  // both keyspace and shard is specified).
   105  func keyspaceShardsToSets(list string) (map[string]bool, map[string]bool) {
   106  	keyspaces := make(map[string]bool)
   107  	shards := make(map[string]bool)
   108  	if list == "" {
   109  		return keyspaces, shards
   110  	}
   111  
   112  	for _, item := range strings.Split(list, ",") {
   113  		if strings.Contains(item, "/") {
   114  			shards[item] = true
   115  		} else {
   116  			keyspaces[item] = true
   117  		}
   118  	}
   119  	return keyspaces, shards
   120  }
   121  
   122  // setToString joins the set to a ", " separated string.
   123  func setToString(set map[string]bool) string {
   124  	result := ""
   125  	for item := range set {
   126  		if result != "" {
   127  			result += ", "
   128  		}
   129  		result += item
   130  	}
   131  	return result
   132  }
   133  
   134  type Config struct {
   135  	Enabled bool
   136  	DryRun  bool
   137  
   138  	Window                  time.Duration
   139  	Size                    int
   140  	MaxFailoverDuration     time.Duration
   141  	MinTimeBetweenFailovers time.Duration
   142  
   143  	DrainConcurrency int
   144  
   145  	// keyspaces has the same purpose as "shards" but applies to a whole keyspace.
   146  	Keyspaces map[string]bool
   147  	// shards is a set of keyspace/shard entries to which buffering is limited.
   148  	// If empty (and *enabled==true), buffering is enabled for all shards.
   149  	Shards map[string]bool
   150  
   151  	// internal: used for testing
   152  	now func() time.Time
   153  }
   154  
   155  func NewDefaultConfig() *Config {
   156  	return &Config{
   157  		Enabled:                 false,
   158  		DryRun:                  false,
   159  		Size:                    10,
   160  		Window:                  10 * time.Second,
   161  		MaxFailoverDuration:     20 * time.Second,
   162  		MinTimeBetweenFailovers: 1 * time.Minute,
   163  		DrainConcurrency:        1,
   164  		now:                     time.Now,
   165  	}
   166  }
   167  
   168  func NewConfigFromFlags() *Config {
   169  	if err := verifyFlags(); err != nil {
   170  		log.Fatalf("Invalid buffer configuration: %v", err)
   171  	}
   172  	bufferSizeStat.Set(int64(bufferSize))
   173  	keyspaces, shards := keyspaceShardsToSets(bufferKeyspaceShards)
   174  
   175  	if bufferEnabledDryRun {
   176  		log.Infof("vtgate buffer in dry-run mode enabled for all requests. Dry-run bufferings will log failovers but not buffer requests.")
   177  	}
   178  
   179  	if bufferEnabled {
   180  		log.Infof("vtgate buffer enabled. PRIMARY requests will be buffered during detected failovers.")
   181  
   182  		// Log a second line if it's only enabled for some keyspaces or shards.
   183  		header := "Buffering limited to configured "
   184  		limited := ""
   185  		if len(keyspaces) > 0 {
   186  			limited += "keyspaces: " + setToString(keyspaces)
   187  		}
   188  		if len(shards) > 0 {
   189  			if limited == "" {
   190  				limited += " and "
   191  			}
   192  			limited += "shards: " + setToString(shards)
   193  		}
   194  		if limited != "" {
   195  			limited = header + limited
   196  			dryRunOverride := ""
   197  			if bufferEnabledDryRun {
   198  				dryRunOverride = " Dry-run mode is overridden for these entries and actual buffering will take place."
   199  			}
   200  			log.Infof("%v.%v", limited, dryRunOverride)
   201  		}
   202  	}
   203  
   204  	if !bufferEnabledDryRun && !bufferEnabled {
   205  		log.Infof("vtgate buffer not enabled.")
   206  	}
   207  
   208  	return &Config{
   209  		Enabled: bufferEnabled,
   210  		DryRun:  bufferEnabledDryRun,
   211  
   212  		Window:                  bufferWindow,
   213  		Size:                    bufferSize,
   214  		MaxFailoverDuration:     bufferMaxFailoverDuration,
   215  		MinTimeBetweenFailovers: bufferMinTimeBetweenFailovers,
   216  
   217  		DrainConcurrency: bufferDrainConcurrency,
   218  
   219  		Keyspaces: keyspaces,
   220  		Shards:    shards,
   221  
   222  		now: time.Now,
   223  	}
   224  }
   225  
   226  func (cfg *Config) bufferingMode(keyspace, shard string) bufferMode {
   227  	// Actual buffering is enabled if
   228  	// a) no keyspaces and shards were listed in particular,
   229  	if cfg.Enabled && len(cfg.Keyspaces) == 0 && len(cfg.Shards) == 0 {
   230  		// No explicit whitelist given i.e. all shards should be buffered.
   231  		return bufferModeEnabled
   232  	}
   233  	// b) or this keyspace is listed,
   234  	if cfg.Keyspaces[keyspace] {
   235  		return bufferModeEnabled
   236  	}
   237  	// c) or this shard is listed.
   238  	keyspaceShard := topoproto.KeyspaceShardString(keyspace, shard)
   239  	if cfg.Shards[keyspaceShard] {
   240  		return bufferModeEnabled
   241  	}
   242  
   243  	if cfg.DryRun {
   244  		return bufferModeDryRun
   245  	}
   246  
   247  	return bufferModeDisabled
   248  }