{
  "schema_version": 2,
  "id": "develop/clients/redis-py/failover",
  "title": "Client-side geographic failover",
  "url": "https://redis.io/docs/latest/develop/clients/redis-py/failover/",
  "summary": "Improve reliability using the failover features of redis-py.",
  "tags": [
    "docs",
    "develop",
    "stack",
    "oss",
    "rs",
    "rc",
    "oss",
    "kubernetes",
    "clients"
  ],
  "last_updated": "2026-08-27T15:55:18+01:00",
  "page_type": "content",
  "content_hash": "41dcdad06042365bd92075d27a6b85e246f0aaf90ed8853b14d3b6699ae5ef9d",
  "sections": [
    {
      "id": "overview",
      "title": "Overview",
      "role": "overview",
      "text": "redis-py supports [Client-side geographic failover](https://en.wikipedia.org/wiki/Failover)\nto improve the availability of connections to Redis databases. This page explains\nhow to configure redis-py for failover. For an overview of the concepts,\nsee the main [Client-side geographic failover](https://redis.io/docs/latest/develop/clients/failover) page."
    },
    {
      "id": "failover-configuration",
      "title": "Failover configuration",
      "role": "content",
      "text": "The example below shows a simple case with a list of two servers,\n`redis-east` and `redis-west`, where `redis-east` is the preferred\ntarget. If `redis-east` fails, redis-py should fail over to\n`redis-west`.\n\nSupply the weighted endpoints using a list of `DatabaseConfig` objects\n(see [Selecting a failover target](https://redis.io/docs/latest/develop/clients/failover#selecting-a-failover-target) for a full description of how\nthe weighted list is used).\nUse the `weight` option to order the endpoints, with the highest\nweight being tried first. Then, use the list to create a `MultiDbConfig` object,\nwhich you can pass to the `MultiDBClient` constructor to create the client.\n`MultiDBClient` implements the usual Redis commands using an internal\n`RedisClient` instance, but will also handle the connection management and failover transparently.\n\n[code example]"
    },
    {
      "id": "endpoint-configuration",
      "title": "Endpoint configuration",
      "role": "content",
      "text": "The `DatabaseConfig` class provides several options to configure each endpoint, as\ndescribed in the table below. Supply the configurations for the whole set of\nendpoints by passing a list of `DatabaseConfig` objects to the `MultiDbConfig`\nconstructor in the `databases_config` parameter.\n\n| Option | Description |\n| --- | --- |\n| `client_kwargs` | Keyword parameters to pass to the internal client constructor for this endpoint. Use it to specify the host, port, username, password, and other connection parameters (see [Connect to the server](https://redis.io/docs/latest/develop/clients/redis-py/connect) for more information). This is especially useful if you are using a custom client class (see [Client configuration](#client-configuration) below for more information). |\n| `from_url` | Redis URL to connect to this endpoint, as an alternative to passing the host and port in `client_kwargs`. |\n| `from_pool` | A `ConnectionPool` to supply the endpoint connection (see [Connect with a connection pool](https://redis.io/docs/latest/develop/clients/redis-py/connect#connect-with-a-connection-pool) for more information) |\n| `weight` | Priority of the endpoint, with higher values being tried first. Default is `1.0`. |\n| `grace_period` | Duration in seconds to keep an unhealthy endpoint disabled before attempting a failback. Default is `60` seconds. |\n| `health_check_url` | URL for health checks that use the database's REST API (see [`LagAwareHealthCheck`](#lag-aware-health-check) for more information). |"
    },
    {
      "id": "client-configuration",
      "title": "Client configuration",
      "role": "content",
      "text": "`MultiDbConfig` provides the `client_class` option to specify the class of the internal client to use for each endpoint. The default is the basic `redis.Redis` client, but\nyou could, for example, replace this with `redis.asyncio.client.Redis` for an asynchronous basic client, or with `redis.cluster.RedisCluster`/`redis.asyncio.cluster.RedisCluster` for a cluster client. Use the `client_kwargs` option of `DatabaseConfig` to supply any extra parameters required by the client class (see [Endpoint configuration](#endpoint-configuration) above for more information).\n\n[code example]"
    },
    {
      "id": "circuit-breaker-configuration",
      "title": "Circuit breaker configuration",
      "role": "content",
      "text": "`MultiDbConfig` gives you several options to configure the circuit breaker\n(see [Detecting connection problems](https://redis.io/docs/latest/develop/clients/failover#detecting-connection-problems) for more information on how the\ncircuit breaker works):\n\n| Option | Description |\n| --- | --- |\n| `failures_detection_window` | Duration in seconds to keep failures and successes in the sliding window. Default is `2` seconds. |\n| `min_num_failures` | Minimum number of failures that must occur to trigger a failover. Default is `1000`. |\n| `failure_rate_threshold` | Fraction of failed commands required to trigger a failover. Default is `0.1` (10%). |"
    },
    {
      "id": "retry-configuration",
      "title": "Retry configuration",
      "role": "content",
      "text": "`MultiDbConfig` provides the `command_retry` option to configure retries for failed commands. This follows the usual approach to configuring retries used with a standard\n`RedisClient` connection (see [Retries](https://redis.io/docs/latest/develop/clients/redis-py/produsage#retries) for more information).\n\n[code example]"
    },
    {
      "id": "failback-configuration",
      "title": "Failback configuration",
      "role": "content",
      "text": "The `MultiDbConfig` constructor provides the following option to configure the failback behavior:\n\n| Option | Description |\n| --- | --- |\n| `auto_fallback_interval` | Time interval between automatic failback attempts. Set this to `-1` to disable automatic failback. Default value is `30` seconds (which means failback is enabled by default). |"
    },
    {
      "id": "general-failover-configuration",
      "title": "General failover configuration",
      "role": "content",
      "text": "There are also a few other options you can pass to the `MultiDbConfig` constructor to control the failover behavior:\n\n| Option | Description |\n| --- | --- |\n| `failover_attempts` | Number of attempts to fail over to a new endpoint before giving up. Default is `10`. |\n| `failover_delay` | Time interval between successive failover attempts. Default is `12` seconds. |\n| `event_dispatcher` | `EventDispatcher` object to use for emitting events. Supply this to register custom event listeners (see [Failover callbacks](#failover-callbacks) below for more information). |"
    },
    {
      "id": "failover-callbacks",
      "title": "Failover callbacks",
      "role": "content",
      "text": "You may want to take some custom action when a failover occurs. For example, you could log a warning, increment a metric, or externally persist the cluster connection state.\n\nYou can implement a custom event listener using a class that implements `EventListenerInterface`. This includes a `listen()` method that is called when\nthe event occurs. You should specifically listen for the `ActiveDatabaseChanged`\nevent, which is emitted when a failover happens.\n\nThe example below shows how to implement a simple listener class and register an instance\nof it using an `EventDispatcher` object. You can then pass the `EventDispatcher` to the\n`MultiDbConfig` constructor to enable the custom behavior.\n\n[code example]"
    },
    {
      "id": "health-check-configuration",
      "title": "Health check configuration",
      "role": "content",
      "text": "Each health check consists of one or more separate \"probes\", each of which is a simple\ntest (such as a [`PING`](https://redis.io/docs/latest/commands/ping) command) to determine if the\ndatabase is available. The results of the separate probes are combined\nusing a configurable policy to determine if the database is healthy.\n\nNote that\nan initial health check is performed on each database when you create the client or when\nyou add a new database. This initial health check has a separate configuration from the\nperiodic health checks, so you can use a stricter policy here to ensure you don't add\na database that is already unhealthy.\n\n`MultiDbConfig` provides the following options to configure the health check behavior:\n\n| Option | Description |\n| --- | --- |\n| `health_check_interval` | Time interval between successive health checks (each of which may consist of multiple probes). Default is `5` seconds. |\n| `health_check_probes` | Number of separate probes performed during each health check. Default is `3`. |\n| `health_check_probes_delay` | Delay between probes during a health check. Default is `0.5` seconds. |\n| `health_check_timeout` | Timeout for the full health check operation (including all probes). |\n| `health_check_policy` | `HealthCheckPolicies` enum value to specify the policy for determining database health from the separate probes of a health check. The options are `HealthCheckPolicies.HEALTHY_ALL` (all probes must succeed), `HealthCheckPolicies.HEALTHY_ANY` (at least one probe must succeed), and `HealthCheckPolicies.HEALTHY_MAJORITY` (more than half the probes must succeed). The default policy is `HealthCheckPolicies.HEALTHY_MAJORITY`. |\n| `health_check` | Custom list of `HealthCheck` objects to specify how to perform each probe during a health check. This defaults to just the simple [`PingHealthCheck`](#pinghealthcheck-default). |\n| `initial_health_check_policy` | `InitialHealthCheck` enum value to specify the policy to use during the initial health check. The options are `InitialHealthCheck.ALL_AVAILABLE` (all probes must succeed), `InitialHealthCheck.ANY_AVAILABLE` (at least one probe must succeed), and `InitialHealthCheck.MAJORITY_AVAILABLE` (more than half the probes must succeed). The default policy is `InitialHealthCheck.ALL_AVAILABLE`. |\n\nThere are several strategies available for health checks that you can configure using the\n`MultiClusterClientConfig` builder. The sections below explain these strategies\nin more detail."
    },
    {
      "id": "pinghealthcheck-default",
      "title": "`PingHealthCheck` (default)",
      "role": "content",
      "text": "The default strategy, `PingHealthCheck`, periodically sends a Redis\n[`PING`](https://redis.io/docs/latest/commands/ping) command\nand checks that it gives the expected response. Any unexpected response\nor exception indicates an unhealthy server. Although `PingHealthCheck` is\nvery simple, it is a good basic approach for most Redis deployments."
    },
    {
      "id": "lag-aware-health-check",
      "title": "`LagAwareHealthCheck` (Redis Software only)",
      "role": "content",
      "text": "`LagAwareHealthCheck` is designed specifically for\nRedis Software [Active-Active](https://redis.io/docs/latest/operate/rs/databases/active-active)\ndeployments. It determines the health of the server by using the\n[REST API](https://redis.io/docs/latest/operate/rs/references/rest-api) to check the\nsynchronization lag between a specific database and the others in the Active-Active\nsetup. If the lag is within a specified tolerance, the server is considered healthy.\n\n`LagAwareHealthCheck` uses the `health_check_url` value for the endpoint\nto connect to the database's REST API, so you must specify this in\nthe `DatabaseConfig` for each endpoint:\n\n[code example]\n\nYou must also add a `LagAwareHealthCheck` instance to the `health_check` list in\nthe `MultiDbConfig` constructor:\n\n[code example]\n\nThe `LagAwareHealthCheck` constructor accepts the following options:\n\n| Option | Description |\n| --- | --- |\n| `rest_api_port` | Port number for Redis Software REST API (default is 9443). |\n| `lag_aware_tolerance` | Tolerable synchronization lag between databases in milliseconds (default is 100ms). |\n| `timeout` | REST API request timeout in seconds (default is 30 seconds). |\n| `auth_basic` | Tuple of (username, password) for basic authentication. |\n| `verify_tls` | Whether to verify TLS certificates (defaults to `True`). |\n| `ca_file` | Path to CA certificate file for TLS verification. |\n| `ca_path` | Path to CA certificates directory for TLS verification. |\n| `ca_data` | CA certificate data as string or bytes. |\n| `client_cert_file` | Path to client certificate file for mutual TLS. |\n| `client_key_file` | Path to client private key file for mutual TLS. |\n| `client_key_password` | Password for encrypted client private key |"
    },
    {
      "id": "custom-health-check-strategy",
      "title": "Custom health check strategy",
      "role": "content",
      "text": "You can supply your own custom health check strategy by\nderiving a new class from the `AbstractHealthCheck` class.\nFor example, you might use this to integrate with external monitoring tools or\nto implement checks that are specific to your application. Add an\ninstance of your custom class to the `health_check` list in\nthe `MultiDbConfig` constructor, as with [`LagAwareHealthCheck`](#lag-aware-health-check).\nNote that health checks are executed in an asyncio event loop, so you \nmust implement the `check_health()` method as an async method.\n\nThe example below\nshows a simple custom strategy that sends a Redis [`ECHO`](https://redis.io/docs/latest/commands/echo)\ncommand and checks for the expected response.\n\n[code example]"
    },
    {
      "id": "managing-databases-at-runtime",
      "title": "Managing databases at runtime",
      "role": "content",
      "text": "Although you will typically configure all databases during the\ninitial connection, you can also modify the configuration at runtime.\nYou can add and remove database endpoints, and update their weights:\n\n[code example]"
    },
    {
      "id": "manual-failback",
      "title": "Manual failback",
      "role": "content",
      "text": "By default, the failback mechanism runs health checks on all servers in the weighted list and selects the highest-weighted server that is healthy. However, you can also use the `set_active_database()` method of `MultiDBClient` to select which database to use manually:\n\n[code example]\n\nNote that `set_active_database()` is thread-safe.\n\nIf you decide to implement manual failback, you will need a way for external systems to trigger this method in your application. For example, if your application exposes a REST API, you might consider creating a REST endpoint to call `set_active_database()`."
    },
    {
      "id": "pubsub-and-re-subscription",
      "title": "Pub/Sub and re-subscription",
      "role": "content",
      "text": "`MultiDBClient` supports [Pub/Sub](https://redis.io/docs/latest/develop/pubsub)\nmessaging with automatic re-subscription to channels during failover.\nThis means you don't have to detect failovers and re-subscribe manually:\n\n- **Subscriber failover**: When the active database becomes unhealthy, the\n  subscriber automatically reconnects to the next available database and\n  re-subscribes to the same channels.\n- **Publisher failover**: The publisher switches to the next available\n  database and continues publishing to the same channels.\n\nFor reliable Pub/Sub delivery during failover, use `MultiDBClient` instances\nfor *both* publishers and subscribers. Create a Pub/Sub interface from the\nclient in the usual way using the `pubsub()` method, then subscribe to one or\nmore channels:\n\n[code example]\n\nRe-subscription happens transparently and is independent of any custom\nevent listeners you register (see [Failover callbacks](#failover-callbacks)).\n\n> [!NOTE]\n> Message loss can still occur if the failover events happen in\n> the reverse order, with the publisher failing over to the new database\n> before the subscriber. Messages published during this window may not reach\n> a subscriber that is still connected to the previous database."
    },
    {
      "id": "behavior-when-all-endpoints-are-unhealthy",
      "title": "Behavior when all endpoints are unhealthy",
      "role": "content",
      "text": "In the extreme case where no endpoint is healthy, a command will throw a `TemporaryUnavailableException`.\nThis indicates that the client is periodically checking to see if any endpoint becomes healthy again. The number of\ntimes it will keep checking is configured by the `failover_attempts` option in `MultiDbConfig` and\nthe delay between attempts is configured by the `failover_delay` option (see [General failover configuration](#general-failover-configuration)). With the default settings, `failover_attempts` * `failover_delay` \ngives a period of 120 seconds to find a healthy endpoint.\n\nYou can still keep retrying commands after a `TemporaryUnavailableException` is thrown (for example,\nyou could add this exception to the `supported_errors` list in your `Retry` configuration, as described\nin [Retries](https://redis.io/docs/latest/develop/clients/redis-py/produsage#retries)). However, if the client exhausts\nall the available failover attempts before any endpoint becomes healthy again, commands will throw a `NoValidDatabaseException`. The client won't recover automatically from this situation, so you\nshould handle it by reconnecting with the `MultiDBClient` constructor after a suitable delay (see\n[Failover configuration](#failover-configuration) for a connection example)."
    },
    {
      "id": "troubleshooting",
      "title": "Troubleshooting",
      "role": "errors",
      "text": "This section lists some common problems and their solutions."
    },
    {
      "id": "excessive-or-constant-health-check-failures",
      "title": "Excessive or constant health check failures",
      "role": "content",
      "text": "If all health checks fail, you should first rule out authentication\nproblems with the Redis server and also make sure there are no persistent\nnetwork connectivity problems.\n\nIf you are using [`PingHealthCheck`](#pinghealthcheck-default) or a\n[custom health check strategy](#custom-health-check-strategy),\ncheck that the `socket_timeout` is not too low for your network conditions\n(see [Timeouts](https://redis.io/docs/latest/develop/clients/redis-py/produsage#timeouts) for more information).\n\nFor\n[`LagAwareHealthCheck`](#lag-aware-health-check), check that the `health_check_url`\nis set correctly for each endpoint. Note that health checks might be taking longer to\nexecute than you anticipated, so make sure your `timeout` setting is not too low."
    },
    {
      "id": "slow-failback-after-recovery",
      "title": "Slow failback after recovery",
      "role": "content",
      "text": "If failback is too slow after a server recovers, you can try\nreducing the `health_check_interval` period and also reducing the `grace_period`\nbefore failback is attempted (see [Health check configuration](#health-check-configuration)\nfor more information about these options)."
    }
  ],
  "examples": [
    {
      "id": "failover-configuration-ex0",
      "language": "py",
      "code": "from redis.multidb.client import MultiDBClient\nfrom redis.multidb.config import MultiDbConfig, DatabaseConfig\n\ndb_configs = [\n    DatabaseConfig(\n        client_kwargs={\"host\": \"redis-east.example.com\", \"port\": \"14000\"},\n        weight=1.0\n    ),\n    DatabaseConfig(\n        client_kwargs={\"host\": \"redis-west.example.com\", \"port\": \"14000\"},\n        weight=0.5\n    ),\n]\n\ncfg = MultiDbConfig(databases_config=db_configs)\nclient = MultiDBClient(cfg)",
      "section_id": "failover-configuration"
    },
    {
      "id": "client-configuration-ex0",
      "language": "py",
      "code": "cfg = MultiDbConfig(\n    ...\n    client_class=redis.asyncio.client.Redis,\n    ...\n)",
      "section_id": "client-configuration"
    },
    {
      "id": "retry-configuration-ex0",
      "language": "py",
      "code": "cfg = MultiDbConfig(\n    ...\n    # Retry failed commands up to three times using exponential backoff\n    # with jitter between attempts.\n    command_retry=Retry(\n        retries=3,\n        backoff=ExponentialWithJitterBackoff(base=1, cap=10),\n    ),\n    ...\n)",
      "section_id": "retry-configuration"
    },
    {
      "id": "failover-callbacks-ex0",
      "language": "py",
      "code": "import logging\n\nfrom redis.multidb.client import MultiDBClient\nfrom redis.multidb.config import MultiDbConfig\n\nfrom redis.event import EventListenerInterface, EventDispatcher\nfrom redis.multidb.event import ActiveDatabaseChanged\n ...\n\nclass LogFailoverEventListener(EventListenerInterface):\n    def __init__(self, logger: logging.Logger):\n        self.logger = logger\n\n    def listen(self, event: ActiveDatabaseChanged):\n        self.logger.warning(\n            f\"Failover happened. Active database switched from {event.old_database} to {event.new_database}\"\n        )\n\nevent_dispatcher = EventDispatcher()\nlistener = LogFailoverEventListener(logging.getLogger(__name__))\n\n# Register custom listener\nevent_dispatcher.register_listeners(\n    {\n        ActiveDatabaseChanged: [listener],\n    }\n)\n\nconfig = MultiDbConfig(\n    event_dispatcher=event_dispatcher,\n    ...\n)\n\nclient = MultiDBClient(config)",
      "section_id": "failover-callbacks"
    },
    {
      "id": "lag-aware-health-check-ex0",
      "language": "py",
      "code": "db_configs = [\n    DatabaseConfig(\n        client_kwargs={\"host\": \"redis-east.example.com\", \"port\": \"14000\"},\n        weight=1.0,\n        health_check_url=\"https://health.redis-east.example.com\"\n    ),\n    DatabaseConfig(\n        client_kwargs={\"host\": \"redis-west.example.com\", \"port\": \"14000\"},\n        weight=0.5,\n        health_check_url=\"https://health.redis-west.example.com\"\n    ),\n]",
      "section_id": "lag-aware-health-check"
    },
    {
      "id": "lag-aware-health-check-ex1",
      "language": "py",
      "code": "cfg = MultiDbConfig(\n    databases_config=db_configs,\n    health_check=[LagAwareHealthCheck(\n        rest_api_port=9443,\n        lag_aware_tolerance=100,  # ms\n        verify_tls=True,\n        # auth_basic=(\"user\", \"pass\"),\n        # ca_file=\"/path/ca.pem\",\n        # client_cert_file=\"/path/cert.pem\",\n        # client_key_file=\"/path/key.pem\",\n    )],\n    ...\n)\n\nclient = MultiDBClient(cfg)",
      "section_id": "lag-aware-health-check"
    },
    {
      "id": "custom-health-check-strategy-ex0",
      "language": "py",
      "code": "from redis.asyncio.multidb.healthcheck import AbstractHealthCheck, AsyncRedisClientT\n\n    class EchoHealthCheck(AbstractHealthCheck):\n        \"\"\"\n        Health check based on ECHO command.\n        \"\"\"\n\n        async def check_health(self, database, hc_client: AsyncRedisClientT) -> bool:\n            await connection.send_command(\"ECHO\", \"healthcheck\")\n            response = await connection.read_response()\n            return response in (b\"healthcheck\", \"healthcheck\")\n\ncfg = MultiDbConfig(\n    ...\n    health_check=[EchoHealthCheck(retry=Retry(retries=3))],\n    ...\n)\n\nclient = MultiDBClient(cfg)",
      "section_id": "custom-health-check-strategy"
    },
    {
      "id": "managing-databases-at-runtime-ex0",
      "language": "py",
      "code": "from redis.multidb.client import MultiDBClient\nfrom redis.multidb.config import MultiDbConfig, DatabaseConfig\n\ncfg = MultiDbConfig(\n    databases_config = [\n        DatabaseConfig(\n            client_kwargs={\"host\": \"redis-east.example.com\", \"port\": \"14000\"},\n            weight=1.0\n        ),\n        DatabaseConfig(\n            client_kwargs={\"host\": \"redis-west.example.com\", \"port\": \"14000\"},\n            weight=0.5\n        ),\n    ]\n)\nclient = MultiDBClient(cfg)\n\n# Add a database programmatically. The `skip_initial_health_check`\n# parameter defaults to `True`, which avoids adding the database if it\n# is unhealthy, but you can override this by setting it to `False`\n# explicitly.\nother = DatabaseConfig(\n    client_kwargs={\"host\": \"redis-south.example.com\", \"port\": \"14000\"},\n    weight=0.5\n)\nclient.add_database(other, skip_initial_health_check=True)\n\n# Get a list of databases, sorted by weight.\ndatabases = client.get_databases()\n\n# Update the weights of each database whose weight is less than 1.\nfor db in databases:\n    if db.weight < 1.0:\n        client.update_database_weight(db, 0.9)\n\n# Remove a database from the failover set.\nclient.remove_database(databases[0])",
      "section_id": "managing-databases-at-runtime"
    },
    {
      "id": "manual-failback-ex0",
      "language": "py",
      "code": "# Manually set `other` as the active database.\nclient.set_active_database(other)",
      "section_id": "manual-failback"
    },
    {
      "id": "pubsub-and-re-subscription-ex0",
      "language": "py",
      "code": "pubsub = client.pubsub()\npubsub.subscribe(\"news\", \"alerts\")\n\n# If a failover happens here, the subscriptions are automatically\n# re-established on the new active database.\nmsg = pubsub.get_message(timeout=1.0)\nif msg:\n    print(msg)",
      "section_id": "pubsub-and-re-subscription"
    }
  ]
}
