PYTHON-5662 - Add support for server selection's deprioritized servers to all topologies (#2639)

This commit is contained in:
Noah Stapp 2025-12-16 12:21:45 -05:00 committed by GitHub
parent f813437154
commit 0cfba4994d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 1651 additions and 72 deletions

View File

@ -2825,7 +2825,7 @@ class _ClientConnectionRetryable(Generic[T]):
if self._last_error is None:
self._last_error = exc
if self._client.topology_description.topology_type == TOPOLOGY_TYPE.Sharded:
if self._server is not None:
self._deprioritized_servers.append(self._server)
def _is_not_eligible_for_retry(self) -> bool:

View File

@ -268,6 +268,7 @@ class Topology:
server_selection_timeout: Optional[float] = None,
address: Optional[_Address] = None,
operation_id: Optional[int] = None,
deprioritized_servers: Optional[list[Server]] = None,
) -> list[Server]:
"""Return a list of Servers matching selector, or time out.
@ -295,7 +296,12 @@ class Topology:
async with self._lock:
server_descriptions = await self._select_servers_loop(
selector, server_timeout, operation, operation_id, address
selector,
server_timeout,
operation,
operation_id,
address,
deprioritized_servers=deprioritized_servers,
)
return [
@ -309,6 +315,7 @@ class Topology:
operation: str,
operation_id: Optional[int],
address: Optional[_Address],
deprioritized_servers: Optional[list[Server]] = None,
) -> list[ServerDescription]:
"""select_servers() guts. Hold the lock when calling this."""
now = time.monotonic()
@ -327,7 +334,12 @@ class Topology:
)
server_descriptions = self._description.apply_selector(
selector, address, custom_selector=self._settings.server_selector
selector,
address,
custom_selector=self._settings.server_selector,
deprioritized_servers=[server.description for server in deprioritized_servers]
if deprioritized_servers
else None,
)
while not server_descriptions:
@ -388,9 +400,13 @@ class Topology:
operation_id: Optional[int] = None,
) -> Server:
servers = await self.select_servers(
selector, operation, server_selection_timeout, address, operation_id
selector,
operation,
server_selection_timeout,
address,
operation_id,
deprioritized_servers,
)
servers = _filter_servers(servers, deprioritized_servers)
if len(servers) == 1:
return servers[0]
server1, server2 = random.sample(servers, 2)
@ -1119,16 +1135,3 @@ def _is_stale_server_description(current_sd: ServerDescription, new_sd: ServerDe
if current_tv["processId"] != new_tv["processId"]:
return False
return current_tv["counter"] > new_tv["counter"]
def _filter_servers(
candidates: list[Server], deprioritized_servers: Optional[list[Server]] = None
) -> list[Server]:
"""Filter out deprioritized servers from a list of server candidates."""
if not deprioritized_servers:
return candidates
filtered = [server for server in candidates if server not in deprioritized_servers]
# If not possible to pick a prioritized server, return the original list
return filtered or candidates

View File

@ -34,16 +34,16 @@ class Selection:
@classmethod
def from_topology_description(cls, topology_description: TopologyDescription) -> Selection:
known_servers = topology_description.known_servers
candidate_servers = topology_description.candidate_servers
primary = None
for sd in known_servers:
for sd in candidate_servers:
if sd.server_type == SERVER_TYPE.RSPrimary:
primary = sd
break
return Selection(
topology_description,
topology_description.known_servers,
topology_description.candidate_servers,
topology_description.common_wire_version,
primary,
)

View File

@ -2815,7 +2815,7 @@ class _ClientConnectionRetryable(Generic[T]):
if self._last_error is None:
self._last_error = exc
if self._client.topology_description.topology_type == TOPOLOGY_TYPE.Sharded:
if self._server is not None:
self._deprioritized_servers.append(self._server)
def _is_not_eligible_for_retry(self) -> bool:

View File

@ -268,6 +268,7 @@ class Topology:
server_selection_timeout: Optional[float] = None,
address: Optional[_Address] = None,
operation_id: Optional[int] = None,
deprioritized_servers: Optional[list[Server]] = None,
) -> list[Server]:
"""Return a list of Servers matching selector, or time out.
@ -295,7 +296,12 @@ class Topology:
with self._lock:
server_descriptions = self._select_servers_loop(
selector, server_timeout, operation, operation_id, address
selector,
server_timeout,
operation,
operation_id,
address,
deprioritized_servers=deprioritized_servers,
)
return [
@ -309,6 +315,7 @@ class Topology:
operation: str,
operation_id: Optional[int],
address: Optional[_Address],
deprioritized_servers: Optional[list[Server]] = None,
) -> list[ServerDescription]:
"""select_servers() guts. Hold the lock when calling this."""
now = time.monotonic()
@ -327,7 +334,12 @@ class Topology:
)
server_descriptions = self._description.apply_selector(
selector, address, custom_selector=self._settings.server_selector
selector,
address,
custom_selector=self._settings.server_selector,
deprioritized_servers=[server.description for server in deprioritized_servers]
if deprioritized_servers
else None,
)
while not server_descriptions:
@ -388,9 +400,13 @@ class Topology:
operation_id: Optional[int] = None,
) -> Server:
servers = self.select_servers(
selector, operation, server_selection_timeout, address, operation_id
selector,
operation,
server_selection_timeout,
address,
operation_id,
deprioritized_servers,
)
servers = _filter_servers(servers, deprioritized_servers)
if len(servers) == 1:
return servers[0]
server1, server2 = random.sample(servers, 2)
@ -1117,16 +1133,3 @@ def _is_stale_server_description(current_sd: ServerDescription, new_sd: ServerDe
if current_tv["processId"] != new_tv["processId"]:
return False
return current_tv["counter"] > new_tv["counter"]
def _filter_servers(
candidates: list[Server], deprioritized_servers: Optional[list[Server]] = None
) -> list[Server]:
"""Filter out deprioritized servers from a list of server candidates."""
if not deprioritized_servers:
return candidates
filtered = [server for server in candidates if server not in deprioritized_servers]
# If not possible to pick a prioritized server, return the original list
return filtered or candidates

View File

@ -85,6 +85,7 @@ class TopologyDescription:
self._server_descriptions = server_descriptions
self._max_set_version = max_set_version
self._max_election_id = max_election_id
self._candidate_servers = list(self._server_descriptions.values())
# The heartbeat_frequency is used in staleness estimates.
self._topology_settings = topology_settings
@ -248,6 +249,11 @@ class TopologyDescription:
"""List of readable Servers."""
return [s for s in self._server_descriptions.values() if s.is_readable]
@property
def candidate_servers(self) -> list[ServerDescription]:
"""List of Servers excluding deprioritized servers."""
return self._candidate_servers
@property
def common_wire_version(self) -> Optional[int]:
"""Minimum of all servers' max wire versions, or None."""
@ -283,11 +289,27 @@ class TopologyDescription:
if (cast(float, s.round_trip_time) - fastest) <= threshold
]
def _filter_servers(
self, deprioritized_servers: Optional[list[ServerDescription]] = None
) -> None:
"""Filter out deprioritized servers from a list of server candidates."""
if not deprioritized_servers:
self._candidate_servers = self.known_servers
else:
deprioritized_addresses = {sd.address for sd in deprioritized_servers}
filtered = [
server
for server in self.known_servers
if server.address not in deprioritized_addresses
]
self._candidate_servers = filtered or self.known_servers
def apply_selector(
self,
selector: Any,
address: Optional[_Address] = None,
custom_selector: Optional[_ServerSelector] = None,
deprioritized_servers: Optional[list[ServerDescription]] = None,
) -> list[ServerDescription]:
"""List of servers matching the provided selector(s).
@ -324,14 +346,23 @@ class TopologyDescription:
description = self.server_descriptions().get(address)
return [description] if description and description.is_server_type_known else []
self._filter_servers(deprioritized_servers)
# Primary selection fast path.
if self.topology_type == TOPOLOGY_TYPE.ReplicaSetWithPrimary and type(selector) is Primary:
for sd in self._server_descriptions.values():
for sd in self._candidate_servers:
if sd.server_type == SERVER_TYPE.RSPrimary:
sds = [sd]
if custom_selector:
sds = custom_selector(sds)
return sds
# All primaries are deprioritized
if deprioritized_servers:
for sd in deprioritized_servers:
if sd.server_type == SERVER_TYPE.RSPrimary:
sds = [sd]
if custom_selector:
sds = custom_selector(sds)
return sds
# No primary found, return an empty list.
return []
@ -339,6 +370,11 @@ class TopologyDescription:
# Ignore read preference for sharded clusters.
if self.topology_type != TOPOLOGY_TYPE.Sharded:
selection = selector(selection)
# No suitable servers found, apply preference again but include deprioritized servers.
if not selection and deprioritized_servers:
self._filter_servers(None)
selection = Selection.from_topology_description(self)
selection = selector(selection)
# Apply custom selector followed by localThresholdMS.
if custom_selector is not None and selection:

View File

@ -35,7 +35,7 @@ from test.utils_shared import parse_read_preference
from bson import json_util
from pymongo.asynchronous.settings import TopologySettings
from pymongo.asynchronous.topology import Topology
from pymongo.common import HEARTBEAT_FREQUENCY
from pymongo.common import HEARTBEAT_FREQUENCY, clean_node
from pymongo.errors import AutoReconnect, ConfigurationError
from pymongo.operations import _Op
from pymongo.server_selectors import writable_server_selector
@ -95,12 +95,21 @@ def create_test(scenario_def):
# "Eligible servers" is defined in the server selection spec as
# the set of servers matching both the ReadPreference's mode
# and tag sets.
top_latency = await create_topology(scenario_def)
top_suitable = await create_topology(scenario_def, local_threshold_ms=1000000)
# "In latency window" is defined in the server selection
# spec as the subset of suitable_servers that falls within the
# allowable latency window.
top_suitable = await create_topology(scenario_def, local_threshold_ms=1000000)
top_latency = await create_topology(scenario_def)
top_suitable_deprioritized_servers = [
top_suitable.get_server_by_address(clean_node(server["address"]))
for server in scenario_def.get("deprioritized_servers", [])
]
top_latency_deprioritized_servers = [
top_latency.get_server_by_address(clean_node(server["address"]))
for server in scenario_def.get("deprioritized_servers", [])
]
# Create server selector.
if scenario_def.get("operation") == "write":
@ -120,21 +129,37 @@ def create_test(scenario_def):
# Select servers.
if not scenario_def.get("suitable_servers"):
with self.assertRaises(AutoReconnect):
await top_suitable.select_server(pref, _Op.TEST, server_selection_timeout=0)
await top_suitable.select_server(
pref,
_Op.TEST,
server_selection_timeout=0,
deprioritized_servers=top_suitable_deprioritized_servers,
)
return
if not scenario_def["in_latency_window"]:
with self.assertRaises(AutoReconnect):
await top_latency.select_server(pref, _Op.TEST, server_selection_timeout=0)
await top_latency.select_server(
pref,
_Op.TEST,
server_selection_timeout=0,
deprioritized_servers=top_latency_deprioritized_servers,
)
return
actual_suitable_s = await top_suitable.select_servers(
pref, _Op.TEST, server_selection_timeout=0
pref,
_Op.TEST,
server_selection_timeout=0,
deprioritized_servers=top_suitable_deprioritized_servers,
)
actual_latency_s = await top_latency.select_servers(
pref, _Op.TEST, server_selection_timeout=0
pref,
_Op.TEST,
server_selection_timeout=0,
deprioritized_servers=top_latency_deprioritized_servers,
)
expected_suitable_servers = {}

View File

@ -0,0 +1,62 @@
{
"topology_description": {
"type": "ReplicaSetNoPrimary",
"servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
]
},
"operation": "read",
"read_preference": {
"mode": "Nearest",
"tag_sets": [
{
"data_center": "nyc"
}
]
},
"deprioritized_servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
],
"suitable_servers": [
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
],
"in_latency_window": [
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
]
}

View File

@ -0,0 +1,39 @@
{
"topology_description": {
"type": "ReplicaSetNoPrimary",
"servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
]
},
"operation": "read",
"read_preference": {
"mode": "Primary"
},
"deprioritized_servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
],
"suitable_servers": [],
"in_latency_window": []
}

View File

@ -0,0 +1,62 @@
{
"topology_description": {
"type": "ReplicaSetNoPrimary",
"servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
]
},
"operation": "read",
"read_preference": {
"mode": "PrimaryPreferred",
"tag_sets": [
{
"data_center": "nyc"
}
]
},
"deprioritized_servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
],
"suitable_servers": [
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
],
"in_latency_window": [
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
]
}

View File

@ -0,0 +1,62 @@
{
"topology_description": {
"type": "ReplicaSetNoPrimary",
"servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
]
},
"operation": "read",
"read_preference": {
"mode": "Secondary",
"tag_sets": [
{
"data_center": "nyc"
}
]
},
"deprioritized_servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
],
"suitable_servers": [
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
],
"in_latency_window": [
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
]
}

View File

@ -0,0 +1,62 @@
{
"topology_description": {
"type": "ReplicaSetNoPrimary",
"servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
]
},
"operation": "read",
"read_preference": {
"mode": "SecondaryPreferred",
"tag_sets": [
{
"data_center": "nyc"
}
]
},
"deprioritized_servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
],
"suitable_servers": [
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
],
"in_latency_window": [
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
]
}

View File

@ -0,0 +1,84 @@
{
"topology_description": {
"type": "ReplicaSetWithPrimary",
"servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
]
},
"operation": "read",
"read_preference": {
"mode": "PrimaryPreferred",
"tag_sets": [
{}
]
},
"deprioritized_servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
],
"suitable_servers": [
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
],
"in_latency_window": [
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
]
}

View File

@ -0,0 +1,100 @@
{
"topology_description": {
"type": "ReplicaSetWithPrimary",
"servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
]
},
"operation": "read",
"read_preference": {
"mode": "SecondaryPreferred",
"tag_sets": [
{}
]
},
"deprioritized_servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
],
"suitable_servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
],
"in_latency_window": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
]
}

View File

@ -0,0 +1,78 @@
{
"topology_description": {
"type": "ReplicaSetWithPrimary",
"servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "a:27017",
"avg_rtt_ms": 26,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
]
},
"operation": "read",
"read_preference": {
"mode": "Nearest",
"tag_sets": [
{
"data_center": "nyc"
}
]
},
"deprioritized_servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
],
"suitable_servers": [
{
"address": "a:27017",
"avg_rtt_ms": 26,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
],
"in_latency_window": [
{
"address": "a:27017",
"avg_rtt_ms": 26,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
]
}

View File

@ -0,0 +1,65 @@
{
"topology_description": {
"type": "ReplicaSetWithPrimary",
"servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
]
},
"operation": "read",
"read_preference": {
"mode": "Primary"
},
"deprioritized_servers": [
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
],
"suitable_servers": [
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
],
"in_latency_window": [
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
]
}

View File

@ -0,0 +1,84 @@
{
"topology_description": {
"type": "ReplicaSetWithPrimary",
"servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
]
},
"operation": "read",
"read_preference": {
"mode": "PrimaryPreferred",
"tag_sets": [
{}
]
},
"deprioritized_servers": [
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
],
"suitable_servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
],
"in_latency_window": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
]
}

View File

@ -0,0 +1,86 @@
{
"topology_description": {
"type": "ReplicaSetWithPrimary",
"servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "a:27017",
"avg_rtt_ms": 26,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
]
},
"operation": "read",
"read_preference": {
"mode": "Secondary",
"tag_sets": [
{
"data_center": "nyc"
}
]
},
"deprioritized_servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
],
"suitable_servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
],
"in_latency_window": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
]
}

View File

@ -0,0 +1,78 @@
{
"topology_description": {
"type": "ReplicaSetWithPrimary",
"servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
]
},
"operation": "read",
"read_preference": {
"mode": "SecondaryPreferred",
"tag_sets": [
{
"data_center": "nyc"
}
]
},
"deprioritized_servers": [
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
],
"suitable_servers": [
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
],
"in_latency_window": [
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
]
}

View File

@ -0,0 +1,70 @@
{
"topology_description": {
"type": "ReplicaSetWithPrimary",
"servers": [
{
"address": "a:27017",
"avg_rtt_ms": 26,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "b:27017",
"avg_rtt_ms": 5,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
},
{
"address": "c:27017",
"avg_rtt_ms": 100,
"type": "RSSecondary",
"tags": {
"data_center": "nyc"
}
}
]
},
"operation": "write",
"read_preference": {
"mode": "SecondaryPreferred",
"tag_sets": [
{
"data_center": "nyc"
}
]
},
"deprioritized_servers": [
{
"address": "a:27017",
"avg_rtt_ms": 26,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
],
"suitable_servers": [
{
"address": "a:27017",
"avg_rtt_ms": 26,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
],
"in_latency_window": [
{
"address": "a:27017",
"avg_rtt_ms": 26,
"type": "RSPrimary",
"tags": {
"data_center": "nyc"
}
}
]
}

View File

@ -0,0 +1,47 @@
{
"topology_description": {
"type": "Sharded",
"servers": [
{
"address": "g:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
},
{
"address": "h:27017",
"avg_rtt_ms": 10,
"type": "Mongos"
}
]
},
"operation": "read",
"read_preference": {
"mode": "Nearest",
"tag_sets": [
{
"data_center": "nyc"
}
]
},
"deprioritized_servers": [
{
"address": "g:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
],
"suitable_servers": [
{
"address": "h:27017",
"avg_rtt_ms": 10,
"type": "Mongos"
}
],
"in_latency_window": [
{
"address": "h:27017",
"avg_rtt_ms": 10,
"type": "Mongos"
}
]
}

View File

@ -0,0 +1,42 @@
{
"topology_description": {
"type": "Sharded",
"servers": [
{
"address": "g:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
},
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
]
},
"operation": "read",
"read_preference": {
"mode": "Primary"
},
"deprioritized_servers": [
{
"address": "g:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
],
"suitable_servers": [
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
],
"in_latency_window": [
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
]
}

View File

@ -0,0 +1,47 @@
{
"topology_description": {
"type": "Sharded",
"servers": [
{
"address": "g:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
},
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
]
},
"operation": "read",
"read_preference": {
"mode": "PrimaryPreferred",
"tag_sets": [
{
"data_center": "nyc"
}
]
},
"deprioritized_servers": [
{
"address": "g:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
],
"suitable_servers": [
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
],
"in_latency_window": [
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
]
}

View File

@ -0,0 +1,47 @@
{
"topology_description": {
"type": "Sharded",
"servers": [
{
"address": "g:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
},
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
]
},
"operation": "read",
"read_preference": {
"mode": "Secondary",
"tag_sets": [
{
"data_center": "nyc"
}
]
},
"deprioritized_servers": [
{
"address": "g:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
],
"suitable_servers": [
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
],
"in_latency_window": [
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
]
}

View File

@ -0,0 +1,47 @@
{
"topology_description": {
"type": "Sharded",
"servers": [
{
"address": "g:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
},
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
]
},
"operation": "read",
"read_preference": {
"mode": "SecondaryPreferred",
"tag_sets": [
{
"data_center": "nyc"
}
]
},
"deprioritized_servers": [
{
"address": "g:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
],
"suitable_servers": [
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
],
"in_latency_window": [
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
]
}

View File

@ -0,0 +1,47 @@
{
"topology_description": {
"type": "Sharded",
"servers": [
{
"address": "g:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
},
{
"address": "h:27017",
"avg_rtt_ms": 10,
"type": "Mongos"
}
]
},
"operation": "write",
"read_preference": {
"mode": "Nearest",
"tag_sets": [
{
"data_center": "nyc"
}
]
},
"deprioritized_servers": [
{
"address": "g:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
],
"suitable_servers": [
{
"address": "h:27017",
"avg_rtt_ms": 10,
"type": "Mongos"
}
],
"in_latency_window": [
{
"address": "h:27017",
"avg_rtt_ms": 10,
"type": "Mongos"
}
]
}

View File

@ -0,0 +1,42 @@
{
"topology_description": {
"type": "Sharded",
"servers": [
{
"address": "g:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
},
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
]
},
"operation": "write",
"read_preference": {
"mode": "Primary"
},
"deprioritized_servers": [
{
"address": "g:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
],
"suitable_servers": [
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
],
"in_latency_window": [
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
]
}

View File

@ -0,0 +1,47 @@
{
"topology_description": {
"type": "Sharded",
"servers": [
{
"address": "g:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
},
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
]
},
"operation": "write",
"read_preference": {
"mode": "PrimaryPreferred",
"tag_sets": [
{
"data_center": "nyc"
}
]
},
"deprioritized_servers": [
{
"address": "g:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
],
"suitable_servers": [
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
],
"in_latency_window": [
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
]
}

View File

@ -0,0 +1,47 @@
{
"topology_description": {
"type": "Sharded",
"servers": [
{
"address": "g:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
},
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
]
},
"operation": "write",
"read_preference": {
"mode": "Secondary",
"tag_sets": [
{
"data_center": "nyc"
}
]
},
"deprioritized_servers": [
{
"address": "g:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
],
"suitable_servers": [
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
],
"in_latency_window": [
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
]
}

View File

@ -0,0 +1,47 @@
{
"topology_description": {
"type": "Sharded",
"servers": [
{
"address": "g:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
},
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
]
},
"operation": "write",
"read_preference": {
"mode": "SecondaryPreferred",
"tag_sets": [
{
"data_center": "nyc"
}
]
},
"deprioritized_servers": [
{
"address": "g:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
],
"suitable_servers": [
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
],
"in_latency_window": [
{
"address": "h:27017",
"avg_rtt_ms": 5,
"type": "Mongos"
}
]
}

View File

@ -0,0 +1,54 @@
{
"topology_description": {
"type": "Single",
"servers": [
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "Standalone",
"tags": {
"data_center": "dc"
}
}
]
},
"operation": "read",
"read_preference": {
"mode": "SecondaryPreferred",
"tag_sets": [
{
"data_center": "nyc"
}
]
},
"deprioritized_servers": [
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "Standalone",
"tags": {
"data_center": "dc"
}
}
],
"suitable_servers": [
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "Standalone",
"tags": {
"data_center": "dc"
}
}
],
"in_latency_window": [
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "Standalone",
"tags": {
"data_center": "dc"
}
}
]
}

View File

@ -0,0 +1,54 @@
{
"topology_description": {
"type": "Single",
"servers": [
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "Standalone",
"tags": {
"data_center": "dc"
}
}
]
},
"operation": "write",
"read_preference": {
"mode": "SecondaryPreferred",
"tag_sets": [
{
"data_center": "nyc"
}
]
},
"deprioritized_servers": [
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "Standalone",
"tags": {
"data_center": "dc"
}
}
],
"suitable_servers": [
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "Standalone",
"tags": {
"data_center": "dc"
}
}
],
"in_latency_window": [
{
"address": "a:27017",
"avg_rtt_ms": 5,
"type": "Standalone",
"tags": {
"data_center": "dc"
}
}
]
}

View File

@ -38,7 +38,7 @@ from pymongo.synchronous.monitor import Monitor
from pymongo.synchronous.pool import PoolOptions
from pymongo.synchronous.server import Server
from pymongo.synchronous.settings import TopologySettings
from pymongo.synchronous.topology import Topology, _ErrorContext, _filter_servers
from pymongo.synchronous.topology import Topology, _ErrorContext
from pymongo.topology_description import TOPOLOGY_TYPE
@ -733,23 +733,6 @@ class TestMultiServerTopology(TopologyTest):
self.assertNotIn(("a", 27017), t.description.server_descriptions())
self.assertEqual(t.description.topology_type_name, "Unknown")
def test_filtered_server_selection(self):
s1 = Server(ServerDescription(("localhost", 27017)), pool=object(), monitor=object()) # type: ignore[arg-type]
s2 = Server(ServerDescription(("localhost2", 27017)), pool=object(), monitor=object()) # type: ignore[arg-type]
servers = [s1, s2]
result = _filter_servers(servers, deprioritized_servers=[s2])
self.assertEqual(result, [s1])
result = _filter_servers(servers, deprioritized_servers=[s1, s2])
self.assertEqual(result, servers)
result = _filter_servers(servers, deprioritized_servers=[])
self.assertEqual(result, servers)
result = _filter_servers(servers)
self.assertEqual(result, servers)
def wait_for_primary(topology):
"""Wait for a Topology to discover a writable server.

View File

@ -33,7 +33,7 @@ from test.utils_selection_tests_shared import (
from test.utils_shared import parse_read_preference
from bson import json_util
from pymongo.common import HEARTBEAT_FREQUENCY
from pymongo.common import HEARTBEAT_FREQUENCY, clean_node
from pymongo.errors import AutoReconnect, ConfigurationError
from pymongo.operations import _Op
from pymongo.server_selectors import writable_server_selector
@ -95,12 +95,21 @@ def create_test(scenario_def):
# "Eligible servers" is defined in the server selection spec as
# the set of servers matching both the ReadPreference's mode
# and tag sets.
top_latency = create_topology(scenario_def)
top_suitable = create_topology(scenario_def, local_threshold_ms=1000000)
# "In latency window" is defined in the server selection
# spec as the subset of suitable_servers that falls within the
# allowable latency window.
top_suitable = create_topology(scenario_def, local_threshold_ms=1000000)
top_latency = create_topology(scenario_def)
top_suitable_deprioritized_servers = [
top_suitable.get_server_by_address(clean_node(server["address"]))
for server in scenario_def.get("deprioritized_servers", [])
]
top_latency_deprioritized_servers = [
top_latency.get_server_by_address(clean_node(server["address"]))
for server in scenario_def.get("deprioritized_servers", [])
]
# Create server selector.
if scenario_def.get("operation") == "write":
@ -120,18 +129,38 @@ def create_test(scenario_def):
# Select servers.
if not scenario_def.get("suitable_servers"):
with self.assertRaises(AutoReconnect):
top_suitable.select_server(pref, _Op.TEST, server_selection_timeout=0)
top_suitable.select_server(
pref,
_Op.TEST,
server_selection_timeout=0,
deprioritized_servers=top_suitable_deprioritized_servers,
)
return
if not scenario_def["in_latency_window"]:
with self.assertRaises(AutoReconnect):
top_latency.select_server(pref, _Op.TEST, server_selection_timeout=0)
top_latency.select_server(
pref,
_Op.TEST,
server_selection_timeout=0,
deprioritized_servers=top_latency_deprioritized_servers,
)
return
actual_suitable_s = top_suitable.select_servers(pref, _Op.TEST, server_selection_timeout=0)
actual_latency_s = top_latency.select_servers(pref, _Op.TEST, server_selection_timeout=0)
actual_suitable_s = top_suitable.select_servers(
pref,
_Op.TEST,
server_selection_timeout=0,
deprioritized_servers=top_suitable_deprioritized_servers,
)
actual_latency_s = top_latency.select_servers(
pref,
_Op.TEST,
server_selection_timeout=0,
deprioritized_servers=top_latency_deprioritized_servers,
)
expected_suitable_servers = {}
for server in scenario_def["suitable_servers"]: