From 8fd6802d196a8af2dc668a0258f85c79a001cf1a Mon Sep 17 00:00:00 2001 From: Joshua Siwek Date: Sun, 9 Mar 2025 17:13:47 -0700 Subject: [PATCH 01/13] #915 added notify-keyspace-events option --- libs/host/Configuration/Options.cs | 3 +++ libs/server/Resp/CmdStrings.cs | 1 + libs/server/ServerConfig.cs | 3 +++ 3 files changed, 7 insertions(+) diff --git a/libs/host/Configuration/Options.cs b/libs/host/Configuration/Options.cs index ac166aea094..41dc7a9d4df 100644 --- a/libs/host/Configuration/Options.cs +++ b/libs/host/Configuration/Options.cs @@ -183,6 +183,9 @@ internal sealed class Options [Option("cluster-password", Required = false, HelpText = "Password to authenticate intra-cluster communication with.")] public string ClusterPassword { get; set; } + [Option("notify-keyspace-events-arguments", Required = false, HelpText = "Keyspace Notification argument string.")] + public string NotifiyKeyspaceEventsArguments { get; set; } + [FilePathValidation(true, true, false)] [Option("acl-file", Required = false, HelpText = "External ACL user file.")] public string AclFile { get; set; } diff --git a/libs/server/Resp/CmdStrings.cs b/libs/server/Resp/CmdStrings.cs index fec0d0c08a0..06fcda951ac 100644 --- a/libs/server/Resp/CmdStrings.cs +++ b/libs/server/Resp/CmdStrings.cs @@ -30,6 +30,7 @@ static partial class CmdStrings public static ReadOnlySpan CertPassword => "cert-password"u8; public static ReadOnlySpan ClusterUsername => "cluster-username"u8; public static ReadOnlySpan ClusterPassword => "cluster-password"u8; + public static ReadOnlySpan NotifiyKeyspaceEvents => "notify-keyspace-events"u8; public static ReadOnlySpan ECHO => "ECHO"u8; public static ReadOnlySpan ACL => "ACL"u8; public static ReadOnlySpan AUTH => "AUTH"u8; diff --git a/libs/server/ServerConfig.cs b/libs/server/ServerConfig.cs index 634114bb313..18b63a54977 100644 --- a/libs/server/ServerConfig.cs +++ b/libs/server/ServerConfig.cs @@ -131,6 +131,7 @@ private bool NetworkCONFIG_SET() string certPassword = null; string clusterUsername = null; string clusterPassword = null; + string notifiyKeyspaceEventsArguments = null; var unknownOption = false; var unknownKey = ""; @@ -147,6 +148,8 @@ private bool NetworkCONFIG_SET() clusterUsername = Encoding.ASCII.GetString(value); else if (key.SequenceEqual(CmdStrings.ClusterPassword)) clusterPassword = Encoding.ASCII.GetString(value); + else if (key.SequenceEqual(CmdStrings.NotifiyKeyspaceEvents)) + notifiyKeyspaceEventsArguments = Encoding.ASCII.GetString(value); else { if (!unknownOption) From abcb7b2f7ca34d5a231ff2859a5ea8f19eb35af3 Mon Sep 17 00:00:00 2001 From: Joshua Siwek Date: Sun, 9 Mar 2025 17:48:52 -0700 Subject: [PATCH 02/13] added NotifiyKeyspaceEventsArguments in GarnetServerOptions.cs; set GarnetServerOptions with argument string --- libs/server/ServerConfig.cs | 4 ++++ libs/server/Servers/GarnetServerOptions.cs | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/libs/server/ServerConfig.cs b/libs/server/ServerConfig.cs index 18b63a54977..3dc841e90f0 100644 --- a/libs/server/ServerConfig.cs +++ b/libs/server/ServerConfig.cs @@ -194,6 +194,10 @@ private bool NetworkCONFIG_SET() else errorMsg += " TLS is disabled."; } } + if (notifiyKeyspaceEventsArguments != null) + { + storeWrapper.serverOptions.NotifiyKeyspaceEventsArguments = notifiyKeyspaceEventsArguments; + } } if (errorMsg == null) diff --git a/libs/server/Servers/GarnetServerOptions.cs b/libs/server/Servers/GarnetServerOptions.cs index 399578e82d4..936a8932e94 100644 --- a/libs/server/Servers/GarnetServerOptions.cs +++ b/libs/server/Servers/GarnetServerOptions.cs @@ -196,6 +196,11 @@ public class GarnetServerOptions : ServerOptions /// public string ClusterPassword; + /// + /// Argument string for keyspace notifications + /// + public string NotifiyKeyspaceEventsArguments; + /// /// Enable per command latency tracking for all commands /// From 8a3c67a5f0c2ecf33e57ec718593518322134c62 Mon Sep 17 00:00:00 2001 From: Joshua Siwek Date: Mon, 10 Mar 2025 07:22:52 -0700 Subject: [PATCH 03/13] added KeyspaceNotficationType enum --- .../IKeyspaceNotificationOptions.cs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 libs/server/KeyspaceNotifications/IKeyspaceNotificationOptions.cs diff --git a/libs/server/KeyspaceNotifications/IKeyspaceNotificationOptions.cs b/libs/server/KeyspaceNotifications/IKeyspaceNotificationOptions.cs new file mode 100644 index 00000000000..54213ca2190 --- /dev/null +++ b/libs/server/KeyspaceNotifications/IKeyspaceNotificationOptions.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Garnet.server.KeyspaceNotifications +{ + /// + /// Represents different types of notifications that can be received. + /// + [Flags] + public enum KeyspaceNotificationType + { + /// Keyspace events, published with __keyspace@<db>__ prefix. + Keyspace = 1 << 0, // K + + /// Keyevent events, published with __keyevent@<db>__ prefix. + KeyEvent = 1 << 1, // E + + /// Generic commands (non-type specific) like DEL, EXPIRE, RENAME, etc. + Generic = 1 << 2, // g + + /// String commands. + String = 1 << 3, // $ + + /// List commands. + List = 1 << 4, // l + + /// Set commands. + Set = 1 << 5, // s + + /// Hash commands. + Hash = 1 << 6, // h + + /// Sorted set commands. + ZSet = 1 << 7, // z + + /// Expired events (events generated every time a key expires). + Expired = 1 << 8, // x + + /// Evicted events (events generated when a key is evicted for maxmemory). + Evicted = 1 << 9, // e + + /// Stream commands. + Stream = 1 << 10, // t + + /// Key miss events (events generated when a key that doesn't exist is accessed). + KeyMiss = 1 << 11, // m (Excluded from NOTIFY_ALL) + + /// Module-only key space notification, indicating a key loaded from RDB. + Loaded = 1 << 12, // module only key space notification + + /// Module key type events. + Module = 1 << 13, // d, module key space notification + + /// New key events (Note: not included in the 'A' class). + New = 1 << 14, // n, new key notification + + /// + /// Alias for "g$lshztxed", meaning all events except "m" (KeyMiss) and "n" (New). + /// + All = Generic | String | List | Set | Hash | ZSet | Expired | Evicted | Stream | Module // A flag + } +} From a40be46e5111325ab740c64d7110a3e28eebfcca Mon Sep 17 00:00:00 2001 From: Joshua Siwek Date: Mon, 10 Mar 2025 07:51:40 -0700 Subject: [PATCH 04/13] added IKeyspaceNotificationOptions --- .../IKeyspaceNotificationOptions.cs | 63 +----------------- .../KeyspaceNotificationType.cs | 65 +++++++++++++++++++ 2 files changed, 68 insertions(+), 60 deletions(-) create mode 100644 libs/server/KeyspaceNotifications/KeyspaceNotificationType.cs diff --git a/libs/server/KeyspaceNotifications/IKeyspaceNotificationOptions.cs b/libs/server/KeyspaceNotifications/IKeyspaceNotificationOptions.cs index 54213ca2190..ce3a1e735d1 100644 --- a/libs/server/KeyspaceNotifications/IKeyspaceNotificationOptions.cs +++ b/libs/server/KeyspaceNotifications/IKeyspaceNotificationOptions.cs @@ -1,65 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - + namespace Garnet.server.KeyspaceNotifications { - /// - /// Represents different types of notifications that can be received. - /// - [Flags] - public enum KeyspaceNotificationType + public interface IKeyspaceNotificationOptions { - /// Keyspace events, published with __keyspace@<db>__ prefix. - Keyspace = 1 << 0, // K - - /// Keyevent events, published with __keyevent@<db>__ prefix. - KeyEvent = 1 << 1, // E - - /// Generic commands (non-type specific) like DEL, EXPIRE, RENAME, etc. - Generic = 1 << 2, // g - - /// String commands. - String = 1 << 3, // $ - - /// List commands. - List = 1 << 4, // l - - /// Set commands. - Set = 1 << 5, // s - - /// Hash commands. - Hash = 1 << 6, // h - - /// Sorted set commands. - ZSet = 1 << 7, // z - - /// Expired events (events generated every time a key expires). - Expired = 1 << 8, // x - - /// Evicted events (events generated when a key is evicted for maxmemory). - Evicted = 1 << 9, // e - - /// Stream commands. - Stream = 1 << 10, // t - - /// Key miss events (events generated when a key that doesn't exist is accessed). - KeyMiss = 1 << 11, // m (Excluded from NOTIFY_ALL) - - /// Module-only key space notification, indicating a key loaded from RDB. - Loaded = 1 << 12, // module only key space notification - - /// Module key type events. - Module = 1 << 13, // d, module key space notification - - /// New key events (Note: not included in the 'A' class). - New = 1 << 14, // n, new key notification - - /// - /// Alias for "g$lshztxed", meaning all events except "m" (KeyMiss) and "n" (New). - /// - All = Generic | String | List | Set | Hash | ZSet | Expired | Evicted | Stream | Module // A flag + KeyspaceNotificationType KeyspaceNotificationType { get; set; } } } diff --git a/libs/server/KeyspaceNotifications/KeyspaceNotificationType.cs b/libs/server/KeyspaceNotifications/KeyspaceNotificationType.cs new file mode 100644 index 00000000000..54213ca2190 --- /dev/null +++ b/libs/server/KeyspaceNotifications/KeyspaceNotificationType.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Garnet.server.KeyspaceNotifications +{ + /// + /// Represents different types of notifications that can be received. + /// + [Flags] + public enum KeyspaceNotificationType + { + /// Keyspace events, published with __keyspace@<db>__ prefix. + Keyspace = 1 << 0, // K + + /// Keyevent events, published with __keyevent@<db>__ prefix. + KeyEvent = 1 << 1, // E + + /// Generic commands (non-type specific) like DEL, EXPIRE, RENAME, etc. + Generic = 1 << 2, // g + + /// String commands. + String = 1 << 3, // $ + + /// List commands. + List = 1 << 4, // l + + /// Set commands. + Set = 1 << 5, // s + + /// Hash commands. + Hash = 1 << 6, // h + + /// Sorted set commands. + ZSet = 1 << 7, // z + + /// Expired events (events generated every time a key expires). + Expired = 1 << 8, // x + + /// Evicted events (events generated when a key is evicted for maxmemory). + Evicted = 1 << 9, // e + + /// Stream commands. + Stream = 1 << 10, // t + + /// Key miss events (events generated when a key that doesn't exist is accessed). + KeyMiss = 1 << 11, // m (Excluded from NOTIFY_ALL) + + /// Module-only key space notification, indicating a key loaded from RDB. + Loaded = 1 << 12, // module only key space notification + + /// Module key type events. + Module = 1 << 13, // d, module key space notification + + /// New key events (Note: not included in the 'A' class). + New = 1 << 14, // n, new key notification + + /// + /// Alias for "g$lshztxed", meaning all events except "m" (KeyMiss) and "n" (New). + /// + All = Generic | String | List | Set | Hash | ZSet | Expired | Evicted | Stream | Module // A flag + } +} From d3fb0a16a76fe1006f6f64adb9ac8cf4a7b309b9 Mon Sep 17 00:00:00 2001 From: Joshua Siwek Date: Thu, 13 Mar 2025 14:17:11 -0700 Subject: [PATCH 05/13] added Methods to PublishKeyspaceNotifications; use KeyspaceNotificationType for AllowedKeyspaceNotifications; --- .../KeyspaceNotificationPublisher.cs | 41 +++++++++++++++++++ .../KeyspaceNotificationStrings.cs | 18 ++++++++ .../KeyspaceNotificationType.cs | 2 +- libs/server/Resp/BasicCommands.cs | 3 ++ libs/server/Servers/GarnetServerOptions.cs | 5 ++- 5 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs create mode 100644 libs/server/KeyspaceNotifications/KeyspaceNotificationStrings.cs diff --git a/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs b/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs new file mode 100644 index 00000000000..6952e669229 --- /dev/null +++ b/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Garnet.server.KeyspaceNotifications; + +namespace Garnet.server +{ + internal sealed partial class RespServerSession : ServerSessionBase + { + public void PublishKeyspaceNotification(KeyspaceNotificationType keyspaceNotificationType, ref ArgSlice argSlice) + { + if (!storeWrapper.serverOptions.AllowedKeyspaceNotifications.HasFlag(keyspaceNotificationType)) + { + return; + } + + if (storeWrapper.serverOptions.AllowedKeyspaceNotifications.HasFlag(KeyspaceNotificationType.Keyspace)) + { + PublishKeyspace(ref argSlice); + } + + if (storeWrapper.serverOptions.AllowedKeyspaceNotifications.HasFlag(KeyspaceNotificationType.Keyevent)) + { + PublishKeyevent(ref argSlice); + } + } + + public void PublishKeyevent(ref ArgSlice argSlice) + { + Publish(ArgSlice.FromPinnedSpan(KeyspaceNotificationStrings.KeyspacePrefix), argSlice); + } + + public void PublishKeyspace(ref ArgSlice argSlice) + { + Publish(ArgSlice.FromPinnedSpan(KeyspaceNotificationStrings.KeyeventPrefix), argSlice); + } + } +} diff --git a/libs/server/KeyspaceNotifications/KeyspaceNotificationStrings.cs b/libs/server/KeyspaceNotifications/KeyspaceNotificationStrings.cs new file mode 100644 index 00000000000..3a45262bb3c --- /dev/null +++ b/libs/server/KeyspaceNotifications/KeyspaceNotificationStrings.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Garnet.server.KeyspaceNotifications +{ + /// + /// Keyspace Notification strings + /// + static partial class KeyspaceNotificationStrings + { + public static ReadOnlySpan KeyspacePrefix => "__keyspace@"u8; + public static ReadOnlySpan KeyeventPrefix => "__keyevent@"u8; + public static ReadOnlySpan Suffix => "__:"u8; + } +} diff --git a/libs/server/KeyspaceNotifications/KeyspaceNotificationType.cs b/libs/server/KeyspaceNotifications/KeyspaceNotificationType.cs index 54213ca2190..6bcea50e55e 100644 --- a/libs/server/KeyspaceNotifications/KeyspaceNotificationType.cs +++ b/libs/server/KeyspaceNotifications/KeyspaceNotificationType.cs @@ -16,7 +16,7 @@ public enum KeyspaceNotificationType Keyspace = 1 << 0, // K /// Keyevent events, published with __keyevent@<db>__ prefix. - KeyEvent = 1 << 1, // E + Keyevent = 1 << 1, // E /// Generic commands (non-type specific) like DEL, EXPIRE, RENAME, etc. Generic = 1 << 2, // g diff --git a/libs/server/Resp/BasicCommands.cs b/libs/server/Resp/BasicCommands.cs index efc91fd1f74..dbb0d88c38e 100644 --- a/libs/server/Resp/BasicCommands.cs +++ b/libs/server/Resp/BasicCommands.cs @@ -7,6 +7,7 @@ using System.Text; using System.Threading.Tasks; using Garnet.common; +using Garnet.server.KeyspaceNotifications; using Microsoft.Extensions.Logging; using Tsavorite.core; @@ -293,6 +294,8 @@ private bool NetworkSET(ref TGarnetApi storageApi) while (!RespWriteUtils.TryWriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) SendAndReset(); + PublishKeyspaceNotification(KeyspaceNotificationType.Set, ref parseState.GetArgSliceByRef(0)); + return true; } diff --git a/libs/server/Servers/GarnetServerOptions.cs b/libs/server/Servers/GarnetServerOptions.cs index 936a8932e94..3f8374c998e 100644 --- a/libs/server/Servers/GarnetServerOptions.cs +++ b/libs/server/Servers/GarnetServerOptions.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using Garnet.server.Auth.Settings; +using Garnet.server.KeyspaceNotifications; using Garnet.server.TLS; using Microsoft.Extensions.Logging; using Tsavorite.core; @@ -197,9 +198,9 @@ public class GarnetServerOptions : ServerOptions public string ClusterPassword; /// - /// Argument string for keyspace notifications + /// Keyspace notifications /// - public string NotifiyKeyspaceEventsArguments; + public KeyspaceNotificationType AllowedKeyspaceNotifications; /// /// Enable per command latency tracking for all commands From 18c806a5a30739183757b1a129db6daf4e112bb9 Mon Sep 17 00:00:00 2001 From: Joshua Siwek Date: Thu, 13 Mar 2025 14:41:49 -0700 Subject: [PATCH 06/13] allow all keyspace notifications when notifiy-keyspace-events = KEA for debugging purposes; use Publish from subscribeBroker instead of ServerSessionBase directly --- .../KeyspaceNotifications/KeyspaceNotificationPublisher.cs | 4 ++-- libs/server/ServerConfig.cs | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs b/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs index 6952e669229..ef1191c7c15 100644 --- a/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs +++ b/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs @@ -30,12 +30,12 @@ public void PublishKeyspaceNotification(KeyspaceNotificationType keyspaceNotific public void PublishKeyevent(ref ArgSlice argSlice) { - Publish(ArgSlice.FromPinnedSpan(KeyspaceNotificationStrings.KeyspacePrefix), argSlice); + subscribeBroker.Publish(ArgSlice.FromPinnedSpan(KeyspaceNotificationStrings.KeyspacePrefix), argSlice); } public void PublishKeyspace(ref ArgSlice argSlice) { - Publish(ArgSlice.FromPinnedSpan(KeyspaceNotificationStrings.KeyeventPrefix), argSlice); + subscribeBroker.Publish(ArgSlice.FromPinnedSpan(KeyspaceNotificationStrings.KeyeventPrefix), argSlice); } } } diff --git a/libs/server/ServerConfig.cs b/libs/server/ServerConfig.cs index 3dc841e90f0..a6c6f64bce2 100644 --- a/libs/server/ServerConfig.cs +++ b/libs/server/ServerConfig.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using Garnet.common; +using Garnet.server.KeyspaceNotifications; using Microsoft.Extensions.Logging; namespace Garnet.server @@ -194,9 +195,11 @@ private bool NetworkCONFIG_SET() else errorMsg += " TLS is disabled."; } } - if (notifiyKeyspaceEventsArguments != null) + + // TODO: Parse argument string into enum flags and remove second if statement + if (notifiyKeyspaceEventsArguments != null && notifiyKeyspaceEventsArguments.Equals("KEA")) { - storeWrapper.serverOptions.NotifiyKeyspaceEventsArguments = notifiyKeyspaceEventsArguments; + storeWrapper.serverOptions.AllowedKeyspaceNotifications = KeyspaceNotificationType.All | KeyspaceNotificationType.Keyspace | KeyspaceNotificationType.Keyevent; } } From 94fb42cc6baa7aa1ff3dd2e7e6ae0f4d7f404988 Mon Sep 17 00:00:00 2001 From: Joshua Siwek Date: Fri, 14 Mar 2025 18:45:21 -0700 Subject: [PATCH 07/13] added db id in suffix for testing; todo hints and span concatination; changed parameters and updated inputs accordingly; --- .../KeyspaceNotificationPublisher.cs | 35 ++++++++++++------- .../KeyspaceNotificationStrings.cs | 2 +- libs/server/Resp/BasicCommands.cs | 2 +- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs b/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs index ef1191c7c15..ad4275f3291 100644 --- a/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs +++ b/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs @@ -1,41 +1,52 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; using Garnet.server.KeyspaceNotifications; namespace Garnet.server { internal sealed partial class RespServerSession : ServerSessionBase { - public void PublishKeyspaceNotification(KeyspaceNotificationType keyspaceNotificationType, ref ArgSlice argSlice) + // TODO: use same parameter for key and keyevent; pass keyevent as ref; test performance + internal void PublishKeyspaceNotification(KeyspaceNotificationType keyspaceNotificationType, ref ArgSlice key, ReadOnlySpan keyevent) { if (!storeWrapper.serverOptions.AllowedKeyspaceNotifications.HasFlag(keyspaceNotificationType)) { return; } + var channel = ConcatSpans(KeyspaceNotificationStrings.KeyspacePrefix, KeyspaceNotificationStrings.Suffix); + if (storeWrapper.serverOptions.AllowedKeyspaceNotifications.HasFlag(KeyspaceNotificationType.Keyspace)) { - PublishKeyspace(ref argSlice); + var keyspaceChannel = ConcatSpans(channel, key.ReadOnlySpan); + PublishKeyspace(ref keyspaceChannel, keyevent); } if (storeWrapper.serverOptions.AllowedKeyspaceNotifications.HasFlag(KeyspaceNotificationType.Keyevent)) { - PublishKeyevent(ref argSlice); - } + var keyeventChannel = ConcatSpans(channel, keyevent); + PublishKeyevent(ref keyeventChannel, ref key); + } + } + private void PublishKeyspace(ref ReadOnlySpan channel, ReadOnlySpan keyevent) + { + // TODO: find a better solution for the string concatenation and converting to ArgSlice + subscribeBroker.Publish(ArgSlice.FromPinnedSpan(channel), ArgSlice.FromPinnedSpan(keyevent)); } - public void PublishKeyevent(ref ArgSlice argSlice) + private void PublishKeyevent(ref ReadOnlySpan channel, ref ArgSlice key) { - subscribeBroker.Publish(ArgSlice.FromPinnedSpan(KeyspaceNotificationStrings.KeyspacePrefix), argSlice); + // TODO: see above + subscribeBroker.Publish(ArgSlice.FromPinnedSpan(channel), key); } - public void PublishKeyspace(ref ArgSlice argSlice) + + private static ReadOnlySpan ConcatSpans(ReadOnlySpan first, ReadOnlySpan second) { - subscribeBroker.Publish(ArgSlice.FromPinnedSpan(KeyspaceNotificationStrings.KeyeventPrefix), argSlice); + byte[] combined = new byte[first.Length + second.Length]; + first.CopyTo(combined); + second.CopyTo(combined.AsSpan(first.Length)); + return combined; } } } diff --git a/libs/server/KeyspaceNotifications/KeyspaceNotificationStrings.cs b/libs/server/KeyspaceNotifications/KeyspaceNotificationStrings.cs index 3a45262bb3c..881f497eeb6 100644 --- a/libs/server/KeyspaceNotifications/KeyspaceNotificationStrings.cs +++ b/libs/server/KeyspaceNotifications/KeyspaceNotificationStrings.cs @@ -13,6 +13,6 @@ static partial class KeyspaceNotificationStrings { public static ReadOnlySpan KeyspacePrefix => "__keyspace@"u8; public static ReadOnlySpan KeyeventPrefix => "__keyevent@"u8; - public static ReadOnlySpan Suffix => "__:"u8; + public static ReadOnlySpan Suffix => "0__:"u8; } } diff --git a/libs/server/Resp/BasicCommands.cs b/libs/server/Resp/BasicCommands.cs index dbb0d88c38e..62db3b5ee61 100644 --- a/libs/server/Resp/BasicCommands.cs +++ b/libs/server/Resp/BasicCommands.cs @@ -294,7 +294,7 @@ private bool NetworkSET(ref TGarnetApi storageApi) while (!RespWriteUtils.TryWriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) SendAndReset(); - PublishKeyspaceNotification(KeyspaceNotificationType.Set, ref parseState.GetArgSliceByRef(0)); + PublishKeyspaceNotification(KeyspaceNotificationType.Set, ref parseState.GetArgSliceByRef(0), CmdStrings.set); return true; } From e186946ece263e85096ba79976d2cd34fd04729c Mon Sep 17 00:00:00 2001 From: Joshua Siwek Date: Sat, 15 Mar 2025 01:42:58 -0700 Subject: [PATCH 08/13] todo hints --- .../KeyspaceNotifications/KeyspaceNotificationPublisher.cs | 3 +-- .../KeyspaceNotifications/KeyspaceNotificationStrings.cs | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs b/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs index ad4275f3291..729c7355eed 100644 --- a/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs +++ b/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs @@ -6,7 +6,7 @@ namespace Garnet.server { internal sealed partial class RespServerSession : ServerSessionBase { - // TODO: use same parameter for key and keyevent; pass keyevent as ref; test performance + // TODO: use same parameter for key and keyevent; pass keyevent as ref; test performance; avoid concatSpans and remove concatenated Spans; check access modifiers internal void PublishKeyspaceNotification(KeyspaceNotificationType keyspaceNotificationType, ref ArgSlice key, ReadOnlySpan keyevent) { if (!storeWrapper.serverOptions.AllowedKeyspaceNotifications.HasFlag(keyspaceNotificationType)) @@ -40,7 +40,6 @@ private void PublishKeyevent(ref ReadOnlySpan channel, ref ArgSlice key) subscribeBroker.Publish(ArgSlice.FromPinnedSpan(channel), key); } - private static ReadOnlySpan ConcatSpans(ReadOnlySpan first, ReadOnlySpan second) { byte[] combined = new byte[first.Length + second.Length]; diff --git a/libs/server/KeyspaceNotifications/KeyspaceNotificationStrings.cs b/libs/server/KeyspaceNotifications/KeyspaceNotificationStrings.cs index 881f497eeb6..e4d27188650 100644 --- a/libs/server/KeyspaceNotifications/KeyspaceNotificationStrings.cs +++ b/libs/server/KeyspaceNotifications/KeyspaceNotificationStrings.cs @@ -13,6 +13,7 @@ static partial class KeyspaceNotificationStrings { public static ReadOnlySpan KeyspacePrefix => "__keyspace@"u8; public static ReadOnlySpan KeyeventPrefix => "__keyevent@"u8; + // TODO: dont use a hardcoded db id public static ReadOnlySpan Suffix => "0__:"u8; } } From 522e2a32af5d0519e0d12fd129da64b375f7f218 Mon Sep 17 00:00:00 2001 From: Joshua Siwek Date: Sat, 15 Mar 2025 01:43:07 -0700 Subject: [PATCH 09/13] wip: Pubish keyspace notification for following: DEL HINCRBYFLOAT HINCRBY HPERSIST INCRBYFLOAT INCR, DECR, INCRBY, DECRBY LINSERT LSET LTRIM MSET SETRANGE SET SMOVE --- libs/server/Resp/ArrayCommands.cs | 7 ++++++- libs/server/Resp/BasicCommands.cs | 4 +++- libs/server/Resp/CmdStrings.cs | 12 ++++++++++++ libs/server/Resp/Objects/HashCommands.cs | 13 +++++++++++++ libs/server/Resp/Objects/ListCommands.cs | 4 ++++ libs/server/Resp/Objects/SetCommands.cs | 3 +++ 6 files changed, 41 insertions(+), 2 deletions(-) diff --git a/libs/server/Resp/ArrayCommands.cs b/libs/server/Resp/ArrayCommands.cs index 9b506705a61..a6871f91abb 100644 --- a/libs/server/Resp/ArrayCommands.cs +++ b/libs/server/Resp/ArrayCommands.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using Garnet.common; +using Garnet.server.KeyspaceNotifications; using Tsavorite.core; namespace Garnet.server @@ -167,6 +168,7 @@ private bool NetworkMSET(ref TGarnetApi storageApi) var key = parseState.GetArgSliceByRef(c).SpanByte; var val = parseState.GetArgSliceByRef(c + 1).SpanByte; _ = storageApi.SET(ref key, ref val); + PublishKeyspaceNotification(KeyspaceNotificationType.String, ref parseState.GetArgSliceByRef(c), CmdStrings.set); } while (!RespWriteUtils.TryWriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) SendAndReset(); @@ -196,7 +198,7 @@ private bool NetworkMSETNX(ref TGarnetApi storageApi) private bool NetworkDEL(ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - int keysDeleted = 0; + int keysDeleted = 0; for (int c = 0; c < parseState.Count; c++) { var key = parseState.GetArgSliceByRef(c).SpanByte; @@ -204,7 +206,10 @@ private bool NetworkDEL(ref TGarnetApi storageApi) // This is only an approximate count because the deletion of a key on disk is performed as a blind tombstone append if (status == GarnetStatus.OK) + { keysDeleted++; + PublishKeyspaceNotification(KeyspaceNotificationType.Generic, ref parseState.GetArgSliceByRef(c), CmdStrings.del); + } } while (!RespWriteUtils.TryWriteInt32(keysDeleted, ref dcurr, dend)) diff --git a/libs/server/Resp/BasicCommands.cs b/libs/server/Resp/BasicCommands.cs index 62db3b5ee61..13c3043238e 100644 --- a/libs/server/Resp/BasicCommands.cs +++ b/libs/server/Resp/BasicCommands.cs @@ -341,7 +341,7 @@ private bool NetworkSetRange(ref TGarnetApi storageApi) var output = ArgSlice.FromPinnedSpan(outputBuffer); storageApi.SETRANGE(key, ref input, ref output); - + PublishKeyspaceNotification(KeyspaceNotificationType.String, ref key, CmdStrings.setrange); while (!RespWriteUtils.TryWriteIntegerFromBytes(outputBuffer.Slice(0, output.Length), ref dcurr, dend)) SendAndReset(); @@ -766,6 +766,7 @@ private bool NetworkIncrement(RespCommand cmd, ref TGarnetApi storag case OperationError.SUCCESS: while (!RespWriteUtils.TryWriteIntegerFromBytes(outputBuffer.Slice(0, output.Length), ref dcurr, dend)) SendAndReset(); + PublishKeyspaceNotification(KeyspaceNotificationType.String, ref key, CmdStrings.incrby); break; case OperationError.INVALID_TYPE: while (!RespWriteUtils.TryWriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) @@ -809,6 +810,7 @@ private bool NetworkIncrementByFloat(ref TGarnetApi storageApi) case OperationError.SUCCESS: while (!RespWriteUtils.TryWriteBulkString(outputBuffer.Slice(0, output.Length), ref dcurr, dend)) SendAndReset(); + PublishKeyspaceNotification(KeyspaceNotificationType.String, ref key, CmdStrings.incrbyfloat); break; case OperationError.INVALID_TYPE: while (!RespWriteUtils.TryWriteError(CmdStrings.RESP_ERR_NOT_VALID_FLOAT, ref dcurr, diff --git a/libs/server/Resp/CmdStrings.cs b/libs/server/Resp/CmdStrings.cs index 06fcda951ac..a3d178f2495 100644 --- a/libs/server/Resp/CmdStrings.cs +++ b/libs/server/Resp/CmdStrings.cs @@ -23,6 +23,7 @@ static partial class CmdStrings public static ReadOnlySpan get => "get"u8; public static ReadOnlySpan SET => "SET"u8; public static ReadOnlySpan set => "set"u8; + public static ReadOnlySpan del => "del"u8; public static ReadOnlySpan REWRITE => "REWRITE"u8; public static ReadOnlySpan rewrite => "rewrite"u8; public static ReadOnlySpan CONFIG => "CONFIG"u8; @@ -150,6 +151,17 @@ static partial class CmdStrings public static ReadOnlySpan TIMEOUT => "TIMEOUT"u8; public static ReadOnlySpan ERROR => "ERROR"u8; public static ReadOnlySpan INCRBY => "INCRBY"u8; + public static ReadOnlySpan incrby => "incrby"u8; + public static ReadOnlySpan hincrby => "hincrby"u8; + public static ReadOnlySpan hincrbyfloat => "hincrbyfloat"u8; + public static ReadOnlySpan incrbyfloat => "incrbyfloat"u8; + public static ReadOnlySpan hpersist => "hpersist"u8; + public static ReadOnlySpan linsert => "linsert"u8; + public static ReadOnlySpan lset => "lset"u8; + public static ReadOnlySpan ltrim => "ltrim"u8; + public static ReadOnlySpan setrange => "setrange"u8; + public static ReadOnlySpan srem => "srem"u8; + public static ReadOnlySpan ssadd => "ssadd"u8; public static ReadOnlySpan NOGET => "NOGET"u8; /// diff --git a/libs/server/Resp/Objects/HashCommands.cs b/libs/server/Resp/Objects/HashCommands.cs index 6bfbe0add35..f3e8a24ce3c 100644 --- a/libs/server/Resp/Objects/HashCommands.cs +++ b/libs/server/Resp/Objects/HashCommands.cs @@ -4,6 +4,7 @@ using System; using System.Text; using Garnet.common; +using Garnet.server.KeyspaceNotifications; using Tsavorite.core; namespace Garnet.server @@ -566,6 +567,17 @@ private unsafe bool HashIncrement(RespCommand command, ref TGarnetAp break; default: ProcessOutputWithHeader(outputFooter.SpanByteAndMemory); + switch (op) + { + case HashOperation.HINCRBY: + PublishKeyspaceNotification(KeyspaceNotificationType.Hash, ref parseState.GetArgSliceByRef(0), CmdStrings.hincrby); + break; + case HashOperation.HINCRBYFLOAT: + PublishKeyspaceNotification(KeyspaceNotificationType.Hash, ref parseState.GetArgSliceByRef(0), CmdStrings.hincrbyfloat); + break; + default: + break; + } break; } return true; @@ -820,6 +832,7 @@ private unsafe bool HashPersist(ref TGarnetApi storageApi) } break; default: + PublishKeyspaceNotification(KeyspaceNotificationType.Hash, ref parseState.GetArgSliceByRef(0), CmdStrings.hpersist); ProcessOutputWithHeader(outputFooter.SpanByteAndMemory); break; } diff --git a/libs/server/Resp/Objects/ListCommands.cs b/libs/server/Resp/Objects/ListCommands.cs index 50412fe2615..22b8d52740c 100644 --- a/libs/server/Resp/Objects/ListCommands.cs +++ b/libs/server/Resp/Objects/ListCommands.cs @@ -4,6 +4,7 @@ using System; using System.Text; using Garnet.common; +using Garnet.server.KeyspaceNotifications; using Tsavorite.core; namespace Garnet.server @@ -508,6 +509,7 @@ private bool ListTrim(ref TGarnetApi storageApi) // no need to process output, just send OK while (!RespWriteUtils.TryWriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) SendAndReset(); + PublishKeyspaceNotification(KeyspaceNotificationType.List, ref parseState.GetArgSliceByRef(0), CmdStrings.ltrim); break; } @@ -667,6 +669,7 @@ private bool ListInsert(ref TGarnetApi storageApi) //process output while (!RespWriteUtils.TryWriteInt32(output.result1, ref dcurr, dend)) SendAndReset(); + PublishKeyspaceNotification(KeyspaceNotificationType.List, ref parseState.GetArgSliceByRef(0), CmdStrings.linsert); break; case GarnetStatus.NOTFOUND: while (!RespWriteUtils.TryWriteDirect(CmdStrings.RESP_RETURN_VAL_0, ref dcurr, dend)) @@ -895,6 +898,7 @@ public bool ListSet(ref TGarnetApi storageApi) case GarnetStatus.OK: //process output ProcessOutputWithHeader(outputFooter.SpanByteAndMemory); + PublishKeyspaceNotification(KeyspaceNotificationType.List, ref parseState.GetArgSliceByRef(0), CmdStrings.lset); break; case GarnetStatus.NOTFOUND: while (!RespWriteUtils.TryWriteError(CmdStrings.RESP_ERR_GENERIC_NOSUCHKEY, ref dcurr, dend)) diff --git a/libs/server/Resp/Objects/SetCommands.cs b/libs/server/Resp/Objects/SetCommands.cs index 6c130fda1d7..cae94fc46bf 100644 --- a/libs/server/Resp/Objects/SetCommands.cs +++ b/libs/server/Resp/Objects/SetCommands.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Text; using Garnet.common; +using Garnet.server.KeyspaceNotifications; using Tsavorite.core; namespace Garnet.server @@ -606,6 +607,8 @@ private unsafe bool SetMove(ref TGarnetApi storageApi) switch (status) { case GarnetStatus.OK: + PublishKeyspaceNotification(KeyspaceNotificationType.Set, ref sourceKey, CmdStrings.srem); + PublishKeyspaceNotification(KeyspaceNotificationType.Set, ref destinationKey, CmdStrings.ssadd); while (!RespWriteUtils.TryWriteInt32(output, ref dcurr, dend)) SendAndReset(); break; From 33b4b01411f6ae6a3739c151c8140aa149d4c89b Mon Sep 17 00:00:00 2001 From: Joshua Siwek Date: Sat, 15 Mar 2025 01:47:09 -0700 Subject: [PATCH 10/13] todo hints --- libs/server/Resp/CmdStrings.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/server/Resp/CmdStrings.cs b/libs/server/Resp/CmdStrings.cs index a3d178f2495..f0c056466d8 100644 --- a/libs/server/Resp/CmdStrings.cs +++ b/libs/server/Resp/CmdStrings.cs @@ -151,6 +151,7 @@ static partial class CmdStrings public static ReadOnlySpan TIMEOUT => "TIMEOUT"u8; public static ReadOnlySpan ERROR => "ERROR"u8; public static ReadOnlySpan INCRBY => "INCRBY"u8; + // TODO: move to another file or region for example KeyspaceNotificationEventStrings public static ReadOnlySpan incrby => "incrby"u8; public static ReadOnlySpan hincrby => "hincrby"u8; public static ReadOnlySpan hincrbyfloat => "hincrbyfloat"u8; @@ -162,6 +163,7 @@ static partial class CmdStrings public static ReadOnlySpan setrange => "setrange"u8; public static ReadOnlySpan srem => "srem"u8; public static ReadOnlySpan ssadd => "ssadd"u8; + // TODO: end public static ReadOnlySpan NOGET => "NOGET"u8; /// From dcc880c65f1ea3ed6f24d1f02b8cc651dd22e98b Mon Sep 17 00:00:00 2001 From: Joshua Siwek Date: Sun, 16 Mar 2025 09:30:55 -0700 Subject: [PATCH 11/13] use unsafe modifier; publish to keyspace before sending commandresult --- .../KeyspaceNotifications/KeyspaceNotificationPublisher.cs | 2 +- libs/server/Resp/BasicCommands.cs | 4 ++-- libs/server/Resp/Objects/ListCommands.cs | 2 +- libs/server/Resp/Objects/SetCommands.cs | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs b/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs index 729c7355eed..3d11ad63f24 100644 --- a/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs +++ b/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs @@ -4,7 +4,7 @@ namespace Garnet.server { - internal sealed partial class RespServerSession : ServerSessionBase + internal sealed unsafe partial class RespServerSession : ServerSessionBase { // TODO: use same parameter for key and keyevent; pass keyevent as ref; test performance; avoid concatSpans and remove concatenated Spans; check access modifiers internal void PublishKeyspaceNotification(KeyspaceNotificationType keyspaceNotificationType, ref ArgSlice key, ReadOnlySpan keyevent) diff --git a/libs/server/Resp/BasicCommands.cs b/libs/server/Resp/BasicCommands.cs index 13c3043238e..83e73313936 100644 --- a/libs/server/Resp/BasicCommands.cs +++ b/libs/server/Resp/BasicCommands.cs @@ -291,11 +291,11 @@ private bool NetworkSET(ref TGarnetApi storageApi) storageApi.SET(ref key, ref value); + PublishKeyspaceNotification(KeyspaceNotificationType.Set, ref parseState.GetArgSliceByRef(0), CmdStrings.set); + while (!RespWriteUtils.TryWriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) SendAndReset(); - PublishKeyspaceNotification(KeyspaceNotificationType.Set, ref parseState.GetArgSliceByRef(0), CmdStrings.set); - return true; } diff --git a/libs/server/Resp/Objects/ListCommands.cs b/libs/server/Resp/Objects/ListCommands.cs index 22b8d52740c..8f9a527e74c 100644 --- a/libs/server/Resp/Objects/ListCommands.cs +++ b/libs/server/Resp/Objects/ListCommands.cs @@ -505,11 +505,11 @@ private bool ListTrim(ref TGarnetApi storageApi) SendAndReset(); break; default: + PublishKeyspaceNotification(KeyspaceNotificationType.List, ref parseState.GetArgSliceByRef(0), CmdStrings.ltrim); //GarnetStatus.OK or NOTFOUND have same result // no need to process output, just send OK while (!RespWriteUtils.TryWriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) SendAndReset(); - PublishKeyspaceNotification(KeyspaceNotificationType.List, ref parseState.GetArgSliceByRef(0), CmdStrings.ltrim); break; } diff --git a/libs/server/Resp/Objects/SetCommands.cs b/libs/server/Resp/Objects/SetCommands.cs index cae94fc46bf..21c5ee8340b 100644 --- a/libs/server/Resp/Objects/SetCommands.cs +++ b/libs/server/Resp/Objects/SetCommands.cs @@ -607,6 +607,7 @@ private unsafe bool SetMove(ref TGarnetApi storageApi) switch (status) { case GarnetStatus.OK: + // TODO: check usage of output var to send correct keyspace notification PublishKeyspaceNotification(KeyspaceNotificationType.Set, ref sourceKey, CmdStrings.srem); PublishKeyspaceNotification(KeyspaceNotificationType.Set, ref destinationKey, CmdStrings.ssadd); while (!RespWriteUtils.TryWriteInt32(output, ref dcurr, dend)) From 90900b53ed5325b22d46d4a6b4ce9ee567c41b33 Mon Sep 17 00:00:00 2001 From: Joshua Siwek Date: Mon, 17 Mar 2025 10:36:00 -0700 Subject: [PATCH 12/13] rename option; Remove unused IKeyspaceNotificationOptions --- libs/host/Configuration/Options.cs | 4 ++-- .../KeyspaceNotifications/IKeyspaceNotificationOptions.cs | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) delete mode 100644 libs/server/KeyspaceNotifications/IKeyspaceNotificationOptions.cs diff --git a/libs/host/Configuration/Options.cs b/libs/host/Configuration/Options.cs index 41dc7a9d4df..7d24b24a99b 100644 --- a/libs/host/Configuration/Options.cs +++ b/libs/host/Configuration/Options.cs @@ -183,8 +183,8 @@ internal sealed class Options [Option("cluster-password", Required = false, HelpText = "Password to authenticate intra-cluster communication with.")] public string ClusterPassword { get; set; } - [Option("notify-keyspace-events-arguments", Required = false, HelpText = "Keyspace Notification argument string.")] - public string NotifiyKeyspaceEventsArguments { get; set; } + [Option("notify-keyspace-events", Required = false, HelpText = "Keyspace Notification argument string.")] + public string NotifyKeyspaceEventsArguments { get; set; } [FilePathValidation(true, true, false)] [Option("acl-file", Required = false, HelpText = "External ACL user file.")] diff --git a/libs/server/KeyspaceNotifications/IKeyspaceNotificationOptions.cs b/libs/server/KeyspaceNotifications/IKeyspaceNotificationOptions.cs deleted file mode 100644 index ce3a1e735d1..00000000000 --- a/libs/server/KeyspaceNotifications/IKeyspaceNotificationOptions.cs +++ /dev/null @@ -1,8 +0,0 @@ - -namespace Garnet.server.KeyspaceNotifications -{ - public interface IKeyspaceNotificationOptions - { - KeyspaceNotificationType KeyspaceNotificationType { get; set; } - } -} From 9a775c1c6e416c99fa17ae2f24ac564e2a3b3f2a Mon Sep 17 00:00:00 2001 From: Joshua Siwek Date: Wed, 19 Mar 2025 08:54:54 +0100 Subject: [PATCH 13/13] removed unused using directives --- .../KeyspaceNotifications/KeyspaceNotificationPublisher.cs | 1 - .../KeyspaceNotifications/KeyspaceNotificationStrings.cs | 4 ---- libs/server/KeyspaceNotifications/KeyspaceNotificationType.cs | 4 ---- 3 files changed, 9 deletions(-) diff --git a/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs b/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs index 3d11ad63f24..009eb920a6e 100644 --- a/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs +++ b/libs/server/KeyspaceNotifications/KeyspaceNotificationPublisher.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using Garnet.server.KeyspaceNotifications; namespace Garnet.server diff --git a/libs/server/KeyspaceNotifications/KeyspaceNotificationStrings.cs b/libs/server/KeyspaceNotifications/KeyspaceNotificationStrings.cs index e4d27188650..3717cb54238 100644 --- a/libs/server/KeyspaceNotifications/KeyspaceNotificationStrings.cs +++ b/libs/server/KeyspaceNotifications/KeyspaceNotificationStrings.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Garnet.server.KeyspaceNotifications { diff --git a/libs/server/KeyspaceNotifications/KeyspaceNotificationType.cs b/libs/server/KeyspaceNotifications/KeyspaceNotificationType.cs index 6bcea50e55e..a4b196b5d8a 100644 --- a/libs/server/KeyspaceNotifications/KeyspaceNotificationType.cs +++ b/libs/server/KeyspaceNotifications/KeyspaceNotificationType.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Garnet.server.KeyspaceNotifications {