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  }