Search code examples
c#redisluastackexchange.redisredistimeseries

Lua redis() command arguments must be strings or integers - however the same command works at redis-cli prompt


There is a command, which works perfectly at my Redis Azure Cache "redis-cli" prompt (an Enterprise E10 instance with the TimeSeries module enabled):

EVAL "redis.call('TS.ADD', KEYS[1], '*', ARGV[1], 'RETENTION', ARGV[2], 'ON_DUPLICATE', 'SUM', 'ENCODING', 'UNCOMPRESSED'); return redis.call('TS.RANGE', KEYS[1], '-', '+')" 1 key1 1 1800000
1) 1) (integer) 1693323424958
   2) 1
2) 1) (integer) 1693323485951
   2) 1
3) 1) (integer) 1693323528702
   2) 1
4) 1) (integer) 1693323531678
   2) 1

As you can see, I have called it 4 times, which has added 4 timestamps, all with the value 1 to the "key1" time series.

However, when I try to call the same command in a simple C# .Net console app:

private static readonly TimeSpan RETENTION_TIME = TimeSpan.FromMinutes(30);

private const string LUA_SCRIPT = @"redis.call('TS.ADD', KEYS[1], '*', ARGV[1], 'RETENTION', ARGV[2], 'ON_DUPLICATE', 'SUM', 'ENCODING', 'UNCOMPRESSED'); return redis.call('TS.RANGE', KEYS[1], '-', '+')";

private static async Task<int> AddTimestampReturnSumAsync(IDatabase db, string key, int retentionTime)
{
    RedisResult result = await db.ScriptEvaluateAsync(LUA_CMD, new RedisKey[] { key }, new RedisValue[] { retentionTime });
    Console.WriteLine(JsonSerializer.Serialize(result));
    return 0; // a placeholder, I actually want to return a sum of the values
}

Then the following line:

int sum = await AddTimestampReturnSumAsync(redis.GetDatabase(), "key1", (int)RETENTION_TIME.TotalMilliseconds);

fails with

StackExchange.Redis.RedisServerException: 'ERR Error running script (call to f_503fcb14af1e60051a54d3617d5265f753dfe423): @user_script:1: @user_script: 1: Lua redis() command arguments must be strings or integers'

exception screenshot

Trying to debug the issue, I have tried the following 2 strings and they just work as expected:

private const string LUA_SCRIPT = @"return KEYS[1]"; // returns a RedisResult with "key1"

private const string LUA_SCRIPT = @"return ARGV[1]"; // returns a RedisResult with 1800000

Is the issue maybe in the Lua code?

I am just trying to call TS.ADD and the TS.RANGE as a single atomic unit from my C# app.

Currently, I am studing the ScriptingTests.cs trying to understand what is different with my code...


Solution

  • I've got a helpful comment at the GitHub about me refering to ARGV[2] in the Lua code, while I had only supplied a single argument to the C# call of ScriptEvaluateAsync().

    After some struggling with time series my final Lua source code looks like this:

    // The script returns 0 if there are 20 or more timestamps in the past 30 min (1800000 ms).
    // Otherwise it adds a new timestamp to the time series at the key and returns 1.
    private const string LUA_SCRIPT =
    @"
    local sum = 0
    local now = redis.call('TIME')
    -- the start timestamp for TS.RANGE command in milliseconds
    local start = now[1] * 1000 + now[2] / 1000 - ARGV[1]
    local tsrange = redis.call('TS.RANGE', KEYS[1], start, '+')
    
    for i = 1, #tsrange do
        -- calculate a sum of all values stored in the time series, from start to now
        sum = sum + tsrange[i][2]['ok']
    
        -- if there are enough timestamps in the time period, then just return 0
        if (sum >= 20) then
            return 0
        end
    end
    
    -- otherwise add the current timestamp and the value 1 to the time series
    redis.call('TS.ADD', KEYS[1], '*', 1, 'RETENTION', ARGV[1], 'ON_DUPLICATE', 'SUM', 'ENCODING', 'UNCOMPRESSED')
    return 1
    ";
    

    And the C# call is here:

    private static async Task<bool> AddTimestampAsync(IDatabase db, string key, long retentionTime)
    {
        RedisResult result = await db.ScriptEvaluateAsync(LUA_SCRIPT, new RedisKey[] { key }, new RedisValue[] { retentionTime });
        // return true if the Lua script returns 1, otherwise false
        return result.Type == ResultType.Integer && (int)result == 1;
    }