{"record":{"id":"4cd01e8efbb28c01","repo":"redis/redis-py","slug":"cannot-set-active-database-database-is-unhealthy-4cd01e","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":"warning","filePath":"redis/multidb/client.py","lineNumber":163,"sourceCode":"        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        self._bg_scheduler.run_coro_sync(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            self.command_executor.active_database = (\n                database,\n                GeoFailoverReason.MANUAL,\n            )\n            return\n\n        raise NoValidDatabaseException(\n            \"Cannot set active database, database is unhealthy\"\n        )\n\n    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[\"retry\"] = Retry(retries=0, backoff=NoBackoff())\n\n        # Maintenance notifications are disabled by default in underlying clients,","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/multidb/client.py#L145-L181","documentation":"Raised as NoValidDatabaseException by MultiDBClient.set_active_database() (client.py:163) when the supplied database IS a member of the list but its circuit breaker is not CLOSED after a fresh _check_db_health run. A non-CLOSED circuit (OPEN or HALF_OPEN) means the database is currently considered unhealthy, so it cannot be promoted to active.","triggerScenarios":"Calling set_active_database(db) where db's circuit opened due to recent failures; the on-demand health check at client.py:153 flipping the circuit to OPEN; the database being in HALF_OPEN recovery.","commonSituations":"Manually failing over to a database that has not recovered yet; network blips causing the circuit to open; aggressive circuit-breaker thresholds marking a slow-but-alive database unhealthy.","solutions":["Wait for the database to recover (circuit returns to CLOSED via HALF_OPEN retry) before promoting it.","Verify connectivity to that endpoint independently (PING) and review health-check thresholds.","Tune the circuit-breaker / health-check settings so transient failures don't keep it OPEN.","Catch NoValidDatabaseException and retry promotion after a short delay."],"exampleFix":"# before\nclient.set_active_database(target)  # raises — target circuit OPEN\n\n# after\nimport time\nfor _ in range(10):\n    try:\n        client.set_active_database(target)\n        break\n    except NoValidDatabaseException:\n        time.sleep(2)\nelse:\n    raise RuntimeError('target database never recovered')","handlingStrategy":"retry","validationCode":"# Validate circuit state before promoting\nfrom redis.multidb.circuit import State as CBState\nif database.circuit.state != CBState.CLOSED:\n    raise RuntimeError('target database circuit is not CLOSED; not healthy')\nclient.set_active_database(database)","typeGuard":"from redis.multidb.circuit import State as CBState\n\ndef is_healthy(database) -> bool:\n    return database.circuit.state == CBState.CLOSED","tryCatchPattern":"from redis.multidb.exception import NoValidDatabaseException\nimport time\n\nfor _ in range(10):\n    try:\n        client.set_active_database(database)\n        break\n    except NoValidDatabaseException:\n        time.sleep(2)\nelse:\n    raise RuntimeError('target database never recovered')","preventionTips":["Promote only databases whose circuit is CLOSED.","Tune circuit-breaker recovery so transients don't lock promotion out.","Verify endpoint health independently before failing over."],"tags":["multidb","active-active","circuit-breaker","health-check","failover"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}