redis/redis-py · critical · NoValidDatabaseException

No valid database available for communication

Error message

No valid database available for communication

What it means

Raised by `WeightBasedFailoverStrategy.database()` (redis/asyncio/multidb/failover.py:66) when iterating `self._databases` yields no database whose circuit breaker is in the CLOSED state. This is the inner strategy signal that failover cannot pick a target; the `DefaultFailoverStrategyExecutor` catches it and either retries or surfaces error 156.

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 da03cdc7e8)

Solutions

  1. Restore at least one Redis endpoint so its circuit recovers to CLOSED (it will be probed in HALF_OPEN after the grace period).
  2. Increase `grace_period` on the circuit breaker / DB config so circuits move to HALF_OPEN and get re-probed sooner if the backend recovered.
  3. Tune failure-detector thresholds (`min_num_failures`, `failure_rate_threshold`, `failures_detection_window`) so transient errors do not open every circuit.
  4. Add more databases (redundant regions) so a single-region outage cannot exhaust the list.

Example fix

# before
# all DBs unreachable -> every circuit OPEN -> NoValidDatabaseException on next command

# after
# 1. bring one endpoint back, then
# 2. tune failover to recover faster
from redis.multidb.circuit import DEFAULT_GRACE_PERIOD
cfg = MultiDbConfig(
    databases_config=[db_a, db_b],
)
for db_cfg in cfg.databases_config:
    db_cfg.grace_period = DEFAULT_GRACE_PERIOD  # shorten to retry HALF_OPEN sooner
Defensive patterns

Strategy: retry

Validate before calling

from redis.multidb.circuit import State as CBState

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

# do not trigger failover/command execution when any_closed(client) is False

Type guard

from redis.multidb.circuit import State as CBState

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

Try / catch

from redis.multidb.exception import NoValidDatabaseException
import asyncio

for _ in range(10):
    try:
        await client.set('k', 'v')
        break
    except NoValidDatabaseException:
        await asyncio.sleep(1)
else:
    raise

Prevention

When it happens

Trigger: The failover strategy being asked (during command execution via `_check_active_database`, or during `set_active_database` fallback) to pick a healthy DB when every database's circuit is OPEN or HALF_OPEN.

Common situations: Total outage of all Redis endpoints; all circuits tripped by the failure detector; the grace period before HALF_OPEN has not elapsed; failure_rate_threshold/min_num_failures too aggressive.

Related errors


AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04). Data as JSON: /data/errors/23da74206c09261f.json. Report an issue: GitHub.