dotnet / dotnet/aspnetcore

API Proposal: Redis GCRA native command with Lua fallback

Open
#67,874 0 comments 0 reactions 0 assignees View on GitHub
api-proposal api-suggestion area-middleware feature-rate-limit
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 6h
Merged PRs (30d)
290

Description

# API Proposal: Redis GCRA compatibility fallback

Hi @DeagleGross and @mgravell, following the compatibility discussion in #65792, I’ve written up this focused proposal for using the native Redis GCRA command with a Lua fallback.

Related discussion: https://github.com/dotnet/aspnetcore/issues/65792

## Background and Motivation

The Redis-backed GCRA limiter discussed in #65792 needs to work with Redis servers that do not expose the native `GCRA` command.

The proposed behavior is:

- Use the native Redis GCRA command when the connected server supports it.
- Otherwise, automatically use a Lua implementation with equivalent GCRA behavior.

This follows the precedent in `RedisOutputCacheStore`, which checks Redis server capabilities and selects either a native command or an equivalent Lua script.

## Proposed API

No additional public API is required for Redis server compatibility.

Native-command selection and Lua fallback are internal implementation details of `RedisGcraRateLimiter`. Applications continue to use the public Redis GCRA API proposed in #65792 without selecting an implementation.

The implementation is split into native and script partial-class files, with capability detection in the main limiter class.

### `RedisGcraRateLimiter.Script.cs`

```csharp
namespace Microsoft.AspNetCore.RateLimiting.StackExchangeRedis;

public sealed partial class RedisGcraRateLimiter
{
private async ValueTask ScriptAcquireAsync(
IDatabase database,
int permitCount,
CancellationToken cancellationToken)
{
}

private static RateLimitLease BuildScriptLease(RedisResult result)
{
}
}
```

### `RedisGcraRateLimiter.Native.cs`

```csharp
namespace Microsoft.AspNetCore.RateLimiting.StackExchangeRedis;

public sealed partial class RedisGcraRateLimiter
{
private async ValueTask GcraAcquireAsync(
IDatabase database,
int permitCount,
CancellationToken cancellationToken)
{
}

private static RateLimitLease BuildGcraLease(RedisResult result)
{
}
}
```

### `RedisGcraRateLimiter.cs`

```diff
public sealed partial class RedisGcraRateLimiter : RateLimiter
{
+ private bool _useNativeGcra;

+ private static bool SupportsNativeGcra(IServer server)
+ {
+ }

+ private void ValidateServerFeatures(IConnectionMultiplexer connection)
+ {
+ _useNativeGcra = true;
+ }

protected override async ValueTask AcquireAsyncCore(
int permitCount,
CancellationToken cancellationToken)
{
+ if (_useNativeGcra)
+ {
+ return await GcraAcquireAsync(database, permitCount, cancellationToken).ConfigureAwait(false);
+ }

+ return await ScriptAcquireAsync(database, permitCount, cancellationToken).ConfigureAwait(false);
}
}
```

These members are private and do not add to or change the public API surface proposed in #65792. They show how one public limiter API can support both newer and older Redis server versions.

## Usage Examples

Application configuration does not change based on the Redis server version:

```csharp
options.AddRedisGcraLimiter("per-tenant", redis =>
{
redis.Configuration = "localhost:6379";
redis.InstanceName = "myapp:ratelimit:";
redis.RequestsPerPeriod = 100;
redis.Period = TimeSpan.FromMinutes(1);
redis.MaxBurst = 10;
});
```

The limiter selects the native or Lua path internally after checking server capabilities.

## Alternative Designs

### Require native Redis GCRA

The limiter could throw when the Redis server does not support the native GCRA command. This would prevent applications using older Redis versions from using the Redis-backed GCRA limiter.

### Expose native or Lua selection as public configuration

The options could expose a public mode for selecting the implementation. This is not proposed because both paths implement the same GCRA behavior and the selection does not need to become part of the public contract.

## Risks

- The Lua fallback should match the native GCRA command's externally visible behavior, so applications receive consistent rate-limiting results regardless of which path is used.
- The limiter should use native GCRA only when the command is available on every Redis server that may handle the request; otherwise, it should use the Lua fallback.
- Lua scripting must be available when the connected Redis server does not support native GCRA.

Contributor guide

Open the contributing guide

Research direction

Start with the compatibility discussion in #65792 and the RedisOutputCacheStore capability-selection precedent. Then inspect RedisGcraRateLimiter.cs, RedisGcraRateLimiter.Script.cs, and RedisGcraRateLimiter.Native.cs to understand the proposed dispatch and lease builders. Done means native GCRA is selected only when supported, the Lua path covers older servers, and no public API changes are introduced.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, redis
Domain
backend, databases
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.