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

  1. Retry the operation: on the next connect() the pool re-queries Sentinel and binds to the new master.
  2. Wrap write commands in a bounded retry loop with backoff that catches ConnectionError.
  3. Shorten socket_timeout / connection recycling so stale master connections are dropped quickly after a failover.
  4. 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

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


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_master

View on GitHub (pinned to 6a6b581b48)