{"id":"195a00d68a091eac","repo":"redis/redis-py","slug":"the-previous-master-is-now-a-slave-195a00","errorCode":null,"errorMessage":"The previous master is now a slave","messagePattern":"The previous master is now a slave","errorType":"exception","errorClass":"ConnectionError","httpStatus":null,"severity":"warning","filePath":"redis/sentinel.py","lineNumber":91,"sourceCode":"        disconnect_on_error: Optional[bool] = False,\n        push_request: Optional[bool] = False,\n    ):\n        try:\n            return super().read_response(\n                disable_decoding=disable_decoding,\n                timeout=timeout,\n                disconnect_on_error=disconnect_on_error,\n                push_request=push_request,\n            )\n        except ReadOnlyError:\n            if self.connection_pool.is_master:\n                # When talking to a master, a ReadOnlyError when likely\n                # indicates that the previous master that we're still connected\n                # to has been demoted to a slave and there's a new master.\n                # calling disconnect will force the connection to re-query\n                # sentinel during the next connect() attempt.\n                self.disconnect()\n                raise ConnectionError(\"The previous master is now a slave\")\n            raise\n\n\nclass SentinelManagedSSLConnection(SentinelManagedConnection, SSLConnection):\n    pass\n\n\nclass SentinelConnectionPoolProxy:\n    def __init__(\n        self,\n        connection_pool,\n        is_master,\n        check_connection,\n        service_name,\n        sentinel_manager,\n    ):\n        self.connection_pool_ref = weakref.ref(connection_pool)\n        self.is_master = is_master","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/sentinel.py#L73-L109","documentation":"Raised by SentinelManagedConnection.read_response (redis/sentinel.py:91). When a Sentinel-managed connection used for the master role receives a ReadOnlyError from Redis, it means a failover occurred and the instance the client treated as master has been demoted to a replica (replicas reject writes with READONLY). The library calls self.disconnect() and raises redis.exceptions.ConnectionError so the next operation forces Sentinel to re-discover the new master.","triggerScenarios":"A Sentinel-managed master connection (obtained via Sentinel.master_for(...)) performs a write/read-write command after a Sentinel failover has demoted the old master. The demoted node returns ReadOnlyError, which is caught in read_response; because connection_pool.is_master is True, the connection is torn down and this ConnectionError is raised.","commonSituations":"During or immediately after a Redis Sentinel failover; client holds stale pooled connections to the old master; failover completed but client's master_address cache has not yet been refreshed; network blip causing transient failover.","solutions":["Retry the operation: the next attempt triggers get_master_address() which re-queries Sentinel and connects to the new master.","Wrap write operations in a retry/backoff loop catching redis.exceptions.ConnectionError.","Ensure Sentinel quorum and sentinel_discover_* latency are healthy so re-discovery is fast.","Tune connection_pool kwargs (e.g. lower health_check_interval, max_connections) so stale connections are reaped sooner."],"exampleFix":"# before\nclient = sentinel.master_for('mymaster', socket_timeout=0.5)\nclient.set('k', 'v')  # may raise mid-failover\n# after\nfrom redis.exceptions import ConnectionError\nfor attempt in range(5):\n    try:\n        client.set('k', 'v')\n        break\n    except ConnectionError:\n        time.sleep(0.1 * (2 ** attempt))","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"import time\nfrom redis.exceptions import ConnectionError as RedisConnectionError\n\ndef write_with_failover_retry(client, *args, max_attempts=5, **kwargs):\n    last_err = None\n    for attempt in range(max_attempts):\n        try:\n            return client.set(*args, **kwargs)\n        except RedisConnectionError as e:\n            if 'previous master is now a slave' not in str(e).lower():\n                raise\n            last_err = e\n            time.sleep(0.1 * (2 ** attempt))\n    raise last_err","preventionTips":["Always wrap Sentinel master writes in retry logic with exponential backoff.","Keep socket_timeout and health_check_interval low enough to detect failover quickly.","Monitor Sentinel for failover events and alert on them.","Avoid holding long-lived idle master connections across failovers."],"tags":["sentinel","failover","replication","transient","topology"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}