{"record":{"id":"896e5da3297542d2","repo":"redis/redis-py","slug":"unhealthy-database","errorCode":null,"errorMessage":"Unhealthy database","messagePattern":"Unhealthy database","errorType":"exception","errorClass":"UnhealthyDatabaseException","httpStatus":null,"severity":"warning","filePath":"redis/asyncio/multidb/healthcheck.py","lineNumber":156,"sourceCode":"\n        # Create wrapper tasks that apply individual timeouts\n        async def execute_with_timeout(health_check: HealthCheck):\n            return await asyncio.wait_for(\n                self._execute(health_check, database),\n                timeout=health_check.health_check_timeout,\n            )\n\n        # Run all health checks concurrently and collect results/exceptions\n        results = await asyncio.gather(\n            *[execute_with_timeout(hc) for hc in health_checks],\n            return_exceptions=True,\n        )\n\n        # Check results - handle exceptions and failures\n        for result in results:\n            if isinstance(result, Exception):\n                # Any exception (including TimeoutError) makes the database unhealthy\n                raise UnhealthyDatabaseException(\"Unhealthy database\", database, result)\n            elif not result:\n                # Health check returned False\n                return False\n\n        return True\n\n    async def get_client(self, database) -> AsyncRedisClientT:\n        \"\"\"\n        Get or create a health check client for the database.\n\n        Creates a single client instance per database that follows topology\n        changes automatically. For cluster databases, the client handles\n        node discovery and slot mapping internally.\n        \"\"\"\n        db_id = id(database)\n        client = self._clients.get(db_id)\n\n        if client is None:","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/asyncio/multidb/healthcheck.py#L138-L174","documentation":"Raised as UnhealthyDatabaseException by AbstractHealthCheckPolicy.execute() (healthcheck.py:153-156) when one of the concurrent health-check probe tasks returned an Exception (including asyncio.TimeoutError from the per-check timeout) instead of a clean True/False. It carries the offending database and the original exception so callers can mark the circuit OPEN.","triggerScenarios":"A health check (PingHealthCheck/LagAwareHealthCheck/custom) raising — e.g. PING hitting ConnectionRefusedError/TimeoutError, LagAwareHealthCheck failing to reach the REST API, a custom HealthCheck.check_health raising. _check_db_health catches it and flips the circuit OPEN via _check_databases_health.","commonSituations":"Network partition or Redis process down causing PING to time out (exceeding health_check_timeout), LagAwareHealthCheck's REST call failing (auth, TLS, 9443 unreachable), DNS resolution failure mid-probe, or a flapping link causing intermittent probe exceptions.","solutions":["Inspect exception.original_exception and exception.database on the caught UnhealthyDatabaseException to localize the failure.","Increase health_check_timeout if probes are timing out on slow links, or health_check_probes for noisy networks (with a majority/any policy).","Fix the underlying connectivity (network/TLS/Redis process/REST API) for the affected database.","Switch health_check_policy to HEALTHY_MAJORITY or HEALTHY_ANY so a single probe exception does not open the circuit.","Confirm LagAwareHealthCheck has health_check_url set and the Redis Enterprise REST API credentials/TLS are correct."],"exampleFix":"// before - default HEALTHY_ALL opens the circuit on any probe exception\nconfig = MultiDbConfig(\n    databases_config=dbs,\n    health_check_policy=HealthCheckPolicies.HEALTHY_ALL,\n)\n\n// after - tolerate transient probe failures\nfrom redis.asyncio.multidb.healthcheck import HealthCheckPolicies\nconfig = MultiDbConfig(\n    databases_config=dbs,\n    health_check_policy=HealthCheckPolicies.HEALTHY_MAJORITY,\n    health_check_timeout=6.0,\n)","handlingStrategy":"try-catch","validationCode":"from redis.multidb.circuit import State as CBState\n\ndef databases_look_healthy(client) -> bool:\n    return all(d.circuit.state == CBState.CLOSED for d, _ in client.get_databases())\n\n# before a latency-sensitive op, optionally check circuit states;\n# UnhealthyDatabaseException is raised internally and flips circuits OPEN,\n# so the caller mainly observes it via the circuit state / failover path.","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 UnhealthyDatabaseException\n\ntry:\n    await client._check_db_health(db)\nexcept UnhealthyDatabaseException as e:\n    logger.warning(\n        \"database %s unhealthy: %r\", e.database, e.original_exception\n    )\n    # the circuit is now OPEN; failover will pick another DB","preventionTips":["Set health_check_timeout high enough for slow links; raise probes with a majority/any policy.","Investigate original_exception on UnhealthyDatabaseException to find root cause.","Use HEALTHY_MAJORITY or HEALTHY_ANY to tolerate transient probe exceptions.","For LagAwareHealthCheck, verify health_check_url and REST API auth/TLS.","Monitor circuit OPEN transitions as a leading indicator of DB trouble."],"tags":["multidb","health-check","circuit-breaker","network","async"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}