{"record":{"id":"8dc200161ce9349a","repo":"redis/redis-py","slug":"no-valid-database-available-for-communication-8dc200","errorCode":null,"errorMessage":"No valid database available for communication","messagePattern":"No valid database available for communication","errorType":"exception","errorClass":"NoValidDatabaseException","httpStatus":null,"severity":"critical","filePath":"redis/multidb/failover.py","lineNumber":66,"sourceCode":"    def execute(self) -> SyncDatabase:\n        \"\"\"Execute the failover strategy.\"\"\"\n        pass\n\n\nclass WeightBasedFailoverStrategy(FailoverStrategy):\n    \"\"\"\n    Failover strategy based on database weights.\n    \"\"\"\n\n    def __init__(self) -> None:\n        self._databases = WeightedList()\n\n    def database(self) -> SyncDatabase:\n        for database, _ in self._databases:\n            if database.circuit.state == CBState.CLOSED:\n                return database\n\n        raise NoValidDatabaseException(\"No valid database available for communication\")\n\n    def set_databases(self, databases: Databases) -> None:\n        self._databases = databases\n\n\nclass DefaultFailoverStrategyExecutor(FailoverStrategyExecutor):\n    \"\"\"\n    Executes given failover strategy.\n    \"\"\"\n\n    def __init__(\n        self,\n        strategy: FailoverStrategy,\n        failover_attempts: int = DEFAULT_FAILOVER_ATTEMPTS,\n        failover_delay: float = DEFAULT_FAILOVER_DELAY,\n    ):\n        self._strategy = strategy\n        self._failover_attempts = failover_attempts","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/multidb/failover.py#L48-L84","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\nresult = client.execute_command('GET', 'k')  # propagates NoValidDatabaseException\n\n# after\ntry:\n    result = client.execute_command('GET', 'k')\nexcept NoValidDatabaseException:\n    # all databases down — serve from local cache / fail closed\n    result = cache.get('k')\n    alert_on_total_outage()","handlingStrategy":"fallback","validationCode":"# Pre-check that at least one database circuit is CLOSED before issuing commands\nfrom redis.multidb.circuit import State as CBState\n\ndef has_routable(client) -> bool:\n    return any(db.circuit.state == CBState.CLOSED for db, _ in client.get_databases())\n\nif not has_routable(client):\n    raise RuntimeError('no database available; entering degraded mode')","typeGuard":"from redis.multidb.circuit import State as CBState\n\ndef any_circuit_closed(client) -> bool:\n    return any(db.circuit.state == CBState.CLOSED for db, _ in client.get_databases())","tryCatchPattern":"from redis.multidb.exception import NoValidDatabaseException, TemporaryUnavailableException\n\ntry:\n    result = client.execute_command('GET', 'k')\nexcept TemporaryUnavailableException:\n    # transient — retry with backoff\n    result = retry_with_backoff(lambda: client.execute_command('GET', 'k'))\nexcept NoValidDatabaseException:\n    # all databases exhausted — degrade gracefully\n    result = serve_from_cache_or_fail_closed('k')","preventionTips":["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."],"tags":["multidb","active-active","failover","circuit-breaker","outage"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}