redis/redis-py · critical · NoValidDatabaseException
No valid database available for communication
Error message
No valid database available for communication
What it means
Raised as NoValidDatabaseException by WeightBasedFailoverStrategy.database() (failover.py:66) when iterating the weighted database list finds no entry with a CLOSED circuit. This is the runtime failover path (distinct from initialization): when the active database fails and the executor asks the strategy for a replacement, every candidate is OPEN/HALF_OPEN, so there is nowhere to route the command.
Solutions
- Catch NoValidDatabaseException (and the TemporaryUnavailableException that precedes it) and surface a degraded-mode response to the user.
- Investigate why every database circuit is OPEN — check connectivity, auth, and Redis process health on all endpoints.
- Tune circuit-breaker recovery (HALF_OPEN grace period, DEFAULT_GRACE_PERIOD) so databases get retried sooner.
- Add more geographically diverse databases to the configuration to reduce correlated failures.
Example fix
# before
result = client.execute_command('GET', 'k') # propagates NoValidDatabaseException
# after
try:
result = client.execute_command('GET', 'k')
except NoValidDatabaseException:
# all databases down — serve from local cache / fail closed
result = cache.get('k')
alert_on_total_outage() Defensive patterns
Strategy: fallback
Validate before calling
# Pre-check that at least one database circuit is CLOSED before issuing commands
from redis.multidb.circuit import State as CBState
def has_routable(client) -> bool:
return any(db.circuit.state == CBState.CLOSED for db, _ in client.get_databases())
if not has_routable(client):
raise RuntimeError('no database available; entering degraded mode') Type guard
from redis.multidb.circuit import State as CBState
def any_circuit_closed(client) -> bool:
return any(db.circuit.state == CBState.CLOSED for db, _ in client.get_databases()) Try / catch
from redis.multidb.exception import NoValidDatabaseException, TemporaryUnavailableException
try:
result = client.execute_command('GET', 'k')
except TemporaryUnavailableException:
# transient — retry with backoff
result = retry_with_backoff(lambda: client.execute_command('GET', 'k'))
except NoValidDatabaseException:
# all databases exhausted — degrade gracefully
result = serve_from_cache_or_fail_closed('k') Prevention
- Catch both TemporaryUnavailableException (retryable) and NoValidDatabaseException (exhausted) at call sites.
- Keep at least one geographically independent database to avoid total outage.
- Tune circuit-breaker recovery (grace period) so circuits return to HALF_OPEN promptly.
- Surface a degraded-mode path (cache / fail-closed) when no database is routable.
When it happens
Trigger: A command is executed after the active database has failed and every other database's circuit is also OPEN; the DefaultFailoverStrategyExecutor retries within failover_attempts and, once exhausted, re-raises this NoValidDatabaseException (failover.py:114-116); a complete fleet outage during runtime.
Common situations: A region-wide outage taking all endpoints down simultaneously; cascading circuit opens after a network partition; overly sensitive circuit breakers opening in lockstep; all databases failed their recurring health checks.
Related errors
- Cannot set active database, database is unhealthy
- No database connections currently available. This is a…
- Cannot set active database, database is unhealthy
- Initial connection failed - no active database found
- No database connections currently available. This is a…
AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10).
Data as JSON: /api/errors/8dc200161ce9349a.
Report an issue: GitHub.
Appendix: source
Thrown at redis/multidb/failover.py:66
def execute(self) -> SyncDatabase:
"""Execute the failover strategy."""
pass
class WeightBasedFailoverStrategy(FailoverStrategy):
"""
Failover strategy based on database weights.
"""
def __init__(self) -> None:
self._databases = WeightedList()
def database(self) -> SyncDatabase:
for database, _ in self._databases:
if database.circuit.state == CBState.CLOSED:
return database
raise NoValidDatabaseException("No valid database available for communication")
def set_databases(self, databases: Databases) -> None:
self._databases = databases
class DefaultFailoverStrategyExecutor(FailoverStrategyExecutor):
"""
Executes given failover strategy.
"""
def __init__(
self,
strategy: FailoverStrategy,
failover_attempts: int = DEFAULT_FAILOVER_ATTEMPTS,
failover_delay: float = DEFAULT_FAILOVER_DELAY,
):
self._strategy = strategy
self._failover_attempts = failover_attemptsView on GitHub (pinned to 6a6b581b48)