{"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/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/asyncio/multidb/healthcheck.py#L138-L174","documentation":"Raised by `AbstractHealthCheckPolicy.execute()` (redis/asyncio/multidb/healthcheck.py:156) as UnhealthyDatabaseException when any individual health check (wrapped in `execute_with_timeout`) returns an Exception — including `asyncio.TimeoutError` if the probe exceeds its `health_check_timeout`. The exception carries `.database` and `.original_exception`, and the caller (`_check_databases_health`) uses it to force the database's circuit to OPEN.","triggerScenarios":"A health-check probe throwing any exception: PING returning a connection error, a probe exceeding `health_check_timeout` (default 3s), a LagAwareHealthCheck REST call failing, etc. Triggered during both initial and recurring health checks.","commonSituations":"Network blip making PING time out; Redis briefly unresponsive under load; LagAwareHealthCheck REST API unreachable; health_check_timeout too low for slow networks; TLS handshake failures during the probe.","solutions":["Increase `health_check_timeout` and/or `health_check_probes` in MultiDbConfig so transient slowness does not trip the check.","Inspect `exc.original_exception` (and the logger.debug at client.py:382-385) for the root cause.","Use a more lenient policy (`HealthCheckPolicies.HEALTHY_MAJORITY` or `HEALTHY_ANY`) so a single failing probe/check does not mark the DB unhealthy.","Fix the underlying connectivity/auth/TLS issue causing the probe exception."],"exampleFix":"# before\ncfg = MultiDbConfig(\n    databases_config=[db],\n    health_check_timeout=1,  # too tight\n)\n\n# after\nfrom redis.asyncio.multidb.healthcheck import HealthCheckPolicies\ncfg = MultiDbConfig(\n    databases_config=[db],\n    health_check_timeout=5,\n    health_check_probes=5,\n    health_check_policy=HealthCheckPolicies.HEALTHY_MAJORITY,\n)","handlingStrategy":"try-catch","validationCode":"from redis.multidb.circuit import State as CBState\n\nasync def db_likely_healthy(client, database) -> bool:\n    try:\n        await client._check_db_health(database)\n        return database.circuit.state == CBState.CLOSED\n    except Exception:\n        return False","typeGuard":"from redis.multidb.exception import UnhealthyDatabaseException\n\ndef is_unhealthy_db_exception(exc) -> bool:\n    return isinstance(exc, UnhealthyDatabaseException)","tryCatchPattern":"from redis.multidb.exception import UnhealthyDatabaseException\n\ntry:\n    await client.set('k','v')\nexcept UnhealthyDatabaseException as e:\n    # e.database is the failing DB, e.original_exception is the root cause\n    logger.warning('DB %s unhealthy: %r', e.database, e.original_exception)\n    # failover layer will pick another DB; or shed load","preventionTips":["Tune `health_check_timeout`, `health_check_probes` to your network latency profile.","Prefer HEALTHY_MAJORITY/HEALTHY_ANY policies when transient probe failures are expected.","Log `original_exception` to distinguish timeouts from auth/connectivity failures."],"tags":["multidb","health","probe","timeout","circuit-breaker"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}