Is it a good idea to wrap TS.ADD and TS.RANGE calls within a transaction?

Another question, please -

We would like to add the following functionality to our ASP.Net Core web app, written in C#:

The web app receives thousands of location-related data per minute. We are only interested in max 20 such data records per OSM node id (open street map) in a 30 min window and would like to discard the rest.

So we plan to call (using the NRedisStack Nuget package, against an Azure Redis Cache Enterprise E10 with TimeSeries module enabled) the following 2 commands:

TS.ADD OsmNodeId * 1 RETENTION <30 mins> ON_DUPLICATE SUM

TS.RANGE OsmNodeId - + AGGREGATION SUM <30 mins>

My question is, if it is a good idea to put these 2 commands into a transaction and have the C# code like:

int retries = 0;
bool shouldDiscard = true;

do
{
    var tran = db.CreateTransAction();
    db.TS().Add(...);
    shouldDiscard = db.TS().Range(...) > 20;
    bool isCommited = tran.Execute();
} while (!isCommited && retries++ < 5);

Greetings from Germany
Alex

Hi Alex,
Sure, it’s sounds like a good idea which should improve the latency and throughput.

1 Like

Thank you, I have ended up using the following Lua script in my C# app to have all commands being atomically executable:

// 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 time = redis.call('TIME')
-- the final timestamp for TS.RANGE command in milliseconds
local now = math.floor(time[1] * 1000 + time[2] / 1000)
-- the start timestamp for TS.RANGE command in milliseconds
local start = now - tonumber(ARGV[1])
local tsrange = redis.call('TS.RANGE', KEYS[1], start, now)

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
";
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;
}