Redis keyspace notifications with a C# example
Tags: redis, cache, pubsub, csharp
Published: 2023-04-23 21:37:30

## Intro
One of Redis' key features is its ability to trigger notifications based on key events happening in its keyspace, allowing for real-time updates. It could be a useful feature for constructing scalable and reliable systems that have to react quickly to changes in data.

In this blog post, we will explore Redis keyspace notifications and demonstrate how to use them in a C# application. We will cover the different types of events that can trigger notifications, how to configure Redis to send notifications, and how to receive and process notifications in a C# application using the `StackExchange.Redis` library.

## Redis configuration
I presume that a Redis instance is already operational locally or elsewhere and that Redis CLI access is already available to you.

By default keyspace event notifications are disabled and in order to enable it we need to execute the following command:

```cmd
config set notify-keyspace-events Kxge$
```

Each letter in the `Kxge$` argument represents a different type of Redis event that should be published to the Pub/Sub mechanism:
- K: Keyspace events for string commands (e.g. SET, GET)
- x: Keyspace events for expired events (e.g. expired keys)
- g: Keyspace events for generic commands (e.g. DEL, EXPIRE)
- e: Keyspace events for events related to keys that are being modified by an EVAL command or a Lua script
- $: Keyspace events for string commands received via Redis' LRANGE and LTRIM commands

This is pretty much it. Later, after the `C# example` part, we are going to execute a couple of more Redis CLI commands to set and expire a key.

## C# example
Let's create a simple console application and install the following NuGet package:

```cmd
StackExchange.Redis
```

Then we can create a new instance of the ConfigurationOptions class and set its EndPoints property to a single Redis endpoint with the address `localhost:6379`.

```csharp
var configurationOptions = new ConfigurationOptions
{
    EndPoints =
    {
        "localhost:6379"
    }
};
```

Right after we need to create a new ConnectionMultiplexer instance and obtain a pub/sub subscriber connection to the specified server.

```csharp
var connectionMultiplexer = await ConnectionMultiplexer.ConnectAsync(configurationOptions);
var subscriber = connectionMultiplexer.GetSubscriber();
```

Now the most interesting part. Here we are subscribing to Redis keyspace notifications. Specifically, it is subscribing to keyspace events that occur on Redis database 0 (zero) by using the `__keyspace@0__:*` prefix in the channel pattern.

```csharp
await subscriber.SubscribeAsync("__keyspace@0__:*", (channel, type) =>
{
    var key = GetKey(channel);

    switch (type)
    {
        case "set":
            Console.WriteLine($"Set: {key}");
            break;
        case "expire":
            Console.WriteLine($"Expire: {key}");
            break;
        case "expired":
            Console.WriteLine($"Expired: {key}");
            break;
    }
});
```

The `SubscribeAsync` method of the Redis subscriber object is used to subscribe to the keyspace events, and it takes a delegate function that will be called whenever a keyspace event occurs on a key that matches the subscription pattern.

The switch statement then handles each type of keyspace event that may occur and writes a message to the console indicating the type of event and the key that was affected by the event. In our example, we are catching only 3 types: set, expire, and expired. But there are many more, you can find the complete list on the Redis docs website.

To extract a key from a channel we can use a method something like this:

```csharp
static string GetKey(string channel)
{
    var index = channel.IndexOf(':');

    if (index >= 0 && index < channel.Length - 1)
    {
        return channel[(index + 1)..];
    }

    return channel;
}
```

Finally, let's add this:

```csharp
await subscriber.UnsubscribeAllAsync();
await connectionMultiplexer.DisposeAsync();
```

The `UnsubscribeAllAsync` method is called on the Redis subscriber object to unsubscribe from all the channels that have been subscribed to using the `SubscribeAsync` method. This method is used to terminate the subscription and stop listening for events.

The `DisposeAsync` method is then called on the ConnectionMultiplexer object to release any resources used by the Redis connection.

## Run and play
Now run the console app and execute the following two Redis CLU commands:

```cmd
set mykey 0
```

The command is used to set the value of a Redis key.

```cmd
expire mykey 10
```

And this one is used to set an expiration time (in seconds) for a Redis key.

Once the expire command is executed, Redis will start a timer for the key, and after 10 seconds, the key will be automatically removed from the database.

As a result, we will see this in our output console:

```
Set: mykey
Expire: mykey
Expired: mykey
```

## Disadvantages
There are some potential disadvantages to using this feature. For example, `lack of durability`: keyspace notifications are not guaranteed to be delivered to clients in the event of a network failure or server crash. This can lead to data loss or inconsistency.

## Practical use
Redis keyspace notifications is a feature that can be used in a variety of ways. Here are some practical uses of Redis keyspace notifications:
- Cache Invalidation: it can be used to invalidate cached data whenever the corresponding key is updated or deleted.
- Real-time Analytics: it can be used to trigger real-time analytics and reporting whenever new data is added to the database.

But let me know in the comments how you would use Redis keyspace notifications.

## Links
- [Redis keyspace notifications](https://redis.io/docs/manual/keyspace-notifications/)
- [StackExchange.Redis](https://github.com/StackExchange/StackExchange.Redis)
- [Complete C# example](https://gist.github.com/sayganov/a57c8290da9539495879cce10669aa2e)