redis/redis-py · warning · UnhealthyDatabaseException
Unhealthy database
Error message
Unhealthy database
What it means
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.
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.
Example fix
// before - default HEALTHY_ALL opens the circuit on any probe exception
config = MultiDbConfig(
databases_config=dbs,
health_check_policy=HealthCheckPolicies.HEALTHY_ALL,
)
// after - tolerate transient probe failures
from redis.asyncio.multidb.healthcheck import HealthCheckPolicies
config = MultiDbConfig(
databases_config=dbs,
health_check_policy=HealthCheckPolicies.HEALTHY_MAJORITY,
health_check_timeout=6.0,
) Defensive patterns
Strategy: try-catch
Validate before calling
from redis.multidb.circuit import State as CBState
def databases_look_healthy(client) -> bool:
return all(d.circuit.state == CBState.CLOSED for d, _ in client.get_databases())
# before a latency-sensitive op, optionally check circuit states;
# UnhealthyDatabaseException is raised internally and flips circuits OPEN,
# so the caller mainly observes it via the circuit state / failover path. Type guard
from redis.multidb.circuit import State as CBState
def circuit_is_closed(db) -> bool:
return db.circuit.state == CBState.CLOSED Try / catch
from redis.multidb.exception import UnhealthyDatabaseException
try:
await client._check_db_health(db)
except UnhealthyDatabaseException as e:
logger.warning(
"database %s unhealthy: %r", e.database, e.original_exception
)
# the circuit is now OPEN; failover will pick another DB Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Initial connection failed - no active database found
- Cannot set active database, database is unhealthy
- Cannot set active database, database is unhealthy
- Could not find a matching bdb
- Initial connection failed - no active database found
AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10).
Data as JSON: /api/errors/896e5da3297542d2.
Report an issue: GitHub.
Appendix: source
Thrown at redis/asyncio/multidb/healthcheck.py:156
# Create wrapper tasks that apply individual timeouts
async def execute_with_timeout(health_check: HealthCheck):
return await asyncio.wait_for(
self._execute(health_check, database),
timeout=health_check.health_check_timeout,
)
# Run all health checks concurrently and collect results/exceptions
results = await asyncio.gather(
*[execute_with_timeout(hc) for hc in health_checks],
return_exceptions=True,
)
# Check results - handle exceptions and failures
for result in results:
if isinstance(result, Exception):
# Any exception (including TimeoutError) makes the database unhealthy
raise UnhealthyDatabaseException("Unhealthy database", database, result)
elif not result:
# Health check returned False
return False
return True
async def get_client(self, database) -> AsyncRedisClientT:
"""
Get or create a health check client for the database.
Creates a single client instance per database that follows topology
changes automatically. For cluster databases, the client handles
node discovery and slot mapping internally.
"""
db_id = id(database)
client = self._clients.get(db_id)
if client is None:View on GitHub (pinned to 6a6b581b48)