Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

server_selectors.js 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. 'use strict';
  2. const ServerType = require('./server_description').ServerType;
  3. const TopologyType = require('./topology_description').TopologyType;
  4. const ReadPreference = require('../topologies/read_preference');
  5. const MongoError = require('../error').MongoError;
  6. // max staleness constants
  7. const IDLE_WRITE_PERIOD = 10000;
  8. const SMALLEST_MAX_STALENESS_SECONDS = 90;
  9. /**
  10. * Returns a server selector that selects for writable servers
  11. */
  12. function writableServerSelector() {
  13. return function(topologyDescription, servers) {
  14. return latencyWindowReducer(topologyDescription, servers.filter(s => s.isWritable));
  15. };
  16. }
  17. /**
  18. * Reduces the passed in array of servers by the rules of the "Max Staleness" specification
  19. * found here: https://github.com/mongodb/specifications/blob/master/source/max-staleness/max-staleness.rst
  20. *
  21. * @param {ReadPreference} readPreference The read preference providing max staleness guidance
  22. * @param {topologyDescription} topologyDescription The topology description
  23. * @param {ServerDescription[]} servers The list of server descriptions to be reduced
  24. * @return {ServerDescription[]} The list of servers that satisfy the requirements of max staleness
  25. */
  26. function maxStalenessReducer(readPreference, topologyDescription, servers) {
  27. if (readPreference.maxStalenessSeconds == null || readPreference.maxStalenessSeconds < 0) {
  28. return servers;
  29. }
  30. const maxStaleness = readPreference.maxStalenessSeconds;
  31. const maxStalenessVariance =
  32. (topologyDescription.heartbeatFrequencyMS + IDLE_WRITE_PERIOD) / 1000;
  33. if (maxStaleness < maxStalenessVariance) {
  34. throw new MongoError(`maxStalenessSeconds must be at least ${maxStalenessVariance} seconds`);
  35. }
  36. if (maxStaleness < SMALLEST_MAX_STALENESS_SECONDS) {
  37. throw new MongoError(
  38. `maxStalenessSeconds must be at least ${SMALLEST_MAX_STALENESS_SECONDS} seconds`
  39. );
  40. }
  41. if (topologyDescription.type === TopologyType.ReplicaSetWithPrimary) {
  42. const primary = servers.filter(primaryFilter)[0];
  43. return servers.reduce((result, server) => {
  44. const stalenessMS =
  45. server.lastUpdateTime -
  46. server.lastWriteDate -
  47. (primary.lastUpdateTime - primary.lastWriteDate) +
  48. topologyDescription.heartbeatFrequencyMS;
  49. const staleness = stalenessMS / 1000;
  50. if (staleness <= readPreference.maxStalenessSeconds) result.push(server);
  51. return result;
  52. }, []);
  53. } else if (topologyDescription.type === TopologyType.ReplicaSetNoPrimary) {
  54. const sMax = servers.reduce((max, s) => (s.lastWriteDate > max.lastWriteDate ? s : max));
  55. return servers.reduce((result, server) => {
  56. const stalenessMS =
  57. sMax.lastWriteDate - server.lastWriteDate + topologyDescription.heartbeatFrequencyMS;
  58. const staleness = stalenessMS / 1000;
  59. if (staleness <= readPreference.maxStalenessSeconds) result.push(server);
  60. return result;
  61. }, []);
  62. }
  63. return servers;
  64. }
  65. /**
  66. * Determines whether a server's tags match a given set of tags
  67. *
  68. * @param {String[]} tagSet The requested tag set to match
  69. * @param {String[]} serverTags The server's tags
  70. */
  71. function tagSetMatch(tagSet, serverTags) {
  72. const keys = Object.keys(tagSet);
  73. const serverTagKeys = Object.keys(serverTags);
  74. for (let i = 0; i < keys.length; ++i) {
  75. const key = keys[i];
  76. if (serverTagKeys.indexOf(key) === -1 || serverTags[key] !== tagSet[key]) {
  77. return false;
  78. }
  79. }
  80. return true;
  81. }
  82. /**
  83. * Reduces a set of server descriptions based on tags requested by the read preference
  84. *
  85. * @param {ReadPreference} readPreference The read preference providing the requested tags
  86. * @param {ServerDescription[]} servers The list of server descriptions to reduce
  87. * @return {ServerDescription[]} The list of servers matching the requested tags
  88. */
  89. function tagSetReducer(readPreference, servers) {
  90. if (
  91. readPreference.tags == null ||
  92. (Array.isArray(readPreference.tags) && readPreference.tags.length === 0)
  93. ) {
  94. return servers;
  95. }
  96. for (let i = 0; i < readPreference.tags.length; ++i) {
  97. const tagSet = readPreference.tags[i];
  98. const serversMatchingTagset = servers.reduce((matched, server) => {
  99. if (tagSetMatch(tagSet, server.tags)) matched.push(server);
  100. return matched;
  101. }, []);
  102. if (serversMatchingTagset.length) {
  103. return serversMatchingTagset;
  104. }
  105. }
  106. return [];
  107. }
  108. /**
  109. * Reduces a list of servers to ensure they fall within an acceptable latency window. This is
  110. * further specified in the "Server Selection" specification, found here:
  111. * https://github.com/mongodb/specifications/blob/master/source/server-selection/server-selection.rst
  112. *
  113. * @param {topologyDescription} topologyDescription The topology description
  114. * @param {ServerDescription[]} servers The list of servers to reduce
  115. * @returns {ServerDescription[]} The servers which fall within an acceptable latency window
  116. */
  117. function latencyWindowReducer(topologyDescription, servers) {
  118. const low = servers.reduce(
  119. (min, server) => (min === -1 ? server.roundTripTime : Math.min(server.roundTripTime, min)),
  120. -1
  121. );
  122. const high = low + topologyDescription.localThresholdMS;
  123. return servers.reduce((result, server) => {
  124. if (server.roundTripTime <= high && server.roundTripTime >= low) result.push(server);
  125. return result;
  126. }, []);
  127. }
  128. // filters
  129. function primaryFilter(server) {
  130. return server.type === ServerType.RSPrimary;
  131. }
  132. function secondaryFilter(server) {
  133. return server.type === ServerType.RSSecondary;
  134. }
  135. function nearestFilter(server) {
  136. return server.type === ServerType.RSSecondary || server.type === ServerType.RSPrimary;
  137. }
  138. function knownFilter(server) {
  139. return server.type !== ServerType.Unknown;
  140. }
  141. /**
  142. * Returns a function which selects servers based on a provided read preference
  143. *
  144. * @param {ReadPreference} readPreference The read preference to select with
  145. */
  146. function readPreferenceServerSelector(readPreference) {
  147. if (!readPreference.isValid()) {
  148. throw new TypeError('Invalid read preference specified');
  149. }
  150. return function(topologyDescription, servers) {
  151. const commonWireVersion = topologyDescription.commonWireVersion;
  152. if (
  153. commonWireVersion &&
  154. (readPreference.minWireVersion && readPreference.minWireVersion > commonWireVersion)
  155. ) {
  156. throw new MongoError(
  157. `Minimum wire version '${
  158. readPreference.minWireVersion
  159. }' required, but found '${commonWireVersion}'`
  160. );
  161. }
  162. if (
  163. topologyDescription.type === TopologyType.Single ||
  164. topologyDescription.type === TopologyType.Sharded
  165. ) {
  166. return latencyWindowReducer(topologyDescription, servers.filter(knownFilter));
  167. }
  168. if (readPreference.mode === ReadPreference.PRIMARY) {
  169. return servers.filter(primaryFilter);
  170. }
  171. if (readPreference.mode === ReadPreference.SECONDARY) {
  172. return latencyWindowReducer(
  173. topologyDescription,
  174. tagSetReducer(
  175. readPreference,
  176. maxStalenessReducer(readPreference, topologyDescription, servers)
  177. )
  178. ).filter(secondaryFilter);
  179. } else if (readPreference.mode === ReadPreference.NEAREST) {
  180. return latencyWindowReducer(
  181. topologyDescription,
  182. tagSetReducer(
  183. readPreference,
  184. maxStalenessReducer(readPreference, topologyDescription, servers)
  185. )
  186. ).filter(nearestFilter);
  187. } else if (readPreference.mode === ReadPreference.SECONDARY_PREFERRED) {
  188. const result = latencyWindowReducer(
  189. topologyDescription,
  190. tagSetReducer(
  191. readPreference,
  192. maxStalenessReducer(readPreference, topologyDescription, servers)
  193. )
  194. ).filter(secondaryFilter);
  195. return result.length === 0 ? servers.filter(primaryFilter) : result;
  196. } else if (readPreference.mode === ReadPreference.PRIMARY_PREFERRED) {
  197. const result = servers.filter(primaryFilter);
  198. if (result.length) {
  199. return result;
  200. }
  201. return latencyWindowReducer(
  202. topologyDescription,
  203. tagSetReducer(
  204. readPreference,
  205. maxStalenessReducer(readPreference, topologyDescription, servers)
  206. )
  207. ).filter(secondaryFilter);
  208. }
  209. };
  210. }
  211. module.exports = {
  212. writableServerSelector,
  213. readPreferenceServerSelector
  214. };