ravendb
Version:
RavenDB client for Node.js
240 lines • 9.25 kB
JavaScript
"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