majority.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. // Copyright 2019 The etcd Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package quorum
  15. import (
  16. "fmt"
  17. "math"
  18. "sort"
  19. "strings"
  20. )
  21. // MajorityConfig is a set of IDs that uses majority quorums to make decisions.
  22. type MajorityConfig map[uint64]struct{}
  23. func (c MajorityConfig) String() string {
  24. sl := make([]uint64, 0, len(c))
  25. for id := range c {
  26. sl = append(sl, id)
  27. }
  28. sort.Slice(sl, func(i, j int) bool { return sl[i] < sl[j] })
  29. var buf strings.Builder
  30. buf.WriteByte('(')
  31. for i := range sl {
  32. if i > 0 {
  33. buf.WriteByte(' ')
  34. }
  35. fmt.Fprint(&buf, sl[i])
  36. }
  37. buf.WriteByte(')')
  38. return buf.String()
  39. }
  40. // Describe returns a (multi-line) representation of the commit indexes for the
  41. // given lookuper.
  42. func (c MajorityConfig) Describe(l AckedIndexer) string {
  43. if len(c) == 0 {
  44. return "<empty majority quorum>"
  45. }
  46. type tup struct {
  47. id uint64
  48. idx Index
  49. ok bool // idx found?
  50. bar int // length of bar displayed for this tup
  51. }
  52. // Below, populate .bar so that the i-th largest commit index has bar i (we
  53. // plot this as sort of a progress bar). The actual code is a bit more
  54. // complicated and also makes sure that equal index => equal bar.
  55. n := len(c)
  56. info := make([]tup, 0, n)
  57. for id := range c {
  58. idx, ok := l.AckedIndex(id)
  59. info = append(info, tup{id: id, idx: idx, ok: ok})
  60. }
  61. // Sort by index
  62. sort.Slice(info, func(i, j int) bool {
  63. if info[i].idx == info[j].idx {
  64. return info[i].id < info[j].id
  65. }
  66. return info[i].idx < info[j].idx
  67. })
  68. // Populate .bar.
  69. for i := range info {
  70. if i > 0 && info[i-1].idx < info[i].idx {
  71. info[i].bar = i
  72. }
  73. }
  74. // Sort by ID.
  75. sort.Slice(info, func(i, j int) bool {
  76. return info[i].id < info[j].id
  77. })
  78. var buf strings.Builder
  79. // Print.
  80. fmt.Fprint(&buf, strings.Repeat(" ", n)+" idx\n")
  81. for i := range info {
  82. bar := info[i].bar
  83. if !info[i].ok {
  84. fmt.Fprint(&buf, "?"+strings.Repeat(" ", n))
  85. } else {
  86. fmt.Fprint(&buf, strings.Repeat("x", bar)+">"+strings.Repeat(" ", n-bar))
  87. }
  88. fmt.Fprintf(&buf, " %5d (id=%d)\n", info[i].idx, info[i].id)
  89. }
  90. return buf.String()
  91. }
  92. // Slice returns the MajorityConfig as a sorted slice.
  93. func (c MajorityConfig) Slice() []uint64 {
  94. var sl []uint64
  95. for id := range c {
  96. sl = append(sl, id)
  97. }
  98. sort.Slice(sl, func(i, j int) bool { return sl[i] < sl[j] })
  99. return sl
  100. }
  101. func insertionSort(sl []uint64) {
  102. a, b := 0, len(sl)
  103. for i := a + 1; i < b; i++ {
  104. for j := i; j > a && sl[j] < sl[j-1]; j-- {
  105. sl[j], sl[j-1] = sl[j-1], sl[j]
  106. }
  107. }
  108. }
  109. // CommittedIndex computes the committed index from those supplied via the
  110. // provided AckedIndexer (for the active config).
  111. func (c MajorityConfig) CommittedIndex(l AckedIndexer) Index {
  112. n := len(c)
  113. if n == 0 {
  114. // This plays well with joint quorums which, when one half is the zero
  115. // MajorityConfig, should behave like the other half.
  116. return math.MaxUint64
  117. }
  118. // Use an on-stack slice to collect the committed indexes when n <= 7
  119. // (otherwise we alloc). The alternative is to stash a slice on
  120. // MajorityConfig, but this impairs usability (as is, MajorityConfig is just
  121. // a map, and that's nice). The assumption is that running with a
  122. // replication factor of >7 is rare, and in cases in which it happens
  123. // performance is a lesser concern (additionally the performance
  124. // implications of an allocation here are far from drastic).
  125. var stk [7]uint64
  126. var srt []uint64
  127. if len(stk) >= n {
  128. srt = stk[:n]
  129. } else {
  130. srt = make([]uint64, n)
  131. }
  132. {
  133. // Fill the slice with the indexes observed. Any unused slots will be
  134. // left as zero; these correspond to voters that may report in, but
  135. // haven't yet. We fill from the right (since the zeroes will end up on
  136. // the left after sorting below anyway).
  137. i := n - 1
  138. for id := range c {
  139. if idx, ok := l.AckedIndex(id); ok {
  140. srt[i] = uint64(idx)
  141. i--
  142. }
  143. }
  144. }
  145. // Sort by index. Use a bespoke algorithm (copied from the stdlib's sort
  146. // package) to keep srt on the stack.
  147. insertionSort(srt)
  148. // The smallest index into the array for which the value is acked by a
  149. // quorum. In other words, from the end of the slice, move n/2+1 to the
  150. // left (accounting for zero-indexing).
  151. pos := n - (n/2 + 1)
  152. return Index(srt[pos])
  153. }
  154. // VoteResult takes a mapping of voters to yes/no (true/false) votes and returns
  155. // a result indicating whether the vote is pending (i.e. neither a quorum of
  156. // yes/no has been reached), won (a quorum of yes has been reached), or lost (a
  157. // quorum of no has been reached).
  158. func (c MajorityConfig) VoteResult(votes map[uint64]bool) VoteResult {
  159. if len(c) == 0 {
  160. // By convention, the elections on an empty config win. This comes in
  161. // handy with joint quorums because it'll make a half-populated joint
  162. // quorum behave like a majority quorum.
  163. return VoteWon
  164. }
  165. ny := [2]int{} // vote counts for no and yes, respectively
  166. var missing int
  167. for id := range c {
  168. v, ok := votes[id]
  169. if !ok {
  170. missing++
  171. continue
  172. }
  173. if v {
  174. ny[1]++
  175. } else {
  176. ny[0]++
  177. }
  178. }
  179. q := len(c)/2 + 1
  180. if ny[1] >= q {
  181. return VoteWon
  182. }
  183. if ny[1]+missing >= q {
  184. return VotePending
  185. }
  186. return VoteLost
  187. }