{"id":"73bebfdeefeaf707","repo":"redis/redis-py","slug":"cannot-set-active-database-database-is-unhealthy","errorCode":null,"errorMessage":"Cannot set active database, database is unhealthy","messagePattern":"Cannot set active database, database is unhealthy","errorType":"exception","errorClass":"NoValidDatabaseException","httpStatus":null,"severity":"error","filePath":"redis/asyncio/multidb/client.py","lineNumber":175,"sourceCode":"\n        for existing_db, _ in self._databases:\n            if existing_db == database:\n                exists = True\n                break\n\n        if not exists:\n            raise ValueError(\"Given database is not a member of database list\")\n\n        await self._check_db_health(database)\n\n        if database.circuit.state == CBState.CLOSED:\n            highest_weighted_db, _ = self._databases.get_top_n(1)[0]\n            await self.command_executor.set_active_database(\n                database, GeoFailoverReason.MANUAL\n            )\n            return\n\n        raise NoValidDatabaseException(\n            \"Cannot set active database, database is unhealthy\"\n        )\n\n    async def add_database(\n        self, config: DatabaseConfig, skip_initial_health_check: bool = True\n    ):\n        \"\"\"\n        Adds a new database to the database list.\n\n        Args:\n            config: DatabaseConfig object that contains the database configuration.\n            skip_initial_health_check: If True, adds the database even if it is unhealthy.\n        \"\"\"\n        # The retry object is not used in the lower level clients, so we can safely remove it.\n        # We rely on command_retry in terms of global retries.\n        config.client_kwargs.update({\"retry\": Retry(retries=0, backoff=NoBackoff())})\n\n        if config.from_url:","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/asyncio/multidb/client.py#L157-L193","documentation":"Raised by `MultiDBClient.set_active_database()` (redis/asyncio/multidb/client.py:175) when the target database is a registered member but its circuit breaker is not in the CLOSED state after a fresh `_check_db_health()` call. Promoting an OPEN/HALF_OPEN database as active would route commands to a known-unhealthy endpoint, so the client refuses with NoValidDatabaseException.","triggerScenarios":"Calling `await client.set_active_database(db)` for a database whose latest health check failed (circuit OPEN), or which is in HALF_OPEN awaiting probe recovery. The health check at client.py:166 runs immediately before the circuit-state check at client.py:168.","commonSituations":"Manually failing over to a database that is still down; race where the DB goes unhealthy between the membership check and the health check; aggressive circuit-breaker thresholds marking a slow DB as OPEN.","solutions":["Wait for the target database's circuit to recover to CLOSED (monitor via health checks) and retry `set_active_database`.","Pick a different database from `get_databases()` whose circuit is CLOSED.","Tune the circuit breaker (`grace_period`) and health-check probes so transient slowness does not trip the circuit.","Verify the underlying Redis endpoint is actually reachable and responsive before attempting the switch."],"exampleFix":"# before\nawait client.set_active_database(target_db)  # NoValidDatabaseException\n\n# after\nfrom redis.multidb.circuit import State as CBState\nhealthy = [db for db, _ in client.get_databases() if db.circuit.state == CBState.CLOSED]\nif healthy:\n    await client.set_active_database(healthy[0])\nelse:\n    # wait for recovery or alert\n    raise RuntimeError('no healthy database to promote')","handlingStrategy":"validation","validationCode":"from redis.multidb.circuit import State as CBState\n\nasync def can_promote(client, database) -> bool:\n    await client._check_db_health(database)  # refresh circuit state\n    return database.circuit.state == CBState.CLOSED\n\n# gate set_active_database on this returning True","typeGuard":"from redis.multidb.circuit import State as CBState\n\ndef circuit_is_closed(database) -> bool:\n    return database.circuit.state == CBState.CLOSED","tryCatchPattern":"from redis.multidb.exception import NoValidDatabaseException\n\ntry:\n    await client.set_active_database(db)\nexcept NoValidDatabaseException as e:\n    if 'unhealthy' in str(e):\n        # wait for recovery, or pick another CLOSED db\n        ...\n    raise","preventionTips":["Check `database.circuit.state == CBState.CLOSED` right before promotion.","Drive manual failover only after confirming the target passes a fresh health check.","Tune circuit-breaker `grace_period` and health-check probes to avoid stuck OPEN states."],"tags":["multidb","health","failover","circuit-breaker"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}