CoolFace
Apppublic

opusdev/vector-similarity-api

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes
topology_description.ts549 linesDownload Raw Back to sdam
1import { EJSON, type ObjectId } from '../bson';2import * as WIRE_CONSTANTS from '../cmap/wire_protocol/constants';3import { type MongoError, MongoRuntimeError, MongoStalePrimaryError } from '../error';4import { compareObjectId, shuffle } from '../utils';5import { ServerType, TopologyType } from './common';6import { ServerDescription } from './server_description';7import type { SrvPollingEvent } from './srv_polling';8 9// constants related to compatibility checks10const MIN_SUPPORTED_SERVER_VERSION = WIRE_CONSTANTS.MIN_SUPPORTED_SERVER_VERSION;11const MAX_SUPPORTED_SERVER_VERSION = WIRE_CONSTANTS.MAX_SUPPORTED_SERVER_VERSION;12const MIN_SUPPORTED_WIRE_VERSION = WIRE_CONSTANTS.MIN_SUPPORTED_WIRE_VERSION;13const MAX_SUPPORTED_WIRE_VERSION = WIRE_CONSTANTS.MAX_SUPPORTED_WIRE_VERSION;14 15const MONGOS_OR_UNKNOWN = new Set<ServerType>([ServerType.Mongos, ServerType.Unknown]);16const MONGOS_OR_STANDALONE = new Set<ServerType>([ServerType.Mongos, ServerType.Standalone]);17const NON_PRIMARY_RS_MEMBERS = new Set<ServerType>([18  ServerType.RSSecondary,19  ServerType.RSArbiter,20  ServerType.RSOther21]);22 23/** @public */24export interface TopologyDescriptionOptions {25  heartbeatFrequencyMS?: number;26  localThresholdMS?: number;27}28 29/**30 * Representation of a deployment of servers31 * @public32 */33export class TopologyDescription {34  type: TopologyType;35  setName: string | null;36  maxSetVersion: number | null;37  maxElectionId: ObjectId | null;38  servers: Map<string, ServerDescription>;39  stale: boolean;40  compatible: boolean;41  compatibilityError?: string;42  logicalSessionTimeoutMinutes: number | null;43  heartbeatFrequencyMS: number;44  localThresholdMS: number;45  commonWireVersion: number;46  /**47   * Create a TopologyDescription48   */49  constructor(50    topologyType: TopologyType,51    serverDescriptions: Map<string, ServerDescription> | null = null,52    setName: string | null = null,53    maxSetVersion: number | null = null,54    maxElectionId: ObjectId | null = null,55    commonWireVersion: number | null = null,56    options: TopologyDescriptionOptions | null = null57  ) {58    options = options ?? {};59 60    this.type = topologyType ?? TopologyType.Unknown;61    this.servers = serverDescriptions ?? new Map();62    this.stale = false;63    this.compatible = true;64    this.heartbeatFrequencyMS = options.heartbeatFrequencyMS ?? 0;65    this.localThresholdMS = options.localThresholdMS ?? 15;66    this.setName = setName ?? null;67    this.maxElectionId = maxElectionId ?? null;68    this.maxSetVersion = maxSetVersion ?? null;69    this.commonWireVersion = commonWireVersion ?? 0;70 71    // determine server compatibility72    for (const serverDescription of this.servers.values()) {73      // Load balancer mode is always compatible.74      if (75        serverDescription.type === ServerType.Unknown ||76        serverDescription.type === ServerType.LoadBalancer77      ) {78        continue;79      }80 81      if (serverDescription.minWireVersion > MAX_SUPPORTED_WIRE_VERSION) {82        this.compatible = false;83        this.compatibilityError = `Server at ${serverDescription.address} requires wire version ${serverDescription.minWireVersion}, but this version of the driver only supports up to ${MAX_SUPPORTED_WIRE_VERSION} (MongoDB ${MAX_SUPPORTED_SERVER_VERSION})`;84      }85 86      if (serverDescription.maxWireVersion < MIN_SUPPORTED_WIRE_VERSION) {87        this.compatible = false;88        this.compatibilityError = `Server at ${serverDescription.address} reports wire version ${serverDescription.maxWireVersion}, but this version of the driver requires at least ${MIN_SUPPORTED_WIRE_VERSION} (MongoDB ${MIN_SUPPORTED_SERVER_VERSION}).`;89        break;90      }91    }92 93    // Whenever a client updates the TopologyDescription from a hello response, it MUST set94    // TopologyDescription.logicalSessionTimeoutMinutes to the smallest logicalSessionTimeoutMinutes95    // value among ServerDescriptions of all data-bearing server types. If any have a null96    // logicalSessionTimeoutMinutes, then TopologyDescription.logicalSessionTimeoutMinutes MUST be97    // set to null.98    this.logicalSessionTimeoutMinutes = null;99    for (const [, server] of this.servers) {100      if (server.isReadable) {101        if (server.logicalSessionTimeoutMinutes == null) {102          // If any of the servers have a null logicalSessionsTimeout, then the whole topology does103          this.logicalSessionTimeoutMinutes = null;104          break;105        }106 107        if (this.logicalSessionTimeoutMinutes == null) {108          // First server with a non null logicalSessionsTimeout109          this.logicalSessionTimeoutMinutes = server.logicalSessionTimeoutMinutes;110          continue;111        }112 113        // Always select the smaller of the:114        // current server logicalSessionsTimeout and the topologies logicalSessionsTimeout115        this.logicalSessionTimeoutMinutes = Math.min(116          this.logicalSessionTimeoutMinutes,117          server.logicalSessionTimeoutMinutes118        );119      }120    }121  }122 123  /**124   * Returns a new TopologyDescription based on the SrvPollingEvent125   * @internal126   */127  updateFromSrvPollingEvent(ev: SrvPollingEvent, srvMaxHosts = 0): TopologyDescription {128    /** The SRV addresses defines the set of addresses we should be using */129    const incomingHostnames = ev.hostnames();130    const currentHostnames = new Set(this.servers.keys());131 132    const hostnamesToAdd = new Set<string>(incomingHostnames);133    const hostnamesToRemove = new Set<string>();134    for (const hostname of currentHostnames) {135      // filter hostnamesToAdd (made from incomingHostnames) down to what is *not* present in currentHostnames136      hostnamesToAdd.delete(hostname);137      if (!incomingHostnames.has(hostname)) {138        // If the SRV Records no longer include this hostname139        // we have to stop using it140        hostnamesToRemove.add(hostname);141      }142    }143 144    if (hostnamesToAdd.size === 0 && hostnamesToRemove.size === 0) {145      // No new hosts to add and none to remove146      return this;147    }148 149    const serverDescriptions = new Map(this.servers);150    for (const removedHost of hostnamesToRemove) {151      serverDescriptions.delete(removedHost);152    }153 154    if (hostnamesToAdd.size > 0) {155      if (srvMaxHosts === 0) {156        // Add all!157        for (const hostToAdd of hostnamesToAdd) {158          serverDescriptions.set(hostToAdd, new ServerDescription(hostToAdd));159        }160      } else if (serverDescriptions.size < srvMaxHosts) {161        // Add only the amount needed to get us back to srvMaxHosts162        const selectedHosts = shuffle(hostnamesToAdd, srvMaxHosts - serverDescriptions.size);163        for (const selectedHostToAdd of selectedHosts) {164          serverDescriptions.set(selectedHostToAdd, new ServerDescription(selectedHostToAdd));165        }166      }167    }168 169    return new TopologyDescription(170      this.type,171      serverDescriptions,172      this.setName,173      this.maxSetVersion,174      this.maxElectionId,175      this.commonWireVersion,176      { heartbeatFrequencyMS: this.heartbeatFrequencyMS, localThresholdMS: this.localThresholdMS }177    );178  }179 180  /**181   * Returns a copy of this description updated with a given ServerDescription182   * @internal183   */184  update(serverDescription: ServerDescription): TopologyDescription {185    const address = serverDescription.address;186 187    // potentially mutated values188    let { type: topologyType, setName, maxSetVersion, maxElectionId, commonWireVersion } = this;189 190    const serverType = serverDescription.type;191    const serverDescriptions = new Map(this.servers);192 193    // update common wire version194    if (serverDescription.maxWireVersion !== 0) {195      if (commonWireVersion == null) {196        commonWireVersion = serverDescription.maxWireVersion;197      } else {198        commonWireVersion = Math.min(commonWireVersion, serverDescription.maxWireVersion);199      }200    }201 202    if (203      typeof serverDescription.setName === 'string' &&204      typeof setName === 'string' &&205      serverDescription.setName !== setName206    ) {207      if (topologyType === TopologyType.Single) {208        // "Single" Topology with setName mismatch is direct connection usage, mark unknown do not remove209        serverDescription = new ServerDescription(address);210      } else {211        serverDescriptions.delete(address);212      }213    }214 215    // update the actual server description216    serverDescriptions.set(address, serverDescription);217 218    if (topologyType === TopologyType.Single) {219      // once we are defined as single, that never changes220      return new TopologyDescription(221        TopologyType.Single,222        serverDescriptions,223        setName,224        maxSetVersion,225        maxElectionId,226        commonWireVersion,227        { heartbeatFrequencyMS: this.heartbeatFrequencyMS, localThresholdMS: this.localThresholdMS }228      );229    }230 231    if (topologyType === TopologyType.Unknown) {232      if (serverType === ServerType.Standalone && this.servers.size !== 1) {233        serverDescriptions.delete(address);234      } else {235        topologyType = topologyTypeForServerType(serverType);236      }237    }238 239    if (topologyType === TopologyType.Sharded) {240      if (!MONGOS_OR_UNKNOWN.has(serverType)) {241        serverDescriptions.delete(address);242      }243    }244 245    if (topologyType === TopologyType.ReplicaSetNoPrimary) {246      if (MONGOS_OR_STANDALONE.has(serverType)) {247        serverDescriptions.delete(address);248      }249 250      if (serverType === ServerType.RSPrimary) {251        const result = updateRsFromPrimary(252          serverDescriptions,253          serverDescription,254          setName,255          maxSetVersion,256          maxElectionId257        );258 259        topologyType = result[0];260        setName = result[1];261        maxSetVersion = result[2];262        maxElectionId = result[3];263      } else if (NON_PRIMARY_RS_MEMBERS.has(serverType)) {264        const result = updateRsNoPrimaryFromMember(serverDescriptions, serverDescription, setName);265        topologyType = result[0];266        setName = result[1];267      }268    }269 270    if (topologyType === TopologyType.ReplicaSetWithPrimary) {271      if (MONGOS_OR_STANDALONE.has(serverType)) {272        serverDescriptions.delete(address);273        topologyType = checkHasPrimary(serverDescriptions);274      } else if (serverType === ServerType.RSPrimary) {275        const result = updateRsFromPrimary(276          serverDescriptions,277          serverDescription,278          setName,279          maxSetVersion,280          maxElectionId281        );282 283        topologyType = result[0];284        setName = result[1];285        maxSetVersion = result[2];286        maxElectionId = result[3];287      } else if (NON_PRIMARY_RS_MEMBERS.has(serverType)) {288        topologyType = updateRsWithPrimaryFromMember(289          serverDescriptions,290          serverDescription,291          setName292        );293      } else {294        topologyType = checkHasPrimary(serverDescriptions);295      }296    }297 298    return new TopologyDescription(299      topologyType,300      serverDescriptions,301      setName,302      maxSetVersion,303      maxElectionId,304      commonWireVersion,305      { heartbeatFrequencyMS: this.heartbeatFrequencyMS, localThresholdMS: this.localThresholdMS }306    );307  }308 309  get error(): MongoError | null {310    const descriptionsWithError = Array.from(this.servers.values()).filter(311      (sd: ServerDescription) => sd.error312    );313 314    if (descriptionsWithError.length > 0) {315      return descriptionsWithError[0].error;316    }317 318    return null;319  }320 321  /**322   * Determines if the topology description has any known servers323   */324  get hasKnownServers(): boolean {325    return Array.from(this.servers.values()).some(326      (sd: ServerDescription) => sd.type !== ServerType.Unknown327    );328  }329 330  /**331   * Determines if this topology description has a data-bearing server available.332   */333  get hasDataBearingServers(): boolean {334    return Array.from(this.servers.values()).some((sd: ServerDescription) => sd.isDataBearing);335  }336 337  /**338   * Determines if the topology has a definition for the provided address339   * @internal340   */341  hasServer(address: string): boolean {342    return this.servers.has(address);343  }344 345  /**346   * Returns a JSON-serializable representation of the TopologyDescription.  This is primarily347   * intended for use with JSON.stringify().348   *349   * This method will not throw.350   */351  toJSON() {352    return EJSON.serialize(this);353  }354}355 356function topologyTypeForServerType(serverType: ServerType): TopologyType {357  switch (serverType) {358    case ServerType.Standalone:359      return TopologyType.Single;360    case ServerType.Mongos:361      return TopologyType.Sharded;362    case ServerType.RSPrimary:363      return TopologyType.ReplicaSetWithPrimary;364    case ServerType.RSOther:365    case ServerType.RSSecondary:366      return TopologyType.ReplicaSetNoPrimary;367    default:368      return TopologyType.Unknown;369  }370}371 372function updateRsFromPrimary(373  serverDescriptions: Map<string, ServerDescription>,374  serverDescription: ServerDescription,375  setName: string | null = null,376  maxSetVersion: number | null = null,377  maxElectionId: ObjectId | null = null378): [TopologyType, string | null, number | null, ObjectId | null] {379  const setVersionElectionIdMismatch = (380    serverDescription: ServerDescription,381    maxSetVersion: number | null,382    maxElectionId: ObjectId | null383  ) => {384    return (385      `primary marked stale due to electionId/setVersion mismatch:` +386      ` server setVersion: ${serverDescription.setVersion},` +387      ` server electionId: ${serverDescription.electionId},` +388      ` topology setVersion: ${maxSetVersion},` +389      ` topology electionId: ${maxElectionId}`390    );391  };392  setName = setName || serverDescription.setName;393  if (setName !== serverDescription.setName) {394    serverDescriptions.delete(serverDescription.address);395    return [checkHasPrimary(serverDescriptions), setName, maxSetVersion, maxElectionId];396  }397 398  if (serverDescription.maxWireVersion >= 17) {399    const electionIdComparison = compareObjectId(maxElectionId, serverDescription.electionId);400    const maxElectionIdIsEqual = electionIdComparison === 0;401    const maxElectionIdIsLess = electionIdComparison === -1;402    const maxSetVersionIsLessOrEqual =403      (maxSetVersion ?? -1) <= (serverDescription.setVersion ?? -1);404 405    if (maxElectionIdIsLess || (maxElectionIdIsEqual && maxSetVersionIsLessOrEqual)) {406      // The reported electionId was greater407      // or the electionId was equal and reported setVersion was greater408      // Always update both values, they are a tuple409      maxElectionId = serverDescription.electionId;410      maxSetVersion = serverDescription.setVersion;411    } else {412      // Stale primary413      // replace serverDescription with a default ServerDescription of type "Unknown"414      serverDescriptions.set(415        serverDescription.address,416        new ServerDescription(serverDescription.address, undefined, {417          error: new MongoStalePrimaryError(418            setVersionElectionIdMismatch(serverDescription, maxSetVersion, maxElectionId)419          )420        })421      );422 423      return [checkHasPrimary(serverDescriptions), setName, maxSetVersion, maxElectionId];424    }425  } else {426    const electionId = serverDescription.electionId ? serverDescription.electionId : null;427    if (serverDescription.setVersion && electionId) {428      if (maxSetVersion && maxElectionId) {429        if (430          maxSetVersion > serverDescription.setVersion ||431          compareObjectId(maxElectionId, electionId) > 0432        ) {433          // this primary is stale, we must remove it434          serverDescriptions.set(435            serverDescription.address,436            new ServerDescription(serverDescription.address, undefined, {437              error: new MongoStalePrimaryError(438                setVersionElectionIdMismatch(serverDescription, maxSetVersion, maxElectionId)439              )440            })441          );442 443          return [checkHasPrimary(serverDescriptions), setName, maxSetVersion, maxElectionId];444        }445      }446 447      maxElectionId = serverDescription.electionId;448    }449 450    if (451      serverDescription.setVersion != null &&452      (maxSetVersion == null || serverDescription.setVersion > maxSetVersion)453    ) {454      maxSetVersion = serverDescription.setVersion;455    }456  }457 458  // We've heard from the primary. Is it the same primary as before?459  for (const [address, server] of serverDescriptions) {460    if (server.type === ServerType.RSPrimary && server.address !== serverDescription.address) {461      // Reset old primary's type to Unknown.462      serverDescriptions.set(463        address,464        new ServerDescription(server.address, undefined, {465          error: new MongoStalePrimaryError(466            'primary marked stale due to discovery of newer primary'467          )468        })469      );470 471      // There can only be one primary472      break;473    }474  }475 476  // Discover new hosts from this primary's response.477  serverDescription.allHosts.forEach((address: string) => {478    if (!serverDescriptions.has(address)) {479      serverDescriptions.set(address, new ServerDescription(address));480    }481  });482 483  // Remove hosts not in the response.484  const currentAddresses = Array.from(serverDescriptions.keys());485  const responseAddresses = serverDescription.allHosts;486  currentAddresses487    .filter((addr: string) => responseAddresses.indexOf(addr) === -1)488    .forEach((address: string) => {489      serverDescriptions.delete(address);490    });491 492  return [checkHasPrimary(serverDescriptions), setName, maxSetVersion, maxElectionId];493}494 495function updateRsWithPrimaryFromMember(496  serverDescriptions: Map<string, ServerDescription>,497  serverDescription: ServerDescription,498  setName: string | null = null499): TopologyType {500  if (setName == null) {501    // TODO(NODE-3483): should be an appropriate runtime error502    throw new MongoRuntimeError('Argument "setName" is required if connected to a replica set');503  }504 505  if (506    setName !== serverDescription.setName ||507    (serverDescription.me && serverDescription.address !== serverDescription.me)508  ) {509    serverDescriptions.delete(serverDescription.address);510  }511 512  return checkHasPrimary(serverDescriptions);513}514 515function updateRsNoPrimaryFromMember(516  serverDescriptions: Map<string, ServerDescription>,517  serverDescription: ServerDescription,518  setName: string | null = null519): [TopologyType, string | null] {520  const topologyType = TopologyType.ReplicaSetNoPrimary;521  setName = setName ?? serverDescription.setName;522  if (setName !== serverDescription.setName) {523    serverDescriptions.delete(serverDescription.address);524    return [topologyType, setName];525  }526 527  serverDescription.allHosts.forEach((address: string) => {528    if (!serverDescriptions.has(address)) {529      serverDescriptions.set(address, new ServerDescription(address));530    }531  });532 533  if (serverDescription.me && serverDescription.address !== serverDescription.me) {534    serverDescriptions.delete(serverDescription.address);535  }536 537  return [topologyType, setName];538}539 540function checkHasPrimary(serverDescriptions: Map<string, ServerDescription>): TopologyType {541  for (const serverDescription of serverDescriptions.values()) {542    if (serverDescription.type === ServerType.RSPrimary) {543      return TopologyType.ReplicaSetWithPrimary;544    }545  }546 547  return TopologyType.ReplicaSetNoPrimary;548}549