UNPKG

ravendb

Version:
240 lines 9.25 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.NodeSelector = void 0; const ArrayUtil_js_1 = require("../Utility/ArrayUtil.js"); const CurrentIndexAndNode_js_1 = require("../Http/CurrentIndexAndNode.js"); const Timer_js_1 = require("../Primitives/Timer.js"); const index_js_1 = require("../Exceptions/index.js"); class NodeSelectorState { topology; failures; fastestRecords; fastest = 0; speedTestMode = 1; unlikelyEveryoneFaultedChoiceIndex; constructor(topology, prevState) { this.topology = topology; this.failures = ArrayUtil_js_1.ArrayUtil.range(topology.nodes.length, () => 0); this.fastestRecords = ArrayUtil_js_1.ArrayUtil.range(topology.nodes.length, () => 0); this.unlikelyEveryoneFaultedChoiceIndex = 0; if (prevState) { if (prevState.fastest < 0 || prevState.fastest >= prevState.nodes.length) { return; } const fastestNode = prevState.nodes[prevState.fastest]; let index = 0; for (const node of topology.nodes) { if (node.clusterTag === fastestNode.clusterTag) { this.fastest = index; break; } index++; } // fastest node was not found in the new topology. enable speed tests if (index >= topology.nodes.length) { this.speedTestMode = 2; } else { // we might be in the process of finding fastest node when we reorder the nodes, we don't want the tests to stop until we reach 10 // otherwise, we want to stop the tests and they may be scheduled later on relevant topology change if (this.fastest < prevState.fastestRecords.length && prevState.fastestRecords[this.fastest] < 10) { this.speedTestMode = prevState.speedTestMode; } } } } get nodes() { return this.topology.nodes; } getNodeWhenEveryoneMarkedAsFaulted() { const index = this.unlikelyEveryoneFaultedChoiceIndex; this.unlikelyEveryoneFaultedChoiceIndex = (this.unlikelyEveryoneFaultedChoiceIndex + 1) % this.nodes.length; return new CurrentIndexAndNode_js_1.default(index, this.nodes[index]); } } class NodeSelector { _updateFastestNodeTimer; _state; constructor(topology) { this._state = new NodeSelectorState(topology); } getTopology() { return this._state.topology; } onFailedRequest(nodeIndex) { const state = this._state; if (nodeIndex < 0 || nodeIndex >= state.failures.length) { return; // probably already changed } state.failures[nodeIndex]++; } onUpdateTopology(topology, forceUpdate = false) { if (!topology) { return false; } const stateEtag = this._state.topology.etag || 0; const topologyEtag = topology.etag || 0; if (stateEtag >= topologyEtag && !forceUpdate) { return false; } this._state = new NodeSelectorState(topology, this._state); return true; } getNodeBySessionId(sessionId) { const state = this._state; if (state.topology.nodes.length === 0) { (0, index_js_1.throwError)("DatabaseDoesNotExistException", "There are no nodes in the topology at all"); } const index = Math.abs(sessionId % state.topology.nodes.length); for (let i = index; i < state.failures.length; i++) { if (state.failures[i] === 0 && state.nodes[i].serverRole === "Member") { return new CurrentIndexAndNode_js_1.default(i, state.nodes[i]); } } for (let i = 0; i < index; i++) { if (state.failures[i] === 0 && state.nodes[i].serverRole === "Member") { return new CurrentIndexAndNode_js_1.default(i, state.nodes[i]); } } return this.getPreferredNode(); } getRequestedNode(nodeTag) { const state = this._state; const serverNodes = state.nodes; for (let i = 0; i < serverNodes.length; i++) { if (serverNodes[i].clusterTag === nodeTag) { return new CurrentIndexAndNode_js_1.default(i, serverNodes[i]); } } if (!state.nodes.length) { (0, index_js_1.throwError)("AllTopologyNodesDownException", "There are no nodes in the topology at all."); } (0, index_js_1.throwError)("RequestedNodeUnavailableException", "Could not find requested node " + nodeTag); } nodeIsAvailable(index) { return this._state.failures[index] === 0; } getPreferredNode() { const state = this._state; return NodeSelector.getPreferredNodeInternal(state); } static getPreferredNodeInternal(state) { const stateFailures = state.failures; const serverNodes = state.nodes; const len = Math.min(serverNodes.length, stateFailures.length); for (let i = 0; i < len; i++) { if (stateFailures[i] === 0 && "Member" === serverNodes[i].serverRole) { return new CurrentIndexAndNode_js_1.default(i, serverNodes[i]); } } return NodeSelector._unlikelyEveryoneFaultedChoice(state); } getNodeSelectorFailures() { return this._state.failures; } static _unlikelyEveryoneFaultedChoice(state) { // if there are all marked as failed, we'll choose the next (the one in CurrentNodeIndex) // one so the user will get an error (or recover :-) ); if (state.nodes.length === 0) { (0, index_js_1.throwError)("DatabaseDoesNotExistException", "There are no nodes in the topology at all."); } const stateFailures = state.failures; const serverNodes = state.nodes; const len = Math.min(serverNodes.length, stateFailures.length); for (let i = 0; i < len; i++) { if (stateFailures[i] === 0) { return new CurrentIndexAndNode_js_1.default(i, serverNodes[i]); } } return state.getNodeWhenEveryoneMarkedAsFaulted(); } getFastestNode() { const state = this._state; if (state.failures[state.fastest] === 0 && state.nodes[state.fastest].serverRole === "Member") { return new CurrentIndexAndNode_js_1.default(state.fastest, state.nodes[state.fastest]); } // until new fastest node is selected, we'll just use the server preferred node or failover as usual this.scheduleSpeedTest(); return this.getPreferredNode(); } restoreNodeIndex(node) { const state = this._state; const nodeIndex = state.nodes.indexOf(node); if (nodeIndex === -1) { return; } state.failures[nodeIndex] = 0; } _switchToSpeedTestPhase() { const state = this._state; if (state.speedTestMode === 0) { state.speedTestMode = 1; } else { return; } state.fastestRecords.fill(0); state.speedTestMode++; } inSpeedTestPhase() { return this._state.speedTestMode > 1; } recordFastest(index, node) { const state = this._state; const stateFastest = state.fastestRecords; // the following two checks are to verify that things didn't move // while we were computing the fastest node, we verify that the index // of the fastest node and the identity of the node didn't change during // our check if (index < 0 || index >= stateFastest.length) { return; } if (node !== state.nodes[index]) { return; } if (++stateFastest[index] >= 10) { this._selectFastest(state, index); return; } if (++state.speedTestMode <= state.nodes.length * 10) { return; } // too many concurrent speed tests are happening const maxIndex = NodeSelector._findMaxIndex(state); this._selectFastest(state, maxIndex); } static _findMaxIndex(state) { const stateFastest = state.fastestRecords; let maxIndex = 0; let maxValue = 0; for (let i = 0; i < stateFastest.length; i++) { if (maxValue >= stateFastest[i]) { continue; } maxIndex = i; maxValue = stateFastest[i]; } return maxIndex; } _selectFastest(state, index) { state.fastest = index; state.speedTestMode = 0; this.scheduleSpeedTest(); } scheduleSpeedTest() { if (this._updateFastestNodeTimer) { return; } this._switchToSpeedTestPhase(); const minuteMs = 60_000; this._updateFastestNodeTimer = new Timer_js_1.Timer(async () => this._switchToSpeedTestPhase(), minuteMs, minuteMs); } dispose() { this._updateFastestNodeTimer?.dispose(); } } exports.NodeSelector = NodeSelector; //# sourceMappingURL=NodeSelector.js.map