redis/redis-py · warning · NoValidDatabaseException

Cannot set active database, database is unhealthy

Error message

Cannot set active database, database is unhealthy

What it means

Raised as NoValidDatabaseException by MultiDBClient.set_active_database() (client.py:163) when the supplied database IS a member of the list but its circuit breaker is not CLOSED after a fresh _check_db_health run. A non-CLOSED circuit (OPEN or HALF_OPEN) means the database is currently considered unhealthy, so it cannot be promoted to active.

Source

Thrown at redis/multidb/client.py:163

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

        self._bg_scheduler.run_coro_sync(self._check_db_health, database)

        if database.circuit.state == CBState.CLOSED:
            highest_weighted_db, _ = self._databases.get_top_n(1)[0]
            self.command_executor.active_database = (
                database,
                GeoFailoverReason.MANUAL,
            )
            return

        raise NoValidDatabaseException(
            "Cannot set active database, database is unhealthy"
        )

    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["retry"] = Retry(retries=0, backoff=NoBackoff())

        # Maintenance notifications are disabled by default in underlying clients,

View on GitHub (pinned to 6a6b581b48)

Solutions

  1. Wait for the database to recover (circuit returns to CLOSED via HALF_OPEN retry) before promoting it.
  2. Verify connectivity to that endpoint independently (PING) and review health-check thresholds.
  3. Tune the circuit-breaker / health-check settings so transient failures don't keep it OPEN.
  4. Catch NoValidDatabaseException and retry promotion after a short delay.

Example fix

# before
client.set_active_database(target)  # raises — target circuit OPEN

# after
import time
for _ in range(10):
    try:
        client.set_active_database(target)
        break
    except NoValidDatabaseException:
        time.sleep(2)
else:
    raise RuntimeError('target database never recovered')
Defensive patterns

Strategy: retry

Validate before calling

# Validate circuit state before promoting
from redis.multidb.circuit import State as CBState
if database.circuit.state != CBState.CLOSED:
    raise RuntimeError('target database circuit is not CLOSED; not healthy')
client.set_active_database(database)

Type guard

from redis.multidb.circuit import State as CBState

def is_healthy(database) -> bool:
    return database.circuit.state == CBState.CLOSED

Try / catch

from redis.multidb.exception import NoValidDatabaseException
import time

for _ in range(10):
    try:
        client.set_active_database(database)
        break
    except NoValidDatabaseException:
        time.sleep(2)
else:
    raise RuntimeError('target database never recovered')

Prevention

When it happens

Trigger: Calling set_active_database(db) where db's circuit opened due to recent failures; the on-demand health check at client.py:153 flipping the circuit to OPEN; the database being in HALF_OPEN recovery.

Common situations: Manually failing over to a database that has not recovered yet; network blips causing the circuit to open; aggressive circuit-breaker thresholds marking a slow-but-alive database unhealthy.

Related errors


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