0

In Azure Redis Enterprise with TimeSeries module enabled I have the following time series with 4 values of 1 at the key "key1":

$ TS.RANGE key1 - +

1) 1) (integer) 1693381431951
   2) 1
2) 1) (integer) 1693381435201
   2) 1
3) 1) (integer) 1693381436720
   2) 1
4) 1) (integer) 1693381438037
   2) 1

I am able to retrieve the same results by a Lua script:

$ EVAL "return redis.call('TS.RANGE', KEYS[1], '-', '+')" 1 key1

1) 1) (integer) 1693381431951
   2) 1
2) 1) (integer) 1693381435201
   2) 1
3) 1) (integer) 1693381436720
   2) 1
4) 1) (integer) 1693381438037
   2) 1

My question is how to sum up the values (and to get a 4 as result in the shown case)?

I am trying the following Lua code:

$ EVAL "local sum = 0; for stamp, val in redis.call('TS.RANGE', KEYS[1], '-', '+') do sum = sum + val end; return sum" 1 key1

(error) ERR Error running script (call to f_28243bc2b451f1770c76c1d7fcce23e7285f3baa): @user_script:1: user_script:1: attempt to call a table value

Doesn't TS.RANGE return pairs of timestamp and numeric values?

My background is that I am calling ScriptEvaluateAsync() in my C# application and would like to sum up all values in a time series, when given a key.

Alexander Farber
  • 21,519
  • 75
  • 241
  • 416
  • 1
    More generally, that Lua error indicates you have provided a non-callable *table* as the first value in *explist* to the [generic `for`](https://www.lua.org/manual/5.1/manual.html#2.4.5), where an iterator (function) was expected. Create iterators for tables by using [`pairs`](https://www.lua.org/manual/5.1/manual.html#pdf-pairs) and [`ipairs`](https://www.lua.org/manual/5.1/manual.html#pdf-ipairs) - for map-like and array-like tables, respectively. – Oka Aug 30 '23 at 12:14

2 Answers2

1

This can get a bit messy and the Redis Lua debugger can help (https://redis.io/docs/interact/programmability/lua-debugging/).

Here's a solution that will do what you need but the Lua could possibly be optimized - I'm not a Lua expert :)

local sum = 0; 
local rangeResponse = redis.call('ts.range', KEYS[1], '-', '+')
for _, item in pairs(rangeResponse) do
  local val = tonumber(item[2]["ok"])
  sum = sum + val
end
return sum

This unpacks the TS.RANGE response and sums the values. Let's set up some data to use it:

127.0.0.1:6379> 4 ts.add key1 * 1
(integer) 1693393058028
(integer) 1693393058034
(integer) 1693393058035
(integer) 1693393058036

and check what we have:

127.0.0.1:6379> TS.RANGE key1 - +
1) 1) (integer) 1693393058028
   2) 1
2) 1) (integer) 1693393058034
   2) 1
3) 1) (integer) 1693393058035
   2) 1
4) 1) (integer) 1693393058036
   2) 1

Now let's store the script in the redis server (I've put the script in a file called sumts.lua:

$ redis-cli -x script load < ~/Desktop/sumts.lua
"caf0749de1e79a8955e212a2fb0ea34e6c76a28c"

We get the SHA for the script back and can use that to call it:

127.0.0.1:6379> evalsha caf0749de1e79a8955e212a2fb0ea34e6c76a28c 1 key1
(integer) 4

we get the integer response 4, the sum of the values in the time series.

Here's an example of how to run through this script in the Lua debugger and see local variables:

$ redis-cli --ldb --eval ~/Desktop/sumts.lua key1

then...

Lua debugging session started, please use:
quit    -- End the session.
restart -- Restart the script in debug mode again.
help    -- Show Lua script debugging commands.

* Stopped at 1, stop reason = step over
-> 1   local sum = 0;
lua debugger> n
* Stopped at 2, stop reason = step over
-> 2   local rangeResponse = redis.call('ts.range', KEYS[1], '-', '+')
lua debugger> n
<redis> ts.range key1 - +
<reply> [[1693393058028,"+1"],[1693393058034,"+1"],[1693393058035,"+1"],[1693393058036,"+1"]]
* Stopped at 3, stop reason = step over
-> 3   for _, item in pairs(rangeResponse) do
lua debugger> n
* Stopped at 4, stop reason = step over
-> 4     local val = tonumber(item[2]["ok"])
lua debugger> n
* Stopped at 5, stop reason = step over
-> 5     sum = sum + val
lua debugger> print
<value> sum = 0
<value> rangeResponse = {{1.69339e+12; {["ok"]="1"}}; {1.69339e+12; {["ok"]="1"}}; {1.69339e+12; {["ok"]="1"}}; {1.69339e+12; {["ok"]="1"}}}
<value> (for generator) = "function@0x562d34cb1590"
<value> (for state) = {{1.69339e+12; {["ok"]="1"}}; {1.69339e+12; {["ok"]="1"}}; {1.69339e+12; {["ok"]="1"}}; {1.69339e+12; {["ok"]="1"}}}
<value> (for control) = 1
<value> _ = 1
<value> item = {1.69339e+12; {["ok"]="1"}}
<value> val = 1

You can keep using n to step through the loop.

Hope this helps.

Alexander Farber
  • 21,519
  • 75
  • 241
  • 416
Simon Prickett
  • 3,838
  • 1
  • 13
  • 26
1

By looking at the cjson.encode(tsrange) I have come up with the following solution, which I also would like to share:

EVAL "local sum = 0; local tsrange = redis.call('TS.RANGE', KEYS[1], '-', '+'); for i = 1, #tsrange do sum = sum + tsrange[i][2]['ok'] end; return sum" 1 key1

And then I call it in my C# app as:

private const string LUA_SCRIPT = @"local sum = 0; local tsrange = redis.call('TS.RANGE', KEYS[1], '-', '+'); for i = 1, #tsrange do sum = sum + tsrange[i][2]['ok'] end; return sum";

private static async Task<int> CalcSumAsync(IDatabase db, string key)
{
    RedisResult result = await db.ScriptEvaluateAsync(LUA_SCRIPT, new RedisKey[] { key });
    return result.Type == ResultType.Integer ? (int)result : 0;
}
Alexander Farber
  • 21,519
  • 75
  • 241
  • 416
  • 1
    Something to note with the `TS.RANGE` approach generailly is the time complexity... it's basically O(n) where N is the number of items in your timeseries when you use - + as your range. An alternative approach might be to use `TS.CREATERULE` and the `sum` aggregator to have Redis keep a sum for you for a given time period and then sum that timeseries... depends how realtime you need your sum etc https://redis.io/commands/ts.createrule/ – Simon Prickett Aug 30 '23 at 13:14