{"record":{"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/6a6b581b48225afa0b76912d1028c6035baee932/redis/asyncio/multidb/client.py#L157-L193","documentation":"Raised as NoValidDatabaseException by set_active_database() when the requested Database exists in the list but its circuit breaker is not CLOSED (it is OPEN or HALF_OPEN) after a fresh _check_db_health. The client refuses to route traffic to a database that the health-check layer just confirmed is unhealthy.","triggerScenarios":"Calling await client.set_active_database(db) where db.circuit.state is OPEN/HALF_OPEN at call time — e.g. forcing a manual failover to a database that is currently failing PING, is mid-recovery, or whose LagAwareHealthCheck REST probe is returning errors. The health re-check at client.py:166 runs immediately before the circuit check.","commonSituations":"Operator-driven failover to a region/endpoint that is still recovering from an outage, lag spikes on an Active-Active replica exceeding lag_aware_tolerance, network blip that opened the circuit, or attempting to repromote a database before the circuit's reset_timeout/grace_period elapsed.","solutions":["Wait for the circuit to recover (reset_timeout/grace_period) or run await client._check_db_health(db) and re-check db.circuit.state == CBState.CLOSED before retrying.","Fix the underlying health issue on the target (network, Redis process, lag) then retry set_active_database.","If you must promote now, pick a different healthy database from get_databases() whose circuit is already CLOSED.","Increase health_check_timeout or relax the health-check policy if probes are flapping and falsely opening the circuit."],"exampleFix":"// before\nawait client.set_active_database(db)  # NoValidDatabaseException: unhealthy\n\n// after\nfrom redis.multidb.circuit import State as CBState\nhealthy = [d for d, _ in client.get_databases() if d.circuit.state == CBState.CLOSED]\nif not healthy:\n    raise RuntimeError(\"no healthy database to promote\")\nawait client.set_active_database(healthy[0])","handlingStrategy":"validation","validationCode":"from redis.multidb.circuit import State as CBState\n\ndef is_promotable(client, db) -> bool:\n    return any(d is db and d.circuit.state == CBState.CLOSED for d, _ in client.get_databases())\n\nif not is_promotable(client, target):\n    # wait for recovery or pick another candidate\n    ...","typeGuard":"from redis.multidb.circuit import State as CBState\n\ndef circuit_is_closed(db) -> bool:\n    return db.circuit.state == CBState.CLOSED","tryCatchPattern":"from redis.multidb.exception import NoValidDatabaseException\n\ntry:\n    await client.set_active_database(target)\nexcept NoValidDatabaseException as e:\n    if \"unhealthy\" in str(e):\n        logger.warning(\"target %s unhealthy; deferring promotion\", target)\n        # schedule a retry after the circuit reset_timeout\n    else:\n        raise","preventionTips":["Check db.circuit.state == CBState.CLOSED immediately before set_active_database.","Avoid manual failover during known outages; let the automatic strategy handle it.","Tune reset_timeout/grace_period so circuits recover within your operational window.","Prefer the highest-weight CLOSED database from get_databases() over a hard-coded target."],"tags":["multidb","failover","circuit-breaker","health-check","validation"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}