Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CodeAnalysisRuleset>$(MSBuildThisFileDirectory)Shared.ruleset</CodeAnalysisRuleset>
<MSBuildWarningsAsMessages>NETSDK1069</MSBuildWarningsAsMessages>
<!-- SER001/SER002/SER003/SER006/SER007/SER008 retired (vector sets, Redis 8.4/8.6/8.8/8.10 features,
and geo-redundant failover, no longer experimental); IDs reserved, see Experiments.cs -->
<NoWarn>$(NoWarn);NU5105;NU1507;SER004;SER005;SER009;SER010</NoWarn>
<!-- SER001/SER002/SER003/SER006/SER007/SER008/SER010 retired (vector sets, Redis 8.4/8.6/8.8/8.10 features,
geo-redundant failover and maintenance notifications, no longer experimental); IDs reserved, see Experiments.cs -->
<NoWarn>$(NoWarn);NU5105;NU1507;SER004;SER005;SER009</NoWarn>
<PackageReleaseNotes>https://github.com/StackExchange/StackExchange.Redis/releases</PackageReleaseNotes>
<PackageProjectUrl>https://seredis.dev/</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand Down
9 changes: 2 additions & 7 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,13 +368,8 @@ broken connection. This requires RESP3, and the client asks for it per connectio
| `enabled` | **Required**: ask, and reject the connection unless notifications are live. Only point this at a deployment you know supports them. |

Note that `enabled` means *required*, not merely *on* - it is the cross-client name for that mode, and it will
refuse a connection that cannot deliver notifications, including any RESP2 connection.

**For now this is purely opt-in: you must set `maintNotifications` yourself.** The `amr`, `rediscloud` and
`enterprise` providers are intended to select `auto` for you, so that on those deployments nothing needs
setting - but that auto-enlistment is held back until the feature has been through formal acceptance testing,
and is expected in a follow-up release. Naming a provider today sets its other defaults without turning
notifications on.
refuse a connection that cannot deliver notifications, including any RESP2 connection. The `amr`, `rediscloud`
and `enterprise` providers select `auto` for you, so on those deployments you need not set anything.

While a disruption has been announced, command timeouts are relaxed - raised to `maintRelaxedTimeout`, never
lowered, so a caller with a more generous timeout keeps it. The window ends when the server says the disruption
Expand Down
39 changes: 16 additions & 23 deletions docs/ServerMaintenanceEvent.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ If you are a Redis vendor and want to integrate support for ServerMaintenanceEve

# Server-native maintenance notifications (Redis Enterprise and Redis Cloud)

> These APIs are experimental, behind diagnostic id `SER010`; see [SER010](exp/SER010.md).

Redis Enterprise and Redis Cloud can tell a client *directly* that a disruption is coming: a shard is migrating, a node is failing over, or the endpoint you are connected to is being replaced. These arrive as RESP3 push frames on the connection itself, and the client does not merely report them: it relaxes timeouts for the duration, re-reads the cluster topology when slots have moved, recovers sharded subscriptions that were stranded, and moves off an endpoint that is going away rather than waiting to be disconnected.

### What this feature is called
Expand All @@ -29,34 +27,29 @@ Server-side you may also see it discussed as *maintenance mode*, *shard migratio

## Do I need to configure anything?

**Yes — for now.** In this release maintenance notifications are **purely opt-in**: nothing turns them on for you, whatever you connect to. You get them only by asking, with `maintNotifications=Auto` (or `Enabled`).

> **This is temporary.** The intent is that the options providers enlist you automatically, so that connecting to a recognized Redis Cloud or Azure Managed Redis hostname turns the feature on without any configuration — exactly as the table below describes. That is held back only until the feature has been through formal acceptance testing, and is expected to land in a follow-up release. Until then, treat the "will be" column as a statement of direction, not of current behaviour.
Usually not. If you connect using the hostname your provider gave you, the matching options provider recognizes it and turns the feature on for you by default.

| You connect to | Recognized as | Notifications now | Will be |
|---|---|---|---|
| `something.cloud.redislabs.com`, `.cloud.redis.io`, `.redislabs.com` | Redis Cloud | **off** unless asked | on (`Auto`) |
| `something.redis.azure.net`, `.redisenterprise.cache.azure.net` | Azure Managed Redis | **off** unless asked | on (`Auto`) |
| `defaults=enterprise`, `defaults=rediscloud`, `defaults=amr` | named explicitly | **off** unless asked | on (`Auto`) |
| your own hostname, a CNAME, private DNS, or through a proxy | nothing | **off** | **off** |
| a self-managed Redis Enterprise cluster | nothing (there is no DNS pattern to recognize) | **off** | **off** |
| You connect to | Recognized as | Notifications |
|---|---|---|
| `something.cloud.redislabs.com`, `.cloud.redis.io`, `.redislabs.com` | Redis Cloud | on (`Auto`) |
| `something.redis.azure.net`, `.redisenterprise.cache.azure.net` | Azure Managed Redis | on (`Auto`) |
| `defaults=enterprise`, `defaults=rediscloud`, `defaults=amr` | named explicitly | on (`Auto`) |
| your own hostname, a CNAME, private DNS, or through a proxy | nothing | **off** |
| a self-managed Redis Enterprise cluster | nothing (there is no DNS pattern to recognize) | **off** |

Nothing fails when the feature is off: the connection works normally and you simply never receive a notification. So to use it today, ask for it explicitly. Either:
The last two rows are the ones to know about, because nothing fails: the connection works normally and you simply never receive a notification. If your endpoint does not look like your provider's, say so explicitly. Either:

```csharp
// change nothing except this feature
var options = ConfigurationOptions.Parse("my-redis.internal.example.com:6379,maintNotifications=Auto");
// the whole deployment posture: prefer RESP3, skip the OSS config-broadcast channel, and ask for notifications
var options = ConfigurationOptions.Parse("my-redis.internal.example.com:6379,defaults=enterprise");
```

or, to take the whole deployment posture as well - prefer RESP3 and skip the OSS config-broadcast channel:
or, to change nothing except this feature:

```csharp
var options = ConfigurationOptions.Parse(
"my-redis.internal.example.com:6379,defaults=enterprise,maintNotifications=Auto");
var options = ConfigurationOptions.Parse("my-redis.internal.example.com:6379,maintNotifications=Auto");
```

Note that `maintNotifications` is needed in *both* forms for now: while the feature is opt-in, naming a provider sets that provider's other defaults but does not turn notifications on. Once auto-enlistment lands, `defaults=enterprise` alone will be enough.

`defaults=` accepts `rediscloud`, `enterprise`, `amr` and `azure`; see [Configuration](Configuration.md) for what each provider sets. It is also the right answer for a *hosted* deployment reached somewhere its own provider cannot see it, such as behind a CNAME or a private endpoint.

### RESP3 is required, and is already the default
Expand All @@ -77,8 +70,8 @@ options.MaintenanceNotifications = MaintenanceNotificationMode.Auto;

| Mode | Meaning |
|---|---|
| `Disabled` | never ask. **The default everywhere, for now** - see above |
| `Auto` | ask, and carry on if the server says no. What the providers will select once auto-enlistment lands |
| `Disabled` | never ask. The default unless a provider recognizes the endpoint - see above |
| `Auto` | ask, and carry on if the server says no. What the Redis Cloud, Redis Enterprise and Azure Managed Redis providers select |
| `Enabled` | **require** them: if the server will not deliver them, or the connection ends up on RESP2, the connection is **rejected** |

`Auto` is the right choice almost always: asking costs one command during the handshake, and a server that accepts and then never sends anything costs nothing at all. `Enabled` exists for the case where running without advance warning is worse than not running: it turns a silent absence into a startup failure, which also makes it a useful way to prove the feature is live in a staging environment.
Expand Down Expand Up @@ -231,7 +224,7 @@ Alternatively set `MaintenanceNotifications = Enabled` in a test or staging envi

So for now the connection succeeds *without* the feature and logs a warning saying so. Note this is the one case where `Enabled` does not reject the connection - it would otherwise be impossible to configure an explicit opt-in for a group at all. Expect this restriction to be lifted; if you are relying on maintenance notifications today, use a single-group connection.

Redis Enterprise and Redis Cloud send them, subject to the feature being enabled on the cluster. Azure Managed Redis is configured to ask for them ahead of its own rollout, so the setting is harmless until their servers begin emitting. Redis Open Source, Valkey and other servers do not send them at all, and the setting is simply inert there: the opt-in is refused and the client carries on.
Redis Enterprise and Redis Cloud send them, subject to the feature being enabled on the cluster. On Redis Enterprise there are *two* switches, and they are easy to conflate: a **cluster-level flag** decides whether the subcommand exists at all (`client_maint_notifications` for proxy-routed databases, `oss_cluster_client_maint_notifications` for `oss_cluster` ones), and the per-connection opt-in that `maintNotifications` controls asks one connection to receive them. With the flag off, `CLIENT MAINT_NOTIFICATIONS ON` is refused, which `Auto` absorbs silently and `Enabled` turns into a refused connection - so if `Enabled` rejects connections to a deployment you believe supports the feature, check the cluster flag before suspecting the client. Azure Managed Redis is configured to ask for them ahead of its own rollout, so the setting is harmless until their servers begin emitting. Redis Open Source, Valkey and other servers do not send them at all, and the setting is simply inert there: the opt-in is refused and the client carries on.

# Azure Cache for Redis maintenance events (pub/sub)

Expand Down
77 changes: 32 additions & 45 deletions docs/exp/SER010.md
Original file line number Diff line number Diff line change
@@ -1,57 +1,39 @@
Maintenance notifications ("smart client handoffs") are a server feature by which a server warns a
connected client in advance of a disruptive event - a shard migration, a failover, or the endpoint
itself being replaced - so the client can act before the disruption rather than react to a broken
connection afterwards. The client opts in per connection with `CLIENT MAINT_NOTIFICATIONS ON`, and the
notifications then arrive as RESP3 push frames on the connection that carries commands.
# Maintenance notifications - SER010 retired

The feature is experimental here for three separate reasons:
**This diagnostic is no longer produced.** Server-native maintenance notifications ("smart client
handoffs") are no longer marked `[Experimental]`, so nothing emits `SER010` and no suppression is needed.
The page is kept because the diagnostic's help link pointed here, and older builds still carry it.

1. **The server side is not universal.** Only Redis Enterprise and Redis Cloud emit these
notifications today; OSS Redis, Valkey and Garnet do not recognize the opt-in at all. That is why
`MaintenanceNotificationMode.Auto` exists, and why the default is `Disabled`.
2. **The wire contract is still evolving.** Notification types have been proposed and withdrawn during
its development, and the payloads are described in prose rather than pinned down by a formal
specification. Our parsing is validated against frames captured from live deployments and is
deliberately liberal about what it accepts, but the shapes may still change.
3. **What the client *does* in response is the substantial part**, and it is being built in stages -
timeout relaxation, then endpoint handoff. Behaviour may therefore change materially between
versions while the diagnostic is in place, even where the API does not.
If you have a suppression for `SER010` - in a `csproj`, a `.editorconfig`, or a `#pragma` - it is now
inert, and can be removed whenever it suits you.

**Currently purely opt-in.** Nothing enables this for you: the options providers for Redis Cloud, Azure
Managed Redis and Redis Enterprise are intended to select `Auto` so that a recognized endpoint needs no
configuration, but that auto-enlistment is held back until the feature has been through formal acceptance
testing, and is expected in a follow-up release. Until then the only thing that turns notifications on is
setting `maintNotifications` yourself.
For how the feature works and how to configure it, see [ServerMaintenanceEvent](../ServerMaintenanceEvent.md).

`MaintenanceNotificationMode`:
## What it covered

- `Disabled` (the default) - never ask; the server sends nothing.
- `Enabled` - **required**: ask, and *reject the connection* unless notifications end up live. That covers a
server that refuses, a server that answers `HELLO 3` as RESP2, and a configuration that could never ask in
the first place (`Protocol = Resp2`, or `HELLO` unavailable) - requiring a RESP3-only feature over RESP2 is
a contradiction, and it fails rather than being half-honoured. Only point this at a deployment you know
supports them.
- `Auto` - ask, and carry on if the server refuses or the connection ends up RESP2; the feature is then
simply off for that server and the connection is never rejected over it. Safe against a mixture of
servers, or one whose support you don't know, and what most callers want.
Maintenance notifications are a server feature by which a server warns a connected client in advance of
a disruptive event - a shard migration, a failover, or the endpoint itself being replaced - so the client
can act before the disruption rather than react to a broken connection afterwards. The gated surface was:

The names are the cross-client ones (they match go-redis and redis-py, so a connection string ports
between clients), which makes `Enabled` easy to misread as "on" - it means "required". One deliberate
divergence: the cross-client spec only calls for interrupting the connection when the *server* errors,
whereas we extend that to the RESP2 cases above, on the grounds that a required feature which cannot
possibly be delivered is the same failure either way.
- the `MaintenanceNotificationMode` and `MaintenanceEndpointType` enums, and the
`MaintenanceNotifications`, `MaintenanceMovingEndpointType`, `MaintenanceRelaxedTimeout`,
`MaintenanceRelaxedWindowMax` and `MaintenancePostEventRelaxedDuration` options on
`ConfigurationOptions` and `DefaultOptionsProvider`
- `PushMaintenanceEvent`, `ClusterSlotMigration` and `MaintenanceNotificationType`
- the `MaintenanceType` property on `FaultContext`, `RedisConnectionException` and `RedisTimeoutException`

## A deployment prerequisite worth knowing
## What it used to mean

On Redis Enterprise there are *two* switches, and they are easy to conflate. A **cluster-level flag** decides
whether the subcommand exists at all - `client_maint_notifications` for proxy-routed databases,
`oss_cluster_client_maint_notifications` for `oss_cluster` ones - and the **per-connection opt-in** this option
controls asks one connection to receive them. A cluster running a supporting version with the flag off will
refuse `CLIENT MAINT_NOTIFICATIONS ON`, which `Auto` absorbs silently and `Enabled` turns into a refused
connection. So if `Enabled` is rejecting connections against a deployment you believe supports the feature,
check the cluster flag before suspecting the client.
While the feature was experimental, it was flagged as subject to change for three reasons:

If you accept the above, you can suppress this warning by adding the following to your `csproj` file:
1. **The server side is not universal.** Only Redis Enterprise and Redis Cloud emitted these
notifications; OSS Redis, Valkey and Garnet do not recognize the opt-in at all.
2. **The wire contract was still evolving.** Notification types were proposed and withdrawn during its
development, and the payloads are described in prose rather than by a formal specification.
3. **What the client *does* in response was being built in stages** - timeout relaxation, then endpoint
handoff - so behaviour could change materially between versions even where the API did not.

Acknowledging that was what the suppression meant:

```xml
<NoWarn>$(NoWarn);SER010</NoWarn>
Expand All @@ -62,3 +44,8 @@ or more granularly / locally in C#:
``` c#
#pragma warning disable SER010
```

## Reserved, not recycled

`SER010` is not reused for anything else, so an old suppression cannot silently hide an unrelated
warning. The same applies to the other retired identifiers - see `Experiments.cs`.
2 changes: 1 addition & 1 deletion src/RESPite/Shared/Experiments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ internal static class Experiments
// SER006 = Server_8_8 (Redis 8.8 features)
// SER007 = GeoRedundantFailover (multi-group connections, health checks, retry)
// SER008 = Server_8_10 (Redis 8.10 features)
// SER010 = MaintenanceNotifications (server-native maintenance notifications / smart client handoffs)

// ReSharper disable InconsistentNaming
public const string Respite = "SER004";
public const string UnitTesting = "SER005";
public const string Transport = "SER009";
public const string MaintenanceNotifications = "SER010";

// ReSharper restore InconsistentNaming

Expand Down
1 change: 0 additions & 1 deletion src/StackExchange.Redis/Availability/FaultContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ public FaultContext(Exception fault)
/// was for. Left to policy rather than acted on here, since "ignore faults during maintenance" is a
/// judgement about the deployment, not about the protocol.
/// </remarks>
[Experimental(Experiments.MaintenanceNotifications, UrlFormat = Experiments.UrlFormat)]
public Maintenance.MaintenanceNotificationType MaintenanceType { get; }

private static bool IsKnownNotApplied(RedisErrorKind kind, CommandStatus status)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,16 @@ public override Task AfterConnectAsync(ConnectionMultiplexer muxer, Action<strin
/// <inheritdoc/>
public override string ConfigurationChannel => ""; // disable on AMR

// Auto-enlistment, deliberately withheld for now; maintenance notifications ship purely opt-in, and
// this goes back in a follow-up once the feature has been through formal acceptance testing. Kept here
// commented rather than deleted so that what returns is exactly what was reviewed.
//
// Pre-emptive: AMR does not emit these yet, and support is being added concurrently with this
// client-side work. Auto is what makes that safe - until the server side ships, the opt-in is refused
// and the feature stays off, and it then starts working without anybody needing to change a connection
// string. AMR also already prefers RESP3 here, which the feature requires.
//
// [Experimental(Experiments.MaintenanceNotifications, UrlFormat = Experiments.UrlFormat)]
// public override MaintenanceNotificationMode MaintenanceNotifications => MaintenanceNotificationMode.Auto;
/// <summary>
/// Ask for maintenance notifications, tolerating a server that doesn't offer them.
/// </summary>
/// <remarks>
/// Pre-emptive: AMR does not emit these yet, and support is being added concurrently with this
/// client-side work. <see cref="MaintenanceNotificationMode.Auto"/> is what makes that safe - until
/// the server side ships, the opt-in is refused and the feature stays off, and it then starts working
/// without anybody needing to change a connection string. AMR also already prefers RESP3 here, which
/// the feature requires.
/// </remarks>
public override MaintenanceNotificationMode MaintenanceNotifications => MaintenanceNotificationMode.Auto;
}
}
Loading
Loading