github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/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 // MarshalYAML marshals a ConsistencyLevel. 96 func (l *ConsistencyLevel) MarshalYAML() (interface{}, error) { 97 return l.String(), nil 98 } 99 100 // UnmarshalYAML unmarshals an ConnectConsistencyLevel into a valid type from string. 101 func (l *ConsistencyLevel) UnmarshalYAML(unmarshal func(interface{}) error) error { 102 var str string 103 if err := unmarshal(&str); err != nil { 104 return err 105 } 106 if str == "" { 107 return errConsistencyLevelUnspecified 108 } 109 strs := make([]string, 0, len(validConsistencyLevels)) 110 for _, valid := range validConsistencyLevels { 111 if str == valid.String() { 112 *l = valid 113 return nil 114 } 115 strs = append(strs, "'"+valid.String()+"'") 116 } 117 return fmt.Errorf("invalid ConsistencyLevel '%s' valid types are: %s", 118 str, strings.Join(strs, ", ")) 119 } 120 121 // ConnectConsistencyLevel is the consistency level for connecting to a cluster 122 type ConnectConsistencyLevel int 123 124 const ( 125 // ConnectConsistencyLevelAny corresponds to connecting to any number of nodes for a given shard 126 // set, this strategy will attempt to connect to all, then the majority, then one and then none. 127 ConnectConsistencyLevelAny ConnectConsistencyLevel = iota 128 129 // ConnectConsistencyLevelNone corresponds to connecting to no nodes for a given shard set 130 ConnectConsistencyLevelNone 131 132 // ConnectConsistencyLevelOne corresponds to connecting to a single node for a given shard set 133 ConnectConsistencyLevelOne 134 135 // ConnectConsistencyLevelMajority corresponds to connecting to the majority of nodes for a given shard set 136 ConnectConsistencyLevelMajority 137 138 // ConnectConsistencyLevelAll corresponds to connecting to all of the nodes for a given shard set 139 ConnectConsistencyLevelAll 140 ) 141 142 // String returns the consistency level as a string 143 func (l ConnectConsistencyLevel) String() string { 144 switch l { 145 case ConnectConsistencyLevelAny: 146 return any 147 case ConnectConsistencyLevelNone: 148 return none 149 case ConnectConsistencyLevelOne: 150 return one 151 case ConnectConsistencyLevelMajority: 152 return majority 153 case ConnectConsistencyLevelAll: 154 return all 155 } 156 return unknown 157 } 158 159 var validConnectConsistencyLevels = []ConnectConsistencyLevel{ 160 ConnectConsistencyLevelAny, 161 ConnectConsistencyLevelNone, 162 ConnectConsistencyLevelOne, 163 ConnectConsistencyLevelMajority, 164 ConnectConsistencyLevelAll, 165 } 166 167 var ( 168 errClusterConnectConsistencyLevelUnspecified = errors.New("cluster connect consistency level not specified") 169 errClusterConnectConsistencyLevelInvalid = errors.New("cluster connect consistency level invalid") 170 ) 171 172 // ValidConnectConsistencyLevels returns a copy of valid consistency levels 173 // to avoid callers mutating the set of valid read consistency levels 174 func ValidConnectConsistencyLevels() []ConnectConsistencyLevel { 175 result := make([]ConnectConsistencyLevel, len(validConnectConsistencyLevels)) 176 copy(result, validConnectConsistencyLevels) 177 return result 178 } 179 180 // ValidateConnectConsistencyLevel returns nil when consistency level is valid, 181 // otherwise it returns an error 182 func ValidateConnectConsistencyLevel(v ConnectConsistencyLevel) error { 183 for _, level := range validConnectConsistencyLevels { 184 if level == v { 185 return nil 186 } 187 } 188 return errClusterConnectConsistencyLevelInvalid 189 } 190 191 // MarshalYAML marshals a ConnectConsistencyLevel. 192 func (l *ConnectConsistencyLevel) MarshalYAML() (interface{}, error) { 193 return l.String(), nil 194 } 195 196 // UnmarshalYAML unmarshals an ConnectConsistencyLevel into a valid type from string. 197 func (l *ConnectConsistencyLevel) UnmarshalYAML(unmarshal func(interface{}) error) error { 198 var str string 199 if err := unmarshal(&str); err != nil { 200 return err 201 } 202 if str == "" { 203 return errClusterConnectConsistencyLevelUnspecified 204 } 205 for _, valid := range validConnectConsistencyLevels { 206 if str == valid.String() { 207 *l = valid 208 return nil 209 } 210 } 211 return fmt.Errorf("invalid ConnectConsistencyLevel '%s' valid types are: %s", 212 str, validConnectConsistencyLevels) 213 } 214 215 // ReadConsistencyLevel is the consistency level for reading from a cluster 216 type ReadConsistencyLevel int 217 218 const ( 219 // ReadConsistencyLevelNone corresponds to reading from no nodes 220 ReadConsistencyLevelNone ReadConsistencyLevel = iota 221 222 // ReadConsistencyLevelOne corresponds to reading from a single node 223 ReadConsistencyLevelOne 224 225 // ReadConsistencyLevelUnstrictMajority corresponds to reading from the majority of nodes 226 // but relaxing the constraint when it cannot be met, falling back to returning success when 227 // reading from at least a single node after attempting reading from the majority of nodes 228 ReadConsistencyLevelUnstrictMajority 229 230 // ReadConsistencyLevelMajority corresponds to reading from the majority of nodes 231 ReadConsistencyLevelMajority 232 233 // ReadConsistencyLevelUnstrictAll corresponds to reading from all nodes 234 // but relaxing the constraint when it cannot be met, falling back to returning success when 235 // reading from at least a single node after attempting reading from all of nodes 236 ReadConsistencyLevelUnstrictAll 237 238 // ReadConsistencyLevelAll corresponds to reading from all of the nodes 239 ReadConsistencyLevelAll 240 ) 241 242 // String returns the consistency level as a string 243 func (l ReadConsistencyLevel) String() string { 244 switch l { 245 case ReadConsistencyLevelNone: 246 return none 247 case ReadConsistencyLevelOne: 248 return one 249 case ReadConsistencyLevelUnstrictMajority: 250 return unstrictMajority 251 case ReadConsistencyLevelMajority: 252 return majority 253 case ReadConsistencyLevelUnstrictAll: 254 return unstrictAll 255 case ReadConsistencyLevelAll: 256 return all 257 } 258 return unknown 259 } 260 261 var validReadConsistencyLevels = []ReadConsistencyLevel{ 262 ReadConsistencyLevelNone, 263 ReadConsistencyLevelOne, 264 ReadConsistencyLevelUnstrictMajority, 265 ReadConsistencyLevelMajority, 266 ReadConsistencyLevelUnstrictAll, 267 ReadConsistencyLevelAll, 268 } 269 270 var ( 271 errReadConsistencyLevelUnspecified = errors.New("read consistency level not specified") 272 errReadConsistencyLevelInvalid = errors.New("read consistency level invalid") 273 ) 274 275 // ValidReadConsistencyLevels returns a copy of valid consistency levels 276 // to avoid callers mutating the set of valid read consistency levels 277 func ValidReadConsistencyLevels() []ReadConsistencyLevel { 278 result := make([]ReadConsistencyLevel, len(validReadConsistencyLevels)) 279 copy(result, validReadConsistencyLevels) 280 return result 281 } 282 283 // ParseReadConsistencyLevel parses a ReadConsistencyLevel 284 // from a string. 285 func ParseReadConsistencyLevel( 286 str string, 287 ) (ReadConsistencyLevel, error) { 288 var r ReadConsistencyLevel 289 if str == "" { 290 return r, errConsistencyLevelUnspecified 291 } 292 for _, valid := range ValidReadConsistencyLevels() { 293 if str == valid.String() { 294 r = valid 295 return r, nil 296 } 297 } 298 return r, fmt.Errorf("invalid ReadConsistencyLevel '%s' valid types are: %v", 299 str, ValidReadConsistencyLevels()) 300 } 301 302 // ValidateReadConsistencyLevel returns nil when consistency level is valid, 303 // otherwise it returns an error 304 func ValidateReadConsistencyLevel(v ReadConsistencyLevel) error { 305 for _, level := range validReadConsistencyLevels { 306 if level == v { 307 return nil 308 } 309 } 310 return errReadConsistencyLevelInvalid 311 } 312 313 // MarshalYAML marshals a ReadConsistencyLevel. 314 func (l *ReadConsistencyLevel) MarshalYAML() (interface{}, error) { 315 return l.String(), nil 316 } 317 318 // UnmarshalYAML unmarshals an ConnectConsistencyLevel into a valid type from string. 319 func (l *ReadConsistencyLevel) UnmarshalYAML(unmarshal func(interface{}) error) error { 320 var str string 321 if err := unmarshal(&str); err != nil { 322 return err 323 } 324 if str == "" { 325 return errReadConsistencyLevelUnspecified 326 } 327 strs := make([]string, 0, len(validReadConsistencyLevels)) 328 for _, valid := range validReadConsistencyLevels { 329 if str == valid.String() { 330 *l = valid 331 return nil 332 } 333 strs = append(strs, "'"+valid.String()+"'") 334 } 335 return fmt.Errorf("invalid ReadConsistencyLevel '%s' valid types are: %s", 336 str, strings.Join(strs, ", ")) 337 } 338 339 // string constants, required to fix lint complaining about 340 // multiple occurrences of same literal string... 341 const ( 342 unknown = "unknown" 343 any = "any" 344 all = "all" 345 unstrictAll = "unstrict_all" 346 one = "one" 347 none = "none" 348 majority = "majority" 349 unstrictMajority = "unstrict_majority" 350 ) 351 352 // WriteConsistencyAchieved returns a bool indicating whether or not we've received enough 353 // successful acks to consider a write successful based on the specified consistency level. 354 func WriteConsistencyAchieved( 355 level ConsistencyLevel, 356 majority, numPeers, numSuccess int, 357 ) bool { 358 switch level { 359 case ConsistencyLevelAll: 360 if numSuccess == numPeers { // Meets all 361 return true 362 } 363 return false 364 case ConsistencyLevelMajority: 365 if numSuccess >= majority { // Meets majority 366 return true 367 } 368 return false 369 case ConsistencyLevelOne: 370 if numSuccess > 0 { // Meets one 371 return true 372 } 373 return false 374 } 375 panic(fmt.Errorf("unrecognized consistency level: %s", level.String())) 376 } 377 378 // ReadConsistencyTermination returns a bool to indicate whether sufficient 379 // responses (error/success) have been received, so that we're able to decide 380 // whether we will be able to satisfy the reuquest or not. 381 // NB: it is not the same as `readConsistencyAchieved`. 382 func ReadConsistencyTermination( 383 level ReadConsistencyLevel, 384 majority, remaining, success int32, 385 ) bool { 386 doneAll := remaining == 0 387 switch level { 388 case ReadConsistencyLevelOne, ReadConsistencyLevelNone: 389 return success > 0 || doneAll 390 case ReadConsistencyLevelMajority, ReadConsistencyLevelUnstrictMajority: 391 return success >= majority || doneAll 392 case ReadConsistencyLevelAll, ReadConsistencyLevelUnstrictAll: 393 return doneAll 394 } 395 panic(fmt.Errorf("unrecognized consistency level: %s", level.String())) 396 } 397 398 // ReadConsistencyAchieved returns whether sufficient responses have been received 399 // to reach the desired consistency. 400 // NB: it is not the same as `readConsistencyTermination`. 401 func ReadConsistencyAchieved( 402 level ReadConsistencyLevel, 403 majority, numPeers, numSuccess int, 404 ) bool { 405 switch level { 406 case ReadConsistencyLevelAll: 407 return numSuccess == numPeers // Meets all 408 case ReadConsistencyLevelMajority: 409 return numSuccess >= majority // Meets majority 410 case ReadConsistencyLevelOne, ReadConsistencyLevelUnstrictMajority, ReadConsistencyLevelUnstrictAll: 411 return numSuccess > 0 // Meets one 412 case ReadConsistencyLevelNone: 413 return true // Always meets none 414 } 415 panic(fmt.Errorf("unrecognized consistency level: %s", level.String())) 416 } 417 418 // NumDesiredForReadConsistency returns the number of replicas that would ideally be used to 419 // satisfy the read consistency. 420 func NumDesiredForReadConsistency(level ReadConsistencyLevel, numReplicas, majority int) int { 421 switch level { 422 case ReadConsistencyLevelAll, ReadConsistencyLevelUnstrictAll: 423 return numReplicas 424 case ReadConsistencyLevelMajority, ReadConsistencyLevelUnstrictMajority: 425 return majority 426 case ReadConsistencyLevelOne: 427 return 1 428 case ReadConsistencyLevelNone: 429 return 0 430 } 431 panic(fmt.Errorf("unrecognized consistency level: %s", level.String())) 432 }