github.com/m3db/m3@v1.5.0/src/dbnode/topology/consistency_level.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package topology 22 23 import ( 24 "errors" 25 "fmt" 26 "strings" 27 ) 28 29 // ConsistencyLevel is the consistency level for cluster operations 30 type ConsistencyLevel int 31 32 // nolint: varcheck, unused 33 const ( 34 consistencyLevelNone ConsistencyLevel = iota 35 36 // ConsistencyLevelOne corresponds to a single node participating 37 // for an operation to succeed 38 ConsistencyLevelOne 39 40 // ConsistencyLevelMajority corresponds to the majority of nodes participating 41 // for an operation to succeed 42 ConsistencyLevelMajority 43 44 // ConsistencyLevelAll corresponds to all nodes participating 45 // for an operation to succeed 46 ConsistencyLevelAll 47 ) 48 49 // String returns the consistency level as a string 50 func (l ConsistencyLevel) String() string { 51 switch l { 52 case consistencyLevelNone: 53 return none 54 case ConsistencyLevelOne: 55 return one 56 case ConsistencyLevelMajority: 57 return majority 58 case ConsistencyLevelAll: 59 return all 60 } 61 return unknown 62 } 63 64 var validConsistencyLevels = []ConsistencyLevel{ 65 consistencyLevelNone, 66 ConsistencyLevelOne, 67 ConsistencyLevelMajority, 68 ConsistencyLevelAll, 69 } 70 71 var ( 72 errConsistencyLevelUnspecified = errors.New("consistency level not specified") 73 errConsistencyLevelInvalid = errors.New("consistency level invalid") 74 ) 75 76 // ValidConsistencyLevels returns a copy of valid consistency levels 77 // to avoid callers mutating the set of valid read consistency levels 78 func ValidConsistencyLevels() []ConsistencyLevel { 79 result := make([]ConsistencyLevel, len(validConsistencyLevels)) 80 copy(result, validConsistencyLevels) 81 return result 82 } 83 84 // ValidateConsistencyLevel returns nil when consistency level is valid, 85 // otherwise it returns an error 86 func ValidateConsistencyLevel(v ConsistencyLevel) error { 87 for _, level := range validConsistencyLevels { 88 if level == v { 89 return nil 90 } 91 } 92 return errConsistencyLevelInvalid 93 } 94 95 // UnmarshalYAML unmarshals an ConnectConsistencyLevel into a valid type from string. 96 func (l *ConsistencyLevel) UnmarshalYAML(unmarshal func(interface{}) error) error { 97 var str string 98 if err := unmarshal(&str); err != nil { 99 return err 100 } 101 if str == "" { 102 return errConsistencyLevelUnspecified 103 } 104 strs := make([]string, 0, len(validConsistencyLevels)) 105 for _, valid := range validConsistencyLevels { 106 if str == valid.String() { 107 *l = valid 108 return nil 109 } 110 strs = append(strs, "'"+valid.String()+"'") 111 } 112 return fmt.Errorf("invalid ConsistencyLevel '%s' valid types are: %s", 113 str, strings.Join(strs, ", ")) 114 } 115 116 // ConnectConsistencyLevel is the consistency level for connecting to a cluster 117 type ConnectConsistencyLevel int 118 119 const ( 120 // ConnectConsistencyLevelAny corresponds to connecting to any number of nodes for a given shard 121 // set, this strategy will attempt to connect to all, then the majority, then one and then none. 122 ConnectConsistencyLevelAny ConnectConsistencyLevel = iota 123 124 // ConnectConsistencyLevelNone corresponds to connecting to no nodes for a given shard set 125 ConnectConsistencyLevelNone 126 127 // ConnectConsistencyLevelOne corresponds to connecting to a single node for a given shard set 128 ConnectConsistencyLevelOne 129 130 // ConnectConsistencyLevelMajority corresponds to connecting to the majority of nodes for a given shard set 131 ConnectConsistencyLevelMajority 132 133 // ConnectConsistencyLevelAll corresponds to connecting to all of the nodes for a given shard set 134 ConnectConsistencyLevelAll 135 ) 136 137 // String returns the consistency level as a string 138 func (l ConnectConsistencyLevel) String() string { 139 switch l { 140 case ConnectConsistencyLevelAny: 141 return any 142 case ConnectConsistencyLevelNone: 143 return none 144 case ConnectConsistencyLevelOne: 145 return one 146 case ConnectConsistencyLevelMajority: 147 return majority 148 case ConnectConsistencyLevelAll: 149 return all 150 } 151 return unknown 152 } 153 154 var validConnectConsistencyLevels = []ConnectConsistencyLevel{ 155 ConnectConsistencyLevelAny, 156 ConnectConsistencyLevelNone, 157 ConnectConsistencyLevelOne, 158 ConnectConsistencyLevelMajority, 159 ConnectConsistencyLevelAll, 160 } 161 162 var ( 163 errClusterConnectConsistencyLevelUnspecified = errors.New("cluster connect consistency level not specified") 164 errClusterConnectConsistencyLevelInvalid = errors.New("cluster connect consistency level invalid") 165 ) 166 167 // ValidConnectConsistencyLevels returns a copy of valid consistency levels 168 // to avoid callers mutating the set of valid read consistency levels 169 func ValidConnectConsistencyLevels() []ConnectConsistencyLevel { 170 result := make([]ConnectConsistencyLevel, len(validConnectConsistencyLevels)) 171 copy(result, validConnectConsistencyLevels) 172 return result 173 } 174 175 // ValidateConnectConsistencyLevel returns nil when consistency level is valid, 176 // otherwise it returns an error 177 func ValidateConnectConsistencyLevel(v ConnectConsistencyLevel) error { 178 for _, level := range validConnectConsistencyLevels { 179 if level == v { 180 return nil 181 } 182 } 183 return errClusterConnectConsistencyLevelInvalid 184 } 185 186 // UnmarshalYAML unmarshals an ConnectConsistencyLevel into a valid type from string. 187 func (l *ConnectConsistencyLevel) UnmarshalYAML(unmarshal func(interface{}) error) error { 188 var str string 189 if err := unmarshal(&str); err != nil { 190 return err 191 } 192 if str == "" { 193 return errClusterConnectConsistencyLevelUnspecified 194 } 195 for _, valid := range validConnectConsistencyLevels { 196 if str == valid.String() { 197 *l = valid 198 return nil 199 } 200 } 201 return fmt.Errorf("invalid ConnectConsistencyLevel '%s' valid types are: %s", 202 str, validConnectConsistencyLevels) 203 } 204 205 // ReadConsistencyLevel is the consistency level for reading from a cluster 206 type ReadConsistencyLevel int 207 208 const ( 209 // ReadConsistencyLevelNone corresponds to reading from no nodes 210 ReadConsistencyLevelNone ReadConsistencyLevel = iota 211 212 // ReadConsistencyLevelOne corresponds to reading from a single node 213 ReadConsistencyLevelOne 214 215 // ReadConsistencyLevelUnstrictMajority corresponds to reading from the majority of nodes 216 // but relaxing the constraint when it cannot be met, falling back to returning success when 217 // reading from at least a single node after attempting reading from the majority of nodes 218 ReadConsistencyLevelUnstrictMajority 219 220 // ReadConsistencyLevelMajority corresponds to reading from the majority of nodes 221 ReadConsistencyLevelMajority 222 223 // ReadConsistencyLevelUnstrictAll corresponds to reading from all nodes 224 // but relaxing the constraint when it cannot be met, falling back to returning success when 225 // reading from at least a single node after attempting reading from all of nodes 226 ReadConsistencyLevelUnstrictAll 227 228 // ReadConsistencyLevelAll corresponds to reading from all of the nodes 229 ReadConsistencyLevelAll 230 ) 231 232 // String returns the consistency level as a string 233 func (l ReadConsistencyLevel) String() string { 234 switch l { 235 case ReadConsistencyLevelNone: 236 return none 237 case ReadConsistencyLevelOne: 238 return one 239 case ReadConsistencyLevelUnstrictMajority: 240 return unstrictMajority 241 case ReadConsistencyLevelMajority: 242 return majority 243 case ReadConsistencyLevelUnstrictAll: 244 return unstrictAll 245 case ReadConsistencyLevelAll: 246 return all 247 } 248 return unknown 249 } 250 251 var validReadConsistencyLevels = []ReadConsistencyLevel{ 252 ReadConsistencyLevelNone, 253 ReadConsistencyLevelOne, 254 ReadConsistencyLevelUnstrictMajority, 255 ReadConsistencyLevelMajority, 256 ReadConsistencyLevelUnstrictAll, 257 ReadConsistencyLevelAll, 258 } 259 260 var ( 261 errReadConsistencyLevelUnspecified = errors.New("read consistency level not specified") 262 errReadConsistencyLevelInvalid = errors.New("read consistency level invalid") 263 ) 264 265 // ValidReadConsistencyLevels returns a copy of valid consistency levels 266 // to avoid callers mutating the set of valid read consistency levels 267 func ValidReadConsistencyLevels() []ReadConsistencyLevel { 268 result := make([]ReadConsistencyLevel, len(validReadConsistencyLevels)) 269 copy(result, validReadConsistencyLevels) 270 return result 271 } 272 273 // ParseReadConsistencyLevel parses a ReadConsistencyLevel 274 // from a string. 275 func ParseReadConsistencyLevel( 276 str string, 277 ) (ReadConsistencyLevel, error) { 278 var r ReadConsistencyLevel 279 if str == "" { 280 return r, errConsistencyLevelUnspecified 281 } 282 for _, valid := range ValidReadConsistencyLevels() { 283 if str == valid.String() { 284 r = valid 285 return r, nil 286 } 287 } 288 return r, fmt.Errorf("invalid ReadConsistencyLevel '%s' valid types are: %v", 289 str, ValidReadConsistencyLevels()) 290 } 291 292 // ValidateReadConsistencyLevel returns nil when consistency level is valid, 293 // otherwise it returns an error 294 func ValidateReadConsistencyLevel(v ReadConsistencyLevel) error { 295 for _, level := range validReadConsistencyLevels { 296 if level == v { 297 return nil 298 } 299 } 300 return errReadConsistencyLevelInvalid 301 } 302 303 // UnmarshalYAML unmarshals an ConnectConsistencyLevel into a valid type from string. 304 func (l *ReadConsistencyLevel) UnmarshalYAML(unmarshal func(interface{}) error) error { 305 var str string 306 if err := unmarshal(&str); err != nil { 307 return err 308 } 309 if str == "" { 310 return errReadConsistencyLevelUnspecified 311 } 312 strs := make([]string, 0, len(validReadConsistencyLevels)) 313 for _, valid := range validReadConsistencyLevels { 314 if str == valid.String() { 315 *l = valid 316 return nil 317 } 318 strs = append(strs, "'"+valid.String()+"'") 319 } 320 return fmt.Errorf("invalid ReadConsistencyLevel '%s' valid types are: %s", 321 str, strings.Join(strs, ", ")) 322 } 323 324 // string constants, required to fix lint complaining about 325 // multiple occurrences of same literal string... 326 const ( 327 unknown = "unknown" 328 any = "any" 329 all = "all" 330 unstrictAll = "unstrict_all" 331 one = "one" 332 none = "none" 333 majority = "majority" 334 unstrictMajority = "unstrict_majority" 335 ) 336 337 // WriteConsistencyAchieved returns a bool indicating whether or not we've received enough 338 // successful acks to consider a write successful based on the specified consistency level. 339 func WriteConsistencyAchieved( 340 level ConsistencyLevel, 341 majority, numPeers, numSuccess int, 342 ) bool { 343 switch level { 344 case ConsistencyLevelAll: 345 if numSuccess == numPeers { // Meets all 346 return true 347 } 348 return false 349 case ConsistencyLevelMajority: 350 if numSuccess >= majority { // Meets majority 351 return true 352 } 353 return false 354 case ConsistencyLevelOne: 355 if numSuccess > 0 { // Meets one 356 return true 357 } 358 return false 359 } 360 panic(fmt.Errorf("unrecognized consistency level: %s", level.String())) 361 } 362 363 // ReadConsistencyTermination returns a bool to indicate whether sufficient 364 // responses (error/success) have been received, so that we're able to decide 365 // whether we will be able to satisfy the reuquest or not. 366 // NB: it is not the same as `readConsistencyAchieved`. 367 func ReadConsistencyTermination( 368 level ReadConsistencyLevel, 369 majority, remaining, success int32, 370 ) bool { 371 doneAll := remaining == 0 372 switch level { 373 case ReadConsistencyLevelOne, ReadConsistencyLevelNone: 374 return success > 0 || doneAll 375 case ReadConsistencyLevelMajority, ReadConsistencyLevelUnstrictMajority: 376 return success >= majority || doneAll 377 case ReadConsistencyLevelAll, ReadConsistencyLevelUnstrictAll: 378 return doneAll 379 } 380 panic(fmt.Errorf("unrecognized consistency level: %s", level.String())) 381 } 382 383 // ReadConsistencyAchieved returns whether sufficient responses have been received 384 // to reach the desired consistency. 385 // NB: it is not the same as `readConsistencyTermination`. 386 func ReadConsistencyAchieved( 387 level ReadConsistencyLevel, 388 majority, numPeers, numSuccess int, 389 ) bool { 390 switch level { 391 case ReadConsistencyLevelAll: 392 return numSuccess == numPeers // Meets all 393 case ReadConsistencyLevelMajority: 394 return numSuccess >= majority // Meets majority 395 case ReadConsistencyLevelOne, ReadConsistencyLevelUnstrictMajority, ReadConsistencyLevelUnstrictAll: 396 return numSuccess > 0 // Meets one 397 case ReadConsistencyLevelNone: 398 return true // Always meets none 399 } 400 panic(fmt.Errorf("unrecognized consistency level: %s", level.String())) 401 } 402 403 // NumDesiredForReadConsistency returns the number of replicas that would ideally be used to 404 // satisfy the read consistency. 405 func NumDesiredForReadConsistency(level ReadConsistencyLevel, numReplicas, majority int) int { 406 switch level { 407 case ReadConsistencyLevelAll, ReadConsistencyLevelUnstrictAll: 408 return numReplicas 409 case ReadConsistencyLevelMajority, ReadConsistencyLevelUnstrictMajority: 410 return majority 411 case ReadConsistencyLevelOne: 412 return 1 413 case ReadConsistencyLevelNone: 414 return 0 415 } 416 panic(fmt.Errorf("unrecognized consistency level: %s", level.String())) 417 }