7

I have a key in the format:

Error.1
Error.24
Error.32

Using StackExchange.Redis, how would I go about KeyDelete on all keys that match the format Error.?

On another answer I've seen the LUA script:

EVAL "return redis.call('del', unpack(redis.call('keys', ARGV[1])))" 0 Error.*

But I'm unsure how to call this using Database.ScriptEvaluate()

Tom Gullen
  • 61,249
  • 84
  • 283
  • 456

3 Answers3

10

Just get all keys matching the pattern, iterate and delete, something like this:

using (var redisConnection = ConnectionMultiplexer.Connect(...))
{
    var server = redisConnection.GetServer(endpoint:...);

    if (server != null)
    {
         foreach (var key in server.Keys(pattern: "Error.*"))
         {
               redisConnection.Database.KeyDelete(key);
         }
    }
}

Later edit:

Example of setting up a Redis Connection: https://gist.github.com/cristipufu/9ad47caf3dba60d712484d0c880597b9

The multiplexer should be stored and reused, not disposed and recreated each time. https://stackexchange.github.io/StackExchange.Redis/Basics

Performance can be significantly increased by adjusting / specifying the pageSize parameter of the Keys call. Its default value is 10. Try 1,000.

StackExchange.Redis server.Keys(pattern:"IsVerySlow*")

Cristi Pufu
  • 9,002
  • 3
  • 37
  • 43
  • 1
    A word of caution. This code works, however you should have a `using` block for the connection, e.g. `using (var redisConnection = ConnectionMultiplexer.Connect(...)) { /* Other code inside the using block */ }`. Otherwise your connections might not be disposed of correctly and you'll end up with thread starvation as I've just encountered in one of my applications. I had Task Manager open and I could actually see the thread-pool growing with each request within this application while I was debugging. – Aaron Newton Mar 01 '17 at 03:16
  • 5
    @AaronNewton , redis is single threaded, so you ever only need one connection. Create a single static connection on app startup and then keep it forever. You won't need using if you do if this way, and in fact should not since creating and disposing connections is expensive and every millisecond counts in a caching provider. – JMD Jan 31 '18 at 16:50
  • Thank you JMD. Strangely enough, we were just looking at this code as it's encountering some performance issues. – Aaron Newton Feb 01 '18 at 06:14
  • 3
    The [documentation](https://stackexchange.github.io/StackExchange.Redis/Basics) explicitly says that the multiplexer **should be stored and reused**, not disposed and recreated each time. – molnarm Jul 05 '18 at 05:48
  • 1
    This isn't the first time I've seen this solution, but it seems clunky to me. You have to request keys from a specific server? Would the be a problem when clustered? Also, is there any notable performance hit with this method? – Sinaesthetic Oct 09 '18 at 21:11
  • 1
    Performance can be significantly increased by adjusting / specifying the `pageSize` parameter of `Keys` call. Its default value is 10. Try 1,000. Here is source of information: https://stackoverflow.com/questions/26438736/stackexchange-redis-server-keyspatternisveryslow#answer-26440148 – ttugates Aug 13 '19 at 14:48
8

And the new version with C# Asynchronous Streaming via IAsyncEnumerable available in Redis.StackExchange v2.1.0-preview.23 and above.

NOTE: this version uses SCAN instead of KEYS if your Redis Instance supports that feature. This is a huge performance boost. You should also make sure your instance of ConnectionMultiplexer is a singleton - i.e. same instance used for the lifetime of the app.

I should also callout that Redis wildcard support allows for pretty flexible patterns. In my implementation (below), I only had a need to have a '*' at the end of the key, so that is all it is coded for. If you need additional wildcard support, you can implement the Redis wildcard supported glob-style patterns here:

  • h?llo matches hello, hallo and hxllo
  • h*llo matches hllo and heeeello
  • h[ae]llo matches hello and hallo, but not hillo
  • h[^e]llo matches hallo, hbllo, ... but not hello
  • h[a-b]llo matches hallo and hbllo

Use \ to escape special characters if you want to match them verbatim.

using Microsoft.Extensions.Caching.Distributed;
using StackExchange.Redis;

private readonly IDistributedCache _cache;
private readonly IConnectionMultiplexer _connectionMultiplexer;

public CacheRepository(IDistributedCache cache, IConnectionMultiplexer connectionMultiplexer)
{
    _cache = cache;
    _connectionMultiplexer = connectionMultiplexer;
}

public async Task RemoveWithWildCardAsync(string keyRoot)
{
    if (string.IsNullOrWhiteSpace(keyRoot))
        throw new ArgumentException("Value cannot be null or whitespace.", nameof(keyRoot));

    // get all the keys* and remove each one
    await foreach (var key in GetKeysAsync(keyRoot + "*"))
    {
        await _cache.RemoveAsync(key);
    }
}

public async IAsyncEnumerable<string> GetKeysAsync(string pattern)
{
    if (string.IsNullOrWhiteSpace(pattern))
        throw new ArgumentException("Value cannot be null or whitespace.", nameof(pattern));

    foreach (var endpoint in _connectionMultiplexer.GetEndPoints())
    {
        var server = _connectionMultiplexer.GetServer(endpoint);
        await foreach (var key in server.KeysAsync(pattern: pattern))
        {
            yield return key.ToString();
        }
    }
}

public IEnumerable<RedisFeatures> GetRedisFeatures()
{
    foreach (var endpoint in _connectionMultiplexer.GetEndPoints())
    {
        var server = _connectionMultiplexer.GetServer(endpoint);
        yield return server.Features;
    }
}
Dave Black
  • 7,305
  • 2
  • 52
  • 41
  • Hi @Dave, do you have any perf benchmark on this? thanks. – Sunny Sharma Jul 08 '20 at 07:54
  • I'm sorry - I don't have any benchmarks. Please share them if you end up creating some. – Dave Black Jul 08 '20 at 17:05
  • @DaveBlack In Microsoft.extensions.caching.stackexchangeredis.3.1.18 KeysAsync method not found. Only Keys method is available which returns IEnumerable. Are you meaning that KeysAsnyc was available in Redis.StackExchange v2.1.0-preview.23 ? – Rasel Jan 26 '22 at 09:24
  • @Rasel - yes, Redis.StackExchange. It's possible they've added it to the .NET 6 version of Microsoft.Extensions.Caching.StackExchangeRedis but I haven't checked. – Dave Black Jan 26 '22 at 15:52
  • @DaveBlack I am using .net Core 3.1 and Microsoft.extensions.caching.stackexchangeredis.3.1.18. BTW, another issue I am facing at runtime, that is Autofac is unable to inject instance for IConnectionMultiplexer. Can you say what I need to add in IServiceCollection on startup? – Rasel Jan 27 '22 at 04:15
  • Did you add both services.AddStackExchangeRedisCache and services.AddSingleton(multiplexer) in your startup ? – Rasel Jan 27 '22 at 12:08
  • @Rasel yes, I have both. – Dave Black Jan 27 '22 at 16:53
  • @Rasel https://stackoverflow.com/a/56493186/17351540 – sharpc Aug 05 '22 at 12:01
1

I spend some time not getting any key using accepted answer

For pattern, use something like this.

server.Keys(pattern: "*Error.*")

You should add stars before and after your pattern.

Sajad Jalilian
  • 136
  • 1
  • 7