redis_replica_selector
Latency-aware Sentinel replica selection for read-only Redis traffic.
Why this exists
The cluster’s replicas are not interchangeable: as of 2026-07-11 the master
(10.10.0.3) and one replica (10.10.0.10) sit ~42ms from the app host while the
other replica (10.10.0.2) is ~300ms away. redis-py’s Sentinel.slave_for
rotates replicas randomly per NEW connection and pins each pooled connection to
whichever replica it landed on — so naive replica reads get stuck on the 300ms
node ~half the time, making them slower than just reading the master.
This module provides:
ReplicaSelector— a per-process singleton owning a background probe task that periodically timesPINGagainst the Sentinel-discovered master and replicas (EWMA-smoothed), then elects the lowest-RTT healthy replica, excluding any replica slower thanexclusion_factor× the master’s RTT. When the elected replica changes, idle pooled connections on registered pools are disconnected so they re-run node selection instead of staying pinned to the old choice.LatencyAwareSentinel— aSentinelsubclass whosefilter_slavesreturns only the elected replica (or an empty list, which triggers redis-py’s built-in fall-through to the master address insiderotate_slaves). Master discovery (master_for) is unaffected.
Consistency contract for anything routed through these read clients: reads may be seconds stale (replication lag is normally <1s). Locks, idempotency claims/markers, stream consumer-group operations, version counters and any read-your-own-write flow MUST stay on the master client.
- class redis_replica_selector.ReplicaSelector(service_name, connection_kwargs, *, probe_interval=30.0, exclusion_factor=3.0, probe_timeout=2.0)
Bases:
objectElects the nearest healthy replica via periodic RTT probes.
One instance per process is shared by every latency-aware client (see
get_shared_selector()). The probe task is started lazily from the firstfilter_slavescall that runs inside a live event loop.- Parameters:
- attach_sentinel(sentinel)
Give the selector a Sentinel to discover topology through.
- Return type:
- Parameters:
sentinel (redis.asyncio.sentinel.Sentinel)
- register_pool(pool)
Register a replica connection pool for eviction on re-election.
- ensure_probe_task()
Start the probe loop if an event loop is running and it isn’t yet.
- Return type:
- choose(alive)
Pick the elected replica from Sentinel’s alive list, or
None.Nonemeans “no suitable replica” — callers translate that to an empty filter result, and redis-py falls through to the master. Before the first probe completes there is no RTT data, so this returnsNone(reads go to master) rather than gambling on a potentially 7x-slower node.
- class redis_replica_selector.LatencyAwareSentinel(*args, selector, **kwargs)
Bases:
SentinelSentinel whose replica discovery returns only the elected replica.
filter_slavesis the single sync choke point redis-py funnels every replica-pool connection decision through; returning one element makes selection deterministic, and returning[]reuses redis-py’s built-in master fallback insiderotate_slaves.master_forbehavior is completely unchanged.- Parameters:
args (Any)
selector (ReplicaSelector)
kwargs (Any)
Return the process-wide
ReplicaSelector, creating it once.All latency-aware clients in a process share one selector so the RTT probe runs once per probe interval regardless of how many read clients exist.
- Return type:
- Parameters: