redis/redis-py · warning · ConnectionError
The previous master is now a slave
Error message
The previous master is now a slave
What it means
Raised by SentinelManagedConnection.read_response() (redis/sentinel.py:91) when a command sent on a connection believed to be the master returns redis.exceptions.ReadOnlyError. Redis returns ReadOnlyError ('You can't write against a read only slave') when a node has been demoted to a replica, so receiving it on a master pool means a Sentinel failover happened under us: the node we held a master connection to is now a replica. redis-py disconnects the stale connection (forcing Sentinel re-discovery on the next connect()) and re-raises as ConnectionError so the caller can retry against the newly elected master.
Solutions
- Retry the operation: on the next connect() the pool re-queries Sentinel and binds to the new master.
- Wrap write commands in a bounded retry loop with backoff that catches ConnectionError.
- Shorten socket_timeout / connection recycling so stale master connections are dropped quickly after a failover.
- Check Sentinel health with `SENTINEL masters` / `SENTINEL ckquorum` if the error recurs — repeated occurrences indicate failover instability.
Example fix
# before
master = sentinel.master_for('mymaster')
master.set('k', 'v') # may raise ConnectionError: The previous master is now a slave
# after: bounded retry on failover-induced ConnectionError
import redis, time
for attempt in range(5):
try:
master.set('k', 'v'); break
except redis.ConnectionError:
time.sleep(0.1 * (2 ** attempt))
else:
raise Defensive patterns
Strategy: retry
Validate before calling
def master_address_stable(sentinel, service_name, prev=None) -> bool:
# returns True when Sentinel reports a master address and it is not
# in the middle of a failover; call before issuing a burst of writes
try:
addr = sentinel.discover_master(service_name)
except Exception:
return False
return prev is None or addr == prev Try / catch
import redis, time
def write_with_failover_retry(master_client, op, *args, retries=5, **kw):
last = None
for attempt in range(retries):
try:
return op(master_client, *args, **kw)
except redis.ConnectionError as e:
last = e
if 'previous master is now a slave' not in str(e).lower():
raise
time.sleep(0.1 * (2 ** attempt))
raise last
# usage:
# write_with_failover_retry(master, lambda c, k, v: c.set(k, v), 'k', 'v') Prevention
- Wrap Sentinel writes in a bounded retry loop that tolerates this ConnectionError.
- Keep socket_timeout and connection recycling short so stale master connections are dropped quickly after failover.
- Monitor Sentinel for repeated failovers (`SENTINEL masters`) — flapping indicates an underlying instability to fix.
When it happens
Trigger: Using a master_for(service_name) client from redis.sentinel.Sentinel, a write command (SET/DEL/INCR/etc.) is dispatched; meanwhile Sentinel has failed over and the old master is now a replica; the in-flight master connection receives ReadOnlyError from Redis; the `if self.connection_pool.is_master` branch at sentinel.py:84 fires, the connection is disconnected, and ConnectionError is raised.
Common situations: During or immediately after a Sentinel failover; long-lived / idle connections in the pool that are not recycled before the failover completes; aggressive socket_keepalive keeping stale master connections open; writes issued inside the window between failover start and pool reset.
Related errors
- No master found for
- The previous master is now a slave
- Failed to subscribe to cluster nodes
- FAILOVER is intentionally not implemented in the client.
- No slave found for
AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10).
Data as JSON: /api/errors/195a00d68a091eac.
Report an issue: GitHub.
Appendix: source
Thrown at redis/sentinel.py:91
disconnect_on_error: Optional[bool] = False,
push_request: Optional[bool] = False,
):
try:
return super().read_response(
disable_decoding=disable_decoding,
timeout=timeout,
disconnect_on_error=disconnect_on_error,
push_request=push_request,
)
except ReadOnlyError:
if self.connection_pool.is_master:
# When talking to a master, a ReadOnlyError when likely
# indicates that the previous master that we're still connected
# to has been demoted to a slave and there's a new master.
# calling disconnect will force the connection to re-query
# sentinel during the next connect() attempt.
self.disconnect()
raise ConnectionError("The previous master is now a slave")
raise
class SentinelManagedSSLConnection(SentinelManagedConnection, SSLConnection):
pass
class SentinelConnectionPoolProxy:
def __init__(
self,
connection_pool,
is_master,
check_connection,
service_name,
sentinel_manager,
):
self.connection_pool_ref = weakref.ref(connection_pool)
self.is_master = is_masterView on GitHub (pinned to 6a6b581b48)