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:61-66) when iterating the weighted database list yields no database with a CLOSED circuit — i.e. every database is currently OPEN/HALF_OPEN and unreachable. It is the strategy-level signal that there is no valid target to route to.

Solutions

  1. Catch NoValidDatabaseException at the command boundary and either degrade gracefully or surface a 503 to callers.
  2. Bring at least one endpoint back (network/Redis/lag) so its circuit returns to CLOSED via _check_db_health.
  3. Add more geographically diverse databases to databases_config so a single region outage cannot open every circuit.
  4. Tune circuit reset_timeout/grace_period and failover_delay so circuits recover and retry within your SLO.
  5. Verify there is no shared dependency (DNS, load balancer) whose failure opens all circuits at once.

Example fix

// before
resp = await client.execute_command("GET", "k")  # propagates NoValidDatabaseException

// after
from redis.multidb.exception import NoValidDatabaseException, TemporaryUnavailableException
try:
    resp = await client.execute_command("GET", "k")
except TemporaryUnavailableException:
    resp = await fallback_store.get("k")  # temporary - retry/degrade
except NoValidDatabaseException:
    raise ServiceUnavailable("all Redis databases are down")
Defensive patterns

Strategy: try-catch

Validate before calling

from redis.multidb.circuit import State as CBState

def has_closed_database(client) -> bool:
    return any(d.circuit.state == CBState.CLOSED for d, _ in client.get_databases())

# before issuing a command when you suspect an outage:
if not has_closed_database(client):
    raise ServiceUnavailable("all Redis circuits are open")

Type guard

from redis.multidb.circuit import State as CBState

def any_circuit_closed(client) -> bool:
    return any(d.circuit.state == CBState.CLOSED for d, _ in client.get_databases())

Try / catch

from redis.multidb.exception import NoValidDatabaseException

try:
    resp = await client.execute_command("GET", "k")
except NoValidDatabaseException:
    # no database can serve this request right now; degrade or fail hard
    raise ServiceUnavailable("all Redis databases are unavailable")

Prevention

When it happens

Trigger: Triggered during command execution when DefaultCommandExecutor._check_active_database() invokes the failover strategy (because the active DB's circuit opened, or the auto_fallback_interval elapsed) and WeightBasedFailoverStrategy.database() finds all circuits non-CLOSED. Also reached directly if someone calls strategy.database().

Common situations: A region-wide outage taking down every Active-Active endpoint simultaneously; cascading circuit opens after a network partition; all databases in HALF_OPEN awaiting probe recovery; misconfigured weights with all endpoints down.

Related errors


AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10). Data as JSON: /api/errors/23da74206c09261f. Report an issue: GitHub.

Appendix: source

Thrown at redis/asyncio/multidb/failover.py:66

    async def execute(self) -> AsyncDatabase:
        """Execute the failover strategy."""
        pass


class WeightBasedFailoverStrategy(AsyncFailoverStrategy):
    """
    Failover strategy based on database weights.
    """

    def __init__(self):
        self._databases = WeightedList()

    async def database(self) -> AsyncDatabase:
        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: AsyncFailoverStrategy,
        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)