redis/redis-py · error · NoValidDatabaseException
Cannot set active database, database is unhealthy
Error message
Cannot set active database, database is unhealthy
What it means
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.
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.
Example fix
// before
await client.set_active_database(db) # NoValidDatabaseException: unhealthy
// after
from redis.multidb.circuit import State as CBState
healthy = [d for d, _ in client.get_databases() if d.circuit.state == CBState.CLOSED]
if not healthy:
raise RuntimeError("no healthy database to promote")
await client.set_active_database(healthy[0]) Defensive patterns
Strategy: validation
Validate before calling
from redis.multidb.circuit import State as CBState
def is_promotable(client, db) -> bool:
return any(d is db and d.circuit.state == CBState.CLOSED for d, _ in client.get_databases())
if not is_promotable(client, target):
# wait for recovery or pick another candidate
... 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 NoValidDatabaseException
try:
await client.set_active_database(target)
except NoValidDatabaseException as e:
if "unhealthy" in str(e):
logger.warning("target %s unhealthy; deferring promotion", target)
# schedule a retry after the circuit reset_timeout
else:
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Cannot set active database, database is unhealthy
- Initial connection failed - no active database found
- No valid database available for communication
- Cluster client has no nodes - cannot create health check…
- Given database is not a member of database list
AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10).
Data as JSON: /api/errors/73bebfdeefeaf707.
Report an issue: GitHub.
Appendix: source
Thrown at redis/asyncio/multidb/client.py:175
for existing_db, _ in self._databases:
if existing_db == database:
exists = True
break
if not exists:
raise ValueError("Given database is not a member of database list")
await self._check_db_health(database)
if database.circuit.state == CBState.CLOSED:
highest_weighted_db, _ = self._databases.get_top_n(1)[0]
await self.command_executor.set_active_database(
database, GeoFailoverReason.MANUAL
)
return
raise NoValidDatabaseException(
"Cannot set active database, database is unhealthy"
)
async def add_database(
self, config: DatabaseConfig, skip_initial_health_check: bool = True
):
"""
Adds a new database to the database list.
Args:
config: DatabaseConfig object that contains the database configuration.
skip_initial_health_check: If True, adds the database even if it is unhealthy.
"""
# The retry object is not used in the lower level clients, so we can safely remove it.
# We rely on command_retry in terms of global retries.
config.client_kwargs.update({"retry": Retry(retries=0, backoff=NoBackoff())})
if config.from_url:View on GitHub (pinned to 6a6b581b48)