{"id":"ebde22b441ce352a","repo":"redis/redis-py","slug":"the-previous-master-is-now-a-slave","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":"error","filePath":"redis/asyncio/sentinel.py","lineNumber":94,"sourceCode":"        disconnect_on_error: Optional[float] = True,\n        push_request: Optional[bool] = False,\n    ):\n        try:\n            return await 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                await 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 SentinelConnectionPool(ConnectionPool):\n    \"\"\"\n    Sentinel backed connection pool.\n\n    If ``check_connection`` flag is set to True, SentinelManagedConnection\n    sends a PING command right after establishing the connection.\n    \"\"\"\n\n    def __init__(self, service_name, sentinel_manager, **kwargs):\n        kwargs[\"connection_class\"] = kwargs.get(\n            \"connection_class\",","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/asyncio/sentinel.py#L76-L112","documentation":"Raised as a ConnectionError inside SentinelManagedConnection.read_response (redis/asyncio/sentinel.py:94) when a ReadOnlyError is received on a connection pool flagged is_master. A ReadOnlyError from a node we believe is master means a failover has occurred: our cached master was demoted to a replica. The connection is forcibly disconnected so the next connect() re-queries Sentinel for the new master.","triggerScenarios":"Issuing a write command through a master_for(...) client during/after a Sentinel failover; the pool still holds a connection to the demoted old master which now refuses writes with READONLY error.","commonSituations":"Active Sentinel failover in progress or just completed; brief window between master demotion and client rediscovery; repeated writes during topology churn.","solutions":["Retry the operation — the disconnect forces re-discovery of the new master on the next attempt.","Wrap the call with a retry loop (or use the client retry_on_error with ReadOnlyError/ConnectionError) sized to the expected failover window.","Ensure socket_timeout and retry config are tuned so rediscovery completes within your SLA."],"exampleFix":"# before\nawait master.set('k', 'v')  # may raise ConnectionError mid-failover\n# after\nfor _ in range(retries):\n    try:\n        return await master.set('k', 'v')\n    except ConnectionError:\n        await asyncio.sleep(backoff)","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"from redis.exceptions import ConnectionError, ReadOnlyError\nfor attempt in range(retries):\n    try:\n        return await master_client.execute_command(*args)\n    except ConnectionError:\n        await asyncio.sleep(backoff * (2 ** attempt))","preventionTips":["Enable retry_on_error=[ConnectionError, ReadOnlyError] on the master client.","Tune socket_timeout so rediscovery completes within your SLA."],"tags":["sentinel","failover","network"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}