Skip to content

Commit ea5a640

Browse files
authored
Support multi-DB cluster usage on valkey (#3040)
* - propose new API to resolve product variant; only handles Redis, Valkey and Garnet at the moment * - change the test-server to support multi-DB operations - update the client to use the product-variant info to allow multi-DB operations on Valkey - unit test with the toy-server * naming is hard * release notes
1 parent 364b847 commit ea5a640

18 files changed

+594
-138
lines changed

docs/ReleaseNotes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Current package versions:
88

99
## Unreleased
1010

11+
- Add `IServer.GetProductVariant` to detect the product variant and version of the connected server, and use that internally
12+
to enable multi-DB operations on Valkey clusters ([#3040 by @mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/3040))
1113
- Ignore cluster nodes with the `handshake` flag ([#3043 by @TimLovellSmith](https://github.com/StackExchange/StackExchange.Redis/pull/3043))
1214

1315
## 2.12.4
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System;
2+
using RESPite;
3+
4+
namespace StackExchange.Redis;
5+
6+
/// <summary>
7+
/// Fields that can appear in INFO output during auto-configuration.
8+
/// </summary>
9+
internal enum AutoConfigureInfoField
10+
{
11+
/// <summary>
12+
/// Unknown or unrecognized field.
13+
/// </summary>
14+
[AsciiHash("")]
15+
Unknown = 0,
16+
17+
[AsciiHash("role")]
18+
Role,
19+
20+
[AsciiHash("master_host")]
21+
MasterHost,
22+
23+
[AsciiHash("master_port")]
24+
MasterPort,
25+
26+
[AsciiHash("redis_version")]
27+
RedisVersion,
28+
29+
[AsciiHash("redis_mode")]
30+
RedisMode,
31+
32+
[AsciiHash("run_id")]
33+
RunId,
34+
35+
[AsciiHash("garnet_version")]
36+
GarnetVersion,
37+
38+
[AsciiHash("valkey_version")]
39+
ValkeyVersion,
40+
}
41+
42+
/// <summary>
43+
/// Metadata and parsing methods for <see cref="AutoConfigureInfoField"/>.
44+
/// </summary>
45+
internal static partial class AutoConfigureInfoFieldMetadata
46+
{
47+
[AsciiHash]
48+
internal static partial bool TryParse(ReadOnlySpan<char> value, out AutoConfigureInfoField field);
49+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// ReSharper disable once CheckNamespace
2+
namespace StackExchange.Redis;
3+
4+
/// <summary>
5+
/// Indicates the flavor of RESP-server being used, if known. Unknown variants will be reported as <see cref="Redis"/>.
6+
/// Inclusion (or omission) in this list does not imply support for any given variant; nor does it indicate any specific
7+
/// relationship with the vendor or rights to use the name. It is provided solely for informational purposes. Identification
8+
/// is not guaranteed, and is based on the server's self-reporting (typically via `INFO`), which may be incomplete or misleading.
9+
/// </summary>
10+
public enum ProductVariant
11+
{
12+
/// <summary>
13+
/// The original Redis server. This is also the default value if the variant is unknown.
14+
/// </summary>
15+
Redis,
16+
17+
/// <summary>
18+
/// <a href="https://valkey.io/">Valkey</a> is a fork of open-source Redis associated with AWS.
19+
/// </summary>
20+
Valkey,
21+
22+
/// <summary>
23+
/// <a href="https://microsoft.github.io/garnet/">Garnet</a> is a Redis-compatible server from Microsoft.
24+
/// </summary>
25+
Garnet,
26+
27+
// if you want to add another variant here, please open an issue with the details (variant name, INFO output, etc.)
28+
}

src/StackExchange.Redis/Enums/ServerType.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
namespace StackExchange.Redis
1+
using System;
2+
using RESPite;
3+
4+
namespace StackExchange.Redis
25
{
36
/// <summary>
47
/// Indicates the flavor of a particular redis server.
@@ -8,29 +11,50 @@ public enum ServerType
811
/// <summary>
912
/// Classic redis-server server.
1013
/// </summary>
14+
[AsciiHash("standalone")]
1115
Standalone,
1216

1317
/// <summary>
1418
/// Monitoring/configuration redis-sentinel server.
1519
/// </summary>
20+
[AsciiHash("sentinel")]
1621
Sentinel,
1722

1823
/// <summary>
1924
/// Distributed redis-cluster server.
2025
/// </summary>
26+
[AsciiHash("cluster")]
2127
Cluster,
2228

2329
/// <summary>
2430
/// Distributed redis installation via <a href="https://github.com/twitter/twemproxy">twemproxy</a>.
2531
/// </summary>
32+
[AsciiHash("")]
2633
Twemproxy,
2734

2835
/// <summary>
2936
/// Redis cluster via <a href="https://github.com/envoyproxy/envoy">envoyproxy</a>.
3037
/// </summary>
38+
[AsciiHash("")]
3139
Envoyproxy,
3240
}
3341

42+
/// <summary>
43+
/// Metadata and parsing methods for <see cref="ServerType"/>.
44+
/// </summary>
45+
internal static partial class ServerTypeMetadata
46+
{
47+
[AsciiHash]
48+
internal static partial bool TryParse(ReadOnlySpan<char> value, out ServerType serverType);
49+
50+
internal static bool TryParse(string? val, out ServerType serverType)
51+
{
52+
if (val is not null) return TryParse(val.AsSpan().Trim(), out serverType);
53+
serverType = default;
54+
return false;
55+
}
56+
}
57+
3458
internal static class ServerTypeExtensions
3559
{
3660
/// <summary>

src/StackExchange.Redis/Format.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -583,14 +583,8 @@ internal static bool TryParseVersion(ReadOnlySpan<char> input, [NotNullWhen(true
583583
version = null;
584584
return false;
585585
}
586-
unsafe
587-
{
588-
fixed (char* ptr = input)
589-
{
590-
string s = new(ptr, 0, input.Length);
591-
return TryParseVersion(s, out version);
592-
}
593-
}
586+
string s = input.ToString();
587+
return TryParseVersion(s, out version);
594588
#endif
595589
}
596590

src/StackExchange.Redis/Interfaces/IServer.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ public partial interface IServer : IRedis
7272
/// </summary>
7373
Version Version { get; }
7474

75+
/// <summary>
76+
/// Attempt to identify the specific Redis product variant and version.
77+
/// </summary>
78+
/// <remarks>Note that it is explicitly not assumed that the version will conform to the <see cref="Version"/> format.</remarks>
79+
ProductVariant GetProductVariant(out string version);
80+
7581
/// <summary>
7682
/// The number of databases supported on this server.
7783
/// </summary>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using RESPite;
3+
4+
namespace StackExchange.Redis;
5+
6+
/// <summary>
7+
/// Known role values used during auto-configuration parsing.
8+
/// </summary>
9+
internal enum KnownRole
10+
{
11+
/// <summary>
12+
/// Unknown or unrecognized role.
13+
/// </summary>
14+
[AsciiHash("")]
15+
None = 0,
16+
17+
[AsciiHash("primary")]
18+
Primary,
19+
20+
[AsciiHash("master")]
21+
Master,
22+
23+
[AsciiHash("replica")]
24+
Replica,
25+
26+
[AsciiHash("slave")]
27+
Slave,
28+
}
29+
30+
/// <summary>
31+
/// Metadata and parsing methods for <see cref="KnownRole"/>.
32+
/// </summary>
33+
internal static partial class KnownRoleMetadata
34+
{
35+
[AsciiHash]
36+
private static partial bool TryParseCore(ReadOnlySpan<char> value, out KnownRole role);
37+
38+
internal static bool TryParse(ReadOnlySpan<char> value, out bool isReplica)
39+
{
40+
if (!TryParseCore(value.Trim(), out var role))
41+
{
42+
isReplica = false;
43+
return false;
44+
}
45+
46+
isReplica = role is KnownRole.Replica or KnownRole.Slave;
47+
return true;
48+
}
49+
internal static bool TryParse(string? val, out bool isReplica)
50+
{
51+
if (val is not null) return TryParse(val.AsSpan(), out isReplica);
52+
isReplica = false;
53+
return false;
54+
}
55+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
11
#nullable enable
2+
StackExchange.Redis.IServer.GetProductVariant(out string! version) -> StackExchange.Redis.ProductVariant
3+
StackExchange.Redis.ProductVariant
4+
StackExchange.Redis.ProductVariant.Garnet = 2 -> StackExchange.Redis.ProductVariant
5+
StackExchange.Redis.ProductVariant.Redis = 0 -> StackExchange.Redis.ProductVariant
6+
StackExchange.Redis.ProductVariant.Valkey = 1 -> StackExchange.Redis.ProductVariant

src/StackExchange.Redis/RedisServer.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ public bool AllowReplicaWrites
4949

5050
public ServerType ServerType => server.ServerType;
5151

52+
public ProductVariant GetProductVariant(out string version) => server.GetProductVariant(out version);
53+
5254
public Version Version => server.Version;
5355

5456
public void ClientKill(EndPoint endpoint, CommandFlags flags = CommandFlags.None)

0 commit comments

Comments
 (0)