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 }