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

  1. Catch NoValidDatabaseException (and the TemporaryUnavailableException that precedes it) and surface a degraded-mode response to the user.
  2. Investigate why every database circuit is OPEN — check connectivity, auth, and Redis process health on all endpoints.
  3. Tune circuit-breaker recovery (HALF_OPEN grace period, DEFAULT_GRACE_PERIOD) so databases get retried sooner.
  4. 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

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


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_attempts

View on GitHub (pinned to 6a6b581b48)