cristipufu/aspnetcore-redis-rate-limiting

Redis Error: WRONGTYPE Operation against a key holding the wrong kind of value

avia-w opened this issue · 1 comments

avia-w commented

I wanted to use this library and did some testing. This was my code:

`services.AddRateLimiter(options =>
{
options.OnRejected = (context, _) =>
{
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
{
context.HttpContext.Response.Headers.RetryAfter =
((int)retryAfter.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo);
}

    context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
    context.HttpContext.Response.WriteAsync("Too many requests. Please try again later.");

    return new ValueTask();
};
options.GlobalLimiter = PartitionedRateLimiter.CreateChained(
        PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
        {
            string clientId = GetClientFromContext(httpContext);
            return RedisRateLimitPartition.GetFixedWindowRateLimiter(clientId, _ =>
               new RedisFixedWindowRateLimiterOptions
               {
                   ConnectionMultiplexerFactory = () => connectionMultiplexer,
                   PermitLimit = 2,
                   Window = TimeSpan.FromSeconds(1)                                   
               });
        }),
        PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
        {
            string clientId = GetClientFromContext(httpContext);
            return RateLimitPartition.GetNoLimiter(clientId);
        }),
        PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
        {
            string clientId = GetClientFromContext(httpContext);
            return RedisRateLimitPartition.GetConcurrencyRateLimiter(clientId, _ =>
               new RedisConcurrencyRateLimiterOptions
               {
                   ConnectionMultiplexerFactory = () => connectionMultiplexer,
                   PermitLimit = 5,
                   QueueLimit = 10
               });
        })
        );

});`

It worked as expected for a while, and at some point I started getting this error from Redis:

"ERR Error running script (call to f_a08ae7b80fbefc1d082f3c02f112bb4f38a59fa7): @user_script:8: WRONGTYPE Operation against a key holding the wrong kind of value"

I looked in Redis monitor and saw the error as a result of this command:
"evalsha" "a08ae7b80fbefc1d082f3c02f112bb4f38a59fa7" "3" "rl:{tabdevweb.ini}" "rl:{tabdevweb.ini}:q" "rl:{tabdevweb.ini}:stats" "2" "1000" "0" "1693226580" "rl:{tabdevweb.ini}" "rl:{tabdevweb.ini}:q" "b2ec810f-80ac-4c50-b2b9-e46aad0de33c" "rl:{tabdevweb.ini}:stats"

After a while (next day) the error disappeared and now its working fine again.

Thanks

Hi @avia-w, thanks for taking the time to report this issue.

Given the evalsha command in the error, the issue is arising from a Lua script that's being executed in Redis. The SHA hash (a08ae7b80fbefc1d082f3c02f112bb4f38a59fa7) references this Lua script, and something inside that script at line 8 is causing the problem, eg: https://github.com/cristipufu/aspnetcore-redis-rate-limiting/blob/master/src/RedisRateLimiting/Concurrency/RedisConcurrencyManager.cs#L24C13-L24C87

The error WRONGTYPE Operation against a key holding the wrong kind of value would occur if @rate_limit_key is not holding a sorted set (zset).

It looks like you're using multiple rate limiters with the same cache key (fixed window + concurrency with the same clientId key).

You need to differentiate between these rate limiters, you could, for example, add a prefix to the clientId identifier:

return RedisRateLimitPartition.GetFixedWindowRateLimiter($"fw{clientId}", _ =>
               new RedisFixedWindowRateLimiterOptions
               {
                   ConnectionMultiplexerFactory = () => connectionMultiplexer,
                   PermitLimit = 2,
                   Window = TimeSpan.FromSeconds(1)                                   
               });
               
return RedisRateLimitPartition.GetConcurrencyRateLimiter($"cc{clientId}", _ =>
               new RedisConcurrencyRateLimiterOptions
               {
                   ConnectionMultiplexerFactory = () => connectionMultiplexer,
                   PermitLimit = 5,
                   QueueLimit = 10
               });