{
  "schema_version": 1,
  "id": "develop/clients/dotnet/failover",
  "title": "Client-side geographic failover",
  "url": "https://redis.io/docs/latest/develop/clients/dotnet/failover/",
  "summary": "Improve reliability using the failover features of StackExchange.Redis.",
  "tags": [
    "docs",
    "develop",
    "stack",
    "oss",
    "rs",
    "rc",
    "oss",
    "kubernetes",
    "clients"
  ],
  "last_updated": "2026-08-07T08:57:54-07:00",
  "page_type": "content",
  "content_hash": "cbf4935a218fd7a1df33be970889a880e384ed5d948639a87c9e126af34424a6",
  "sections": [
    {
      "id": "overview",
      "title": "Overview",
      "role": "overview",
      "text": "StackExchange.Redis 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 StackExchange.Redis 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": "Failover support is available in StackExchange.Redis v3.1.0 and later. The failover\ntypes live in the `StackExchange.Redis.Availability` namespace, so you should add the following\n`using` directives to your source file:\n\n[code example]\n\nThe failover feature is fully supported and intended for production use.\nHowever, because it is a large, new API surface, the types in the\n`StackExchange.Redis.Availability` namespace are marked with the `[Experimental]`\nattribute so that the library can reserve the right to adjust them without the usual\nbackwards-compatibility guarantees. (This marker is expected to be removed in a later\n3.1.x release.) As a result, the compiler reports the `SER007` diagnostic when you use\nthese types. Suppressing this diagnostic is the normal way to use the feature. To do so,\neither add the following to your `.csproj` file:\n\n[code example]\n\nor suppress it locally in your source file:\n\n[code example]\n\n\nThe 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, StackExchange.Redis should fail over to\n`redis-west`.\n\nA failover-capable connection is a *group* of endpoints, each of which could serve\nyour workload. Create the group using `ConnectionMultiplexer.ConnectGroupAsync()`, passing\nan array of `ConnectionGroupMember` instances instead of a single connection string,\nas shown in the example below:\n\n[code example]\n\n`ConnectGroupAsync()` returns a connection that you use\njust like a standard multiplexer, but it also handles the connection management and\nfailover transparently. The [`IDatabase`](https://redis.io/docs/latest/develop/clients/dotnet/connect)\nand `ISubscriber` instances obtained from the multiplexer also work with a group\nas they do with an individual endpoint."
    },
    {
      "id": "endpoint-configuration",
      "title": "Endpoint configuration",
      "role": "content",
      "text": "Each endpoint is represented by a `ConnectionGroupMember`, which you can create from a\nconnection string or from a\n[`ConfigurationOptions`](https://redis.io/docs/latest/develop/clients/dotnet/connect) instance.\nUse `ConfigurationOptions` when you need to provide credentials, TLS settings, or other\noptions for each endpoint individually:\n\n[code example]\n\nThe `ConnectionGroupMember` class provides the following properties to configure each\nendpoint:\n\n| Property | Default | Description |\n| :-- | :-- | :-- |\n| `Weight` | `0` | Priority of the endpoint, with higher values being tried first (see [Selecting a failover target](https://redis.io/docs/latest/develop/clients/failover#selecting-a-failover-target) for a full description of how the weighted list is used). |\n| `HealthCheck` | Group default | Per-member [health check](#health-check-configuration) override. |\n| `CircuitBreaker` | Group default | Per-member [circuit breaker](#circuit-breaker-configuration) override. |\n| `FailbackDelay` | Group default | Per-member [failback delay](#failback-configuration) override. |\n| `SkipInitialHealthCheck` | `false` | If `true`, skip the health check performed when the member is first added to the group. |\n\nNote that you can also adjust weights at runtime, for example in response to changing conditions:\n\n[code example]"
    },
    {
      "id": "group-configuration",
      "title": "Group configuration",
      "role": "content",
      "text": "Health checks, circuit breakers, and retries are each configured using\nimmutable *policy types* (`HealthCheck`, `CircuitBreaker`, and `RetryPolicy`).\nEach of the three policy types follows the same basic pattern:\n\n1. The policy type is immutable and safe to share between members.\n2. Each policy has a nested `Builder` that carries the mutable settings. A new\n   `Builder` starts from the default values, so you only set what you want to change.\n   A `Builder` converts implicitly to its policy type, so you can assign or pass it inline.\n   Alternatively, you can call the `Create()` method explicitly to create the policy.\n   `Create()` also validates the values, throwing\n   `ArgumentOutOfRangeException` or `ArgumentException` at the point of configuration if\n   a value is invalid.\n3. `MultiGroupOptions` (itself immutable, with its own `Builder`) holds the group-wide\n   defaults. `ConnectionGroupMember` contains the same properties as `MultiGroupOptions`\n   but allows you to override these shared values for each member.\n\nSupply the group-wide defaults by passing a `MultiGroupOptions` instance as the second\nargument to `ConnectGroupAsync()`:\n\n[code example]"
    },
    {
      "id": "circuit-breaker-configuration",
      "title": "Circuit breaker configuration",
      "role": "content",
      "text": "A circuit breaker passively monitors the traffic already flowing over a connection and\ncloses the connection when it detects that the connection has become unstable\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). Configure the circuit breaker for the whole group using the\n`CircuitBreaker` option of `MultiGroupOptions`:\n\n[code example]\n\nThe `CircuitBreaker.Builder` class provides the following properties:\n\n| Property | Default | Description |\n| :-- | :-- | :-- |\n| `FailureRateThreshold` | `10` | Percentage of failures within the window that trips the breaker. |\n| `MinimumNumberOfFailures` | `1000` | Minimum number of tracked failures in the window before the breaker can trip (this avoids acting on tiny samples). |\n| `MetricsWindowSize` | `2` | Length of rolling time window (in seconds) over which successes and failures are counted. |\n\nThe circuit breaker counts transient and connection-level errors (including timeouts) as\nfailures, but counts\napplication-level errors (such as a `WRONGTYPE` for a bad command) as successes\nbecause they don't indicate an unhealthy connection.\n\nYou can also configure a circuit breaker for a single connection (outside a group) using\nthe `ConfigurationOptions.CircuitBreaker` option. Use `CircuitBreaker.None` to disable\nthe circuit breaker."
    },
    {
      "id": "retry-configuration",
      "title": "Retry configuration",
      "role": "content",
      "text": "You can configure an `IDatabaseAsync` instance to retry commands that fail due\nto transient errors (such as temporary network delays) using the `WithRetry()` method:\n\n[code example]\n\nNote that you can enable retries only for `IDatabaseAsync`, not for the\nsynchronous equivalent `IDatabase`.\n\nIf you call `WithRetry()` without parameters, it will use the default retry\npolicy (see [Group configuration](#group-configuration) for details). However,\nyou can also pass a policy explicitly to override the defaults for an individual\ndatabase:\n\n[code example]\n\nThe `RetryPolicy.Builder` class provides the following properties:\n\n| Property | Default | Description |\n| :-- | :-- | :-- |\n| `MaxAttempts` | `3` | Maximum number of attempts (including the first) before giving up. |\n| `MaxAttemptsBeforeFailover` | `1` | Maximum number of attempts against the current member before a retry is allowed to move to a failover member (group connections only). |\n| `RetryDelay` | `1` | Delay (in seconds) between retries on the same server. |\n| `JitterMax` | `0.5` | Upper bound of the random delay (in seconds) added to each retry, to avoid stampedes. |\n| `FailoverDelay` | `5` | Maximum time (in seconds) to wait when a retry is expecting a failover. |\n| `MaxCommandRetryCategory` | `CommandRetryWriteLastWins` | The most \"dangerous\" command category that will be retried (see [Which operations are safe to retry?](#which-operations-are-safe-to-retry)) |\n| `MaxAttemptsOnWatchConflict` | `3` | Maximum number of attempts allowed for a watched transaction that keeps failing (see [Watch keys for changes](https://redis.io/docs/latest/develop/clients/dotnet/transpipe#watch-keys-for-changes) for more information). |\n\nThe retry mechanism classifies errors in the same way as the circuit breaker\n(see [Circuit breaker configuration](#circuit-breaker-configuration)). Only transient\nerrors are retried.\n\n#### Which operations are safe to retry?\n\nCommands are generally safe to retry if they are *idempotent* (that is to say, multiple invocations\nof the same command have the same result as a single invocation). This excludes\ncommands like [`INCR`](https://redis.io/docs/latest/commands/incr) that modify whatever value\nis currently stored in the database.\n\nEach command belongs to a *retry category* that describes how \"dangerous\" it is\nfrom the perspective of retrying. Categories are ordered from least to most dangerous,\nand each has its own `CommandFlags` value as described in the table below:\n\n| `CommandFlags` value | Meaning |\n|----------------------|---------|\n| `CommandRetryAlways` | Always safe to retry, regardless of connection/server state |\n| `CommandRetryConnection` | Connection-level or safe metadata (e.g. `CLIENT SETNAME`, `CONFIG GET`) |\n| `CommandRetryReadOnly` | Pure reads (e.g. `GET`) |\n| `CommandRetryWriteChecked` | Conditional writes (e.g. `SETNX`, `SET ... IFEQ`) |\n| `CommandRetryWriteLastWins` | Unconditional overwrite — last-writer-wins (e.g. `SET`) |\n| `CommandRetryWriteAccumulating` | Cumulative writes where a retry can double-apply (e.g. `INCR`, `LPUSH`) |\n| `CommandRetryServerAdmin` | Server administration (e.g. `CONFIG SET`) |\n| `CommandRetryNever` | Never retry |\n\nA policy only retries\ncommands at or below its `MaxCommandRetryCategory` level. For the built-in typed methods\n(such as `StringGet`, `StringSet`, and `HashSet`) `StackExchange.Redis` assigns the appropriate\ncategory automatically.\n\nArbitrary commands issued using `Execute()`/`ExecuteAsync()`, and Lua scripts run using\n`ScriptEvaluate()`/`ScriptEvaluateAsync()`, have side-effects that the library cannot\ninfer, so they are not retried by default. However, you can pass your own choice of\nretry category using the `flags` parameter:\n\n[code example]"
    },
    {
      "id": "failback-configuration",
      "title": "Failback configuration",
      "role": "content",
      "text": "Each member tracks two independent pieces of state:\n\n- `IsConnected`: the last observed connectivity status of the underlying connection.\n- `IsUnhealthy`: whether the member has been disabled by a failed health check\n  or a tripped circuit breaker.\n\nA member can be selected as active only when it is both connected and\nhealthy.\n\n`MultiGroupOptions.FailbackDelay` is the minimum interval over which a member must remain healthy\n(measured from its most recent failure) before it is automatically returned to rotation.\nThis helps to protect against *flapping*, where a member is only intermittently available\nbetween repeated failures.\n\n[code example]\n\n`FailbackDelay` defaults to a value of `TimeSpan.Zero`, which means the member comes back into\nrotation as soon as it passes a health check. If you use a value of `TimeSpan.MaxValue` then\nautomatic failback is effectively disabled. However, you can use the `ResetIsUnhealthy()` or\n`TryFailoverTo()` methods to enable a member manually (see [Manual failover](#manual-failover)\nfor more information)."
    },
    {
      "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\nmember is available. The results of the separate probes are combined using a configurable\npolicy to determine whether the member is healthy. When a member fails its health check,\nit is flagged as unhealthy and traffic is routed to other healthy members instead. When\nan unhealthy member recovers, traffic can be routed to it again, after the\n[failback delay](#failback-configuration) has elapsed.\n\nConfigure health checks for the whole group using the `HealthCheck` option of\n`MultiGroupOptions`:\n\n[code example]\n\nUse the `MultiGroupOptions.HealthCheckInterval` option to set the interval between health\nchecks for all members of the group. Set this to `TimeSpan.MaxValue` if you want to\ndisable periodic health checking.\n\nThe `HealthCheck.Builder` class provides the following properties to configure each probe:\n\n| Property | Default | Description |\n| :-- | :-- | :-- |\n| `ProbeCount` | `3` | Number of probe operations to perform per health check. |\n| `ProbeTimeout` | `3` | Maximum time allowed (in seconds) for an individual probe to complete. |\n| `ProbeInterval` | `500` | Delay (in milliseconds) between consecutive failed probes. |\n| `Probe` | `Ping` | The probe operation to execute (see [Probe types](#probe-types) below). |\n| `ProbePolicy` | `AllSuccess` | Policy for evaluating multiple probe results (see [Probe policies](#probe-policies) below). |"
    },
    {
      "id": "probe-types",
      "title": "Probe types",
      "role": "content",
      "text": "StackExchange.Redis provides the following built-in probe types:\n\n| Probe | Description |\n| :-- | :-- |\n| `HealthCheckProbe.Ping` (default) | Sends a [`PING`](https://redis.io/docs/latest/commands/ping) command. Lightweight, and recommended for most scenarios. |\n| `HealthCheckProbe.IsConnected` | Checks the socket connection status without sending any command. Even more lightweight than `Ping`, but only verifies the connection, not that Redis is responsive. |\n| `HealthCheckProbe.StringSet` | Writes a random value and reads it back to verify read/write capability. More comprehensive, but higher overhead than `Ping`. This probe automatically skips replica servers. |"
    },
    {
      "id": "probe-policies",
      "title": "Probe policies",
      "role": "content",
      "text": "When `ProbeCount` is greater than 1, the probe policy determines how the individual\nprobe results are combined to give an overall verdict. The available policies are:\n\n| Policy | Description |\n| :-- | :-- |\n| `HealthCheckProbePolicy.AnySuccess` | Healthy if *any* probe succeeds (most lenient). |\n| `HealthCheckProbePolicy.AllSuccess` (default) | Healthy only if *all* probes succeed (strictest). |\n| `HealthCheckProbePolicy.MajoritySuccess` | Healthy if a *majority* of probes succeed. |"
    },
    {
      "id": "custom-health-check-probes",
      "title": "Custom health check probes",
      "role": "content",
      "text": "You can supply your own health check probe by deriving a new class from\n`HealthCheckProbe`, or from `HealthCheckProbePolicy` for a custom evaluation policy. For\nexample, you might use this to integrate with external monitoring tools or to implement\nchecks that are specific to your application. See the\n[StackExchange.Redis failover documentation](https://stackexchange.github.io/StackExchange.Redis/Failover#advanced-customization)\nfor details and examples."
    },
    {
      "id": "managing-members-at-runtime",
      "title": "Managing members at runtime",
      "role": "content",
      "text": "Although you will typically configure all members during the initial connection, you can\nalso modify the group at runtime by casting the connection to `IConnectionGroup`. For\nexample, you can add a new datacenter before decommissioning an old one for a\nzero-downtime migration:\n\n[code example]\n\nEach `ConnectionGroupMember` also exposes its current status, which you can inspect at\nany time using the `GetMembers()` method of the connection:\n\n[code example]"
    },
    {
      "id": "manual-failover",
      "title": "Manual failover",
      "role": "content",
      "text": "By default, the group selects the active member automatically based on weight and\nlatency. However, you can also use the `TryFailoverTo()` method to select which member to\nuse manually, for example to route traffic away from a region during maintenance:\n\n[code example]\n\n`TryFailoverTo()` returns `false` if the target member is not connected or is not part of\nthe group. While an explicit failover is active, the chosen member is preferred for all\ntraffic and weight and latency are ignored. However, if the chosen member becomes\nunavailable, the group still falls back automatically to other connected members. Explicit\nfailovers are not persisted across application restarts."
    },
    {
      "id": "monitoring-failover-events",
      "title": "Monitoring failover events",
      "role": "content",
      "text": "You may want to take some custom action when a failover occurs. For example, you could log\na warning, increment a metric, or externally persist the connection state. Use the\n`ConnectionChanged` event to react when the active member changes:\n\n[code example]"
    },
    {
      "id": "pubsub-and-re-subscription",
      "title": "Pub/Sub and re-subscription",
      "role": "content",
      "text": "A connection group supports [Pub/Sub](https://redis.io/docs/latest/develop/pubsub) messaging with\nautomatic re-subscription to channels during failover, so you don't have to detect\nfailovers and re-subscribe manually. When you subscribe to a channel, the subscription is\nestablished against *all* members (for immediate pickup during a failover), and the library\nfilters received messages so that you only observe messages from the active member.\nPublishing occurs only to the active member.\n\nCreate an `ISubscriber` from the connection in the usual way using the `GetSubscriber()`\nmethod, then subscribe to one or more channels:\n\n[code example]"
    }
  ],
  "examples": [
    {
      "id": "failover-configuration-ex0",
      "language": "csharp",
      "code": "using StackExchange.Redis;\nusing StackExchange.Redis.Availability;",
      "section_id": "failover-configuration"
    },
    {
      "id": "failover-configuration-ex1",
      "language": "xml",
      "code": "<NoWarn>$(NoWarn);SER007</NoWarn>",
      "section_id": "failover-configuration"
    },
    {
      "id": "failover-configuration-ex2",
      "language": "csharp",
      "code": "#pragma warning disable SER007",
      "section_id": "failover-configuration"
    },
    {
      "id": "failover-configuration-ex3",
      "language": "csharp",
      "code": "// Define your Redis endpoints, with the highest weight being tried first.\nConnectionGroupMember[] members = [\n    new(\"redis-east.example.com:6379\", name: \"US East\") { Weight = 1.0 },\n    new(\"redis-west.example.com:6379\", name: \"US West\") { Weight = 0.5 }\n];\n\n// Connect to all members.\nawait using var conn = await ConnectionMultiplexer.ConnectGroupAsync(members);\n\n// Use the connection exactly as you would a normal multiplexer.\nIDatabase db = conn.GetDatabase();\nawait db.StringSetAsync(\"mykey\", \"myvalue\");\nRedisValue value = await db.StringGetAsync(\"mykey\");",
      "section_id": "failover-configuration"
    },
    {
      "id": "endpoint-configuration-ex0",
      "language": "csharp",
      "code": "var eastConfig = new ConfigurationOptions\n{\n    EndPoints = { \"redis-east-1.example.com:6379\", \"redis-east-2.example.com:6379\" },\n    Password = \"east-password\",\n    Ssl = true,\n};\n\nvar westConfig = new ConfigurationOptions\n{\n    EndPoints = { \"redis-west-1.example.com:6379\", \"redis-west-2.example.com:6379\" },\n    Password = \"west-password\",\n    Ssl = true,\n};\n\nConnectionGroupMember[] members = [\n    new(eastConfig, name: \"US East\"),\n    new(westConfig, name: \"US West\")\n];\n\nawait using var conn = await ConnectionMultiplexer.ConnectGroupAsync(members);",
      "section_id": "endpoint-configuration"
    },
    {
      "id": "endpoint-configuration-ex1",
      "language": "csharp",
      "code": "members[0].Weight = 1;   // Reduce preference for the local DC\nmembers[1].Weight = 10;  // Increase preference for the remote DC",
      "section_id": "endpoint-configuration"
    },
    {
      "id": "group-configuration-ex0",
      "language": "csharp",
      "code": "HealthCheck healthCheck = new HealthCheck.Builder { ProbeCount = 5 };\nCircuitBreaker breaker = new CircuitBreaker.Builder { FailureRateThreshold = 25 };\nRetryPolicy retry = new RetryPolicy.Builder { MaxAttempts = 5 };\n\nMultiGroupOptions options = new MultiGroupOptions.Builder\n{\n    HealthCheck = healthCheck,\n    CircuitBreaker = breaker,\n    RetryPolicy = retry,\n    HealthCheckInterval = TimeSpan.FromSeconds(2),\n};\n\nawait using var conn = await ConnectionMultiplexer.ConnectGroupAsync(members, options);",
      "section_id": "group-configuration"
    },
    {
      "id": "circuit-breaker-configuration-ex0",
      "language": "csharp",
      "code": "MultiGroupOptions options = new MultiGroupOptions.Builder\n{\n    CircuitBreaker = new CircuitBreaker.Builder\n    {\n        FailureRateThreshold = 25,                   // Trip above 25% failures.\n        MinimumNumberOfFailures = 100,               // ...but only after 100 failures in the window.\n        MetricsWindowSize = TimeSpan.FromSeconds(5), // Rolling window to measure over.\n    }\n};\n\nawait using var conn = await ConnectionMultiplexer.ConnectGroupAsync(members, options);",
      "section_id": "circuit-breaker-configuration"
    },
    {
      "id": "retry-configuration-ex0",
      "language": "csharp",
      "code": "await using var conn = await ConnectionMultiplexer.ConnectGroupAsync(members);\n\n// Wrap the database once, then reuse the wrapper like any other IDatabaseAsync.\n// The parameterless overload uses the policy configured on the connection.\nIDatabaseAsync db = conn.GetDatabase().WithRetry();\n\n// A transient fault is retried automatically. If the group fails over in the\n// meantime, the retry lands on the new active member.\nRedisValue value = await db.StringGetAsync(\"mykey\");",
      "section_id": "retry-configuration"
    },
    {
      "id": "retry-configuration-ex1",
      "language": "csharp",
      "code": "RetryPolicy policy = new RetryPolicy.Builder\n{\n    MaxAttempts = 5,\n    RetryDelay = TimeSpan.FromMilliseconds(200),\n    JitterMax = TimeSpan.FromMilliseconds(100),\n};\nIDatabaseAsync db = conn.GetDatabase().WithRetry(policy);",
      "section_id": "retry-configuration"
    },
    {
      "id": "retry-configuration-ex2",
      "language": "csharp",
      "code": "// a Lua script that only reads: opt into retries\nvar value = await db.ScriptEvaluateAsync(\n    \"return redis.call('GET', KEYS[1])\",\n    keys: [key],\n    flags: CommandFlags.CommandRetryReadOnly\n);",
      "section_id": "retry-configuration"
    },
    {
      "id": "failback-configuration-ex0",
      "language": "csharp",
      "code": "MultiGroupOptions options = new MultiGroupOptions.Builder\n{\n    FailbackDelay = TimeSpan.FromMinutes(2), // Must be healthy for 2 minutes after its last failure.\n};",
      "section_id": "failback-configuration"
    },
    {
      "id": "health-check-configuration-ex0",
      "language": "csharp",
      "code": "HealthCheck healthCheck = new HealthCheck.Builder\n{\n    ProbeCount = 3,                                 // Maximum probe attempts per check.\n    ProbeTimeout = TimeSpan.FromSeconds(3),         // Timeout for each probe attempt.\n    ProbeInterval = TimeSpan.FromMilliseconds(500), // Delay between failed probes.\n    Probe = HealthCheckProbe.Ping,                  // Which probe type to use.\n    ProbePolicy = HealthCheckProbePolicy.AllSuccess // How to evaluate the probe results.\n};\n\nMultiGroupOptions options = new MultiGroupOptions.Builder\n{\n    HealthCheck = healthCheck,\n    HealthCheckInterval = TimeSpan.FromSeconds(5),  // How often checks run (a group-level concern).\n};\n\nawait using var conn = await ConnectionMultiplexer.ConnectGroupAsync(members, options);",
      "section_id": "health-check-configuration"
    },
    {
      "id": "managing-members-at-runtime-ex0",
      "language": "csharp",
      "code": "var group = (IConnectionGroup)conn;\n\n// Add a new member at runtime.\nvar newMember = new ConnectionGroupMember(\"new-dc.example.com:6379\", name: \"New Datacenter\") { Weight = 5 };\nawait group.AddAsync(newMember);\n\n// Remove a member.\ngroup.Remove(newMember);",
      "section_id": "managing-members-at-runtime"
    },
    {
      "id": "managing-members-at-runtime-ex1",
      "language": "csharp",
      "code": "foreach (ConnectionGroupMember member in conn.GetMembers())\n{\n    Console.WriteLine($\"{member.Name}: Connected={member.IsConnected}, \" +\n                      $\"Unhealthy={member.IsUnhealthy}, Weight={member.Weight}, Latency={member.Latency}\");\n}",
      "section_id": "managing-members-at-runtime"
    },
    {
      "id": "manual-failover-ex0",
      "language": "csharp",
      "code": "// Get the members and find the one you want to fail over to.\nConnectionGroupMember target = conn.GetMembers().First(m => m.Name == \"US West\");\n\n// Attempt to fail over to the specified member.\nif (conn.TryFailoverTo(target))\n{\n    Console.WriteLine($\"Successfully failed over to {target.Name}\");\n}\n\n// Later, pass null to remove the explicit failover and restore automatic selection.\nconn.TryFailoverTo(null);",
      "section_id": "manual-failover"
    },
    {
      "id": "monitoring-failover-events-ex0",
      "language": "csharp",
      "code": "conn.ConnectionChanged += (sender, args) =>\n{\n    if (args.Type == GroupConnectionChangedEventArgs.ChangeType.ActiveChanged)\n    {\n        Console.WriteLine($\"Active member changed from {args.PreviousGroup?.Name ?? \"none\"} to {args.Group.Name}\");\n    }\n};",
      "section_id": "monitoring-failover-events"
    },
    {
      "id": "pubsub-and-re-subscription-ex0",
      "language": "csharp",
      "code": "ISubscriber subscriber = conn.GetSubscriber();\n\n// If a failover happens, the subscription is automatically re-established\n// on the new active member.\nawait subscriber.SubscribeAsync(RedisChannel.Literal(\"notifications\"), (channel, message) =>\n{\n    Console.WriteLine($\"Received: {message}\");\n});\n\nawait subscriber.PublishAsync(RedisChannel.Literal(\"notifications\"), \"Hello, World!\");",
      "section_id": "pubsub-and-re-subscription"
    }
  ]
}
