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  }