github.com/altipla-consulting/ravendb-go-client@v0.1.3/node_selector.go (about) 1 package ravendb 2 3 import ( 4 "time" 5 ) 6 7 // NodeSelector describes node selector 8 type NodeSelector struct { 9 updateFastestNodeTimer *time.Timer 10 state *NodeSelectorState 11 } 12 13 // NewNodeSelector creates a new NodeSelector 14 func NewNodeSelector(t *Topology) *NodeSelector { 15 state := NewNodeSelectorState(t) 16 return &NodeSelector{ 17 state: state, 18 } 19 } 20 21 func (s *NodeSelector) getTopology() *Topology { 22 return s.state.topology 23 } 24 25 func (s *NodeSelector) onFailedRequest(nodeIndex int) { 26 state := s.state 27 if nodeIndex < 0 || nodeIndex >= len(state.failures) { 28 return // probably already changed 29 } 30 31 state.failures[nodeIndex].incrementAndGet() 32 } 33 34 func (s *NodeSelector) onUpdateTopology(topology *Topology, forceUpdate bool) bool { 35 if topology == nil { 36 return false 37 } 38 39 stateEtag := s.state.topology.Etag 40 topologyEtag := topology.Etag 41 42 if stateEtag >= topologyEtag && !forceUpdate { 43 return false 44 } 45 46 s.state = NewNodeSelectorState(topology) 47 48 return true 49 } 50 51 func (s *NodeSelector) getPreferredNode() (*CurrentIndexAndNode, error) { 52 state := s.state 53 stateFailures := state.failures 54 serverNodes := state.nodes 55 n := min(len(serverNodes), len(stateFailures)) 56 for i := 0; i < n; i++ { 57 if stateFailures[i].get() == 0 && serverNodes[i].URL != "" { 58 return NewCurrentIndexAndNode(i, serverNodes[i]), nil 59 } 60 } 61 return s.unlikelyEveryoneFaultedChoice(state) 62 } 63 64 func (s *NodeSelector) unlikelyEveryoneFaultedChoice(state *NodeSelectorState) (*CurrentIndexAndNode, error) { 65 // if there are all marked as failed, we'll chose the first 66 // one so the user will get an error (or recover :-) ); 67 if len(state.nodes) == 0 { 68 return nil, newAllTopologyNodesDownError("There are no nodes in the topology at all") 69 } 70 71 return NewCurrentIndexAndNode(0, state.nodes[0]), nil 72 } 73 74 func (s *NodeSelector) getNodeBySessionID(sessionId int) (*CurrentIndexAndNode, error) { 75 state := s.state 76 index := sessionId % len(state.topology.Nodes) 77 78 for i := index; i < len(state.failures); i++ { 79 if state.failures[i].get() == 0 && state.nodes[i].ServerRole == ServerNodeRoleMember { 80 return NewCurrentIndexAndNode(i, state.nodes[i]), nil 81 } 82 } 83 84 for i := 0; i < index; i++ { 85 if state.failures[i].get() == 0 && state.nodes[i].ServerRole == ServerNodeRoleMember { 86 return NewCurrentIndexAndNode(i, state.nodes[i]), nil 87 } 88 } 89 90 return s.getPreferredNode() 91 } 92 93 func (s *NodeSelector) getFastestNode() (*CurrentIndexAndNode, error) { 94 state := s.state 95 if state.failures[state.fastest].get() == 0 && state.nodes[state.fastest].ServerRole == ServerNodeRoleMember { 96 return NewCurrentIndexAndNode(state.fastest, state.nodes[state.fastest]), nil 97 } 98 99 // if the fastest node has failures, we'll immediately schedule 100 // another run of finding who the fastest node is, in the meantime 101 // we'll just use the server preferred node or failover as usual 102 103 s.switchToSpeedTestPhase() 104 return s.getPreferredNode() 105 } 106 107 func (s *NodeSelector) restoreNodeIndex(nodeIndex int) { 108 state := s.state 109 if len(state.failures) < nodeIndex { 110 return // the state was changed and we no longer have it? 111 } 112 113 state.failures[nodeIndex].set(0) 114 } 115 116 /* 117 TODO: not used, remove? 118 func nodeSelectorThrowEmptyTopology() error { 119 return newIllegalStateError("Empty database topology, this shouldn't happen.") 120 } 121 */ 122 123 func (s *NodeSelector) switchToSpeedTestPhase() { 124 state := s.state 125 126 if !state.speedTestMode.compareAndSet(0, 1) { 127 return 128 } 129 130 for i := 0; i < len(state.fastestRecords); i++ { 131 state.fastestRecords[i] = 0 132 } 133 134 state.speedTestMode.incrementAndGet() 135 } 136 137 func (s *NodeSelector) inSpeedTestPhase() bool { 138 return s.state.speedTestMode.get() > 1 139 } 140 141 func (s *NodeSelector) recordFastest(index int, node *ServerNode) { 142 state := s.state 143 stateFastest := state.fastestRecords 144 145 // the following two checks are to verify that things didn't move 146 // while we were computing the fastest node, we verify that the index 147 // of the fastest node and the identity of the node didn't change during 148 // our check 149 if index < 0 || index >= len(stateFastest) { 150 return 151 } 152 153 if node != state.nodes[index] { 154 return 155 } 156 157 stateFastest[index]++ 158 if stateFastest[index] >= 10 { 159 s.selectFastest(state, index) 160 } 161 162 if state.speedTestMode.incrementAndGet() <= len(state.nodes)*10 { 163 return 164 } 165 166 //too many concurrent speed tests are happening 167 maxIndex := s.findMaxIndex(state) 168 s.selectFastest(state, maxIndex) 169 } 170 171 func (s *NodeSelector) findMaxIndex(state *NodeSelectorState) int { 172 stateFastest := state.fastestRecords 173 maxIndex := 0 174 maxValue := 0 175 176 for i := 0; i < len(stateFastest); i++ { 177 if maxValue >= stateFastest[i] { 178 continue 179 } 180 181 maxIndex = i 182 maxValue = stateFastest[i] 183 } 184 185 return maxIndex 186 } 187 188 func (s *NodeSelector) selectFastest(state *NodeSelectorState, index int) { 189 state.fastest = index 190 state.speedTestMode.set(0) 191 192 if s.updateFastestNodeTimer != nil { 193 s.updateFastestNodeTimer.Reset(time.Minute) 194 } else { 195 f := func() { 196 s.updateFastestNodeTimer = nil 197 s.switchToSpeedTestPhase() 198 } 199 s.updateFastestNodeTimer = time.AfterFunc(time.Minute, f) 200 } 201 } 202 203 func (s *NodeSelector) scheduleSpeedTest() { 204 s.switchToSpeedTestPhase() 205 } 206 207 func (s *NodeSelector) Close() { 208 if s.updateFastestNodeTimer != nil { 209 s.updateFastestNodeTimer.Stop() 210 s.updateFastestNodeTimer = nil 211 } 212 } 213 214 type NodeSelectorState struct { 215 topology *Topology 216 nodes []*ServerNode 217 failures []atomicInteger 218 fastestRecords []int 219 fastest int 220 speedTestMode atomicInteger 221 } 222 223 func NewNodeSelectorState(topology *Topology) *NodeSelectorState { 224 nodes := topology.Nodes 225 res := &NodeSelectorState{ 226 topology: topology, 227 nodes: nodes, 228 } 229 failures := make([]atomicInteger, len(nodes)) 230 res.failures = failures 231 res.fastestRecords = make([]int, len(nodes)) 232 return res 233 }