I’m trying to use the following Lua script using C# StackExchange library:
private const string LuaScriptToExecute = @"
local current
current = redis.call(""incr"", KEYS[1])
if current == 1 then
redis.call(""expire"", KEYS[1], KEYS[2])
return 1
else
return current
end
Whenever i’m evaluating the script "as a string", it works properly:
var incrementValue = await Database.ScriptEvaluateAsync(LuaScriptToExecute,
new RedisKey[] { key, ttlInSeconds });
If I understand correctly, each time I invoke the ScriptEvaluateAsync
method, the script is transmitted to the redis server which is not very effective.
To overcome this, I tried using the "prepared script" approach, by running:
_setCounterWithExpiryScript = LuaScript.Prepare(LuaScriptToExecute);
...
...
var incrementValue = await Database.ScriptEvaluateAsync(_setCounterWithExpiryScript,
new[] { key, ttlInSeconds });
Whenever I try to use this approach, I receive the following error:
ERR Error running script (call to f_7c891a96328dfc3aca83aa6fb9340674b54c4442): @user_script:3: @user_script: 3: Lua redis() command arguments must be strings or integers
What am I doing wrong?
What is the right approach in using "prepared" LuaScripts that receive dynamic parameters?
2
Answers
First of all, Jan's comment above is correct.
The script line that updated the key's TTL should be
redis.call(""expire"", KEYS[1], ARGV[1])
.Regarding the issue itself, after searching for similar issues in RedisStackExchange's Github, I found that Lua scripts do not work really well in cluster mode.
Fortunately, it seems that "loading the scripts" isn't really necessary. The
ScriptEvaluateAsync
method works properly in cluster mode and is sufficient (caching-wise). More details can be found in the following Github issue.So at the end, using
ScriptEvaluateAsync
without "preparing the script" did the job.As a side note about Jan's comment above that this script isn't needed and can be replaced with two C# calls, it is actually quite important since this operation should be atomic as it is a "Rate limiter" pattern.
If I look in the documentation: no idea.
If I look in the unit test on github it looks really easy.
(by the way, is your
ttlInSeconds
reallyRedisKey
and notRedisValue
? You are accessing it thruKEYS[2]
– shouldnt that beARGV[1]
? Anyway…)It looks like you should rewrite your script to use named parameters and not arguments:
Then just execute it with anonymous class as parameter:
Warning:
Please note that Redis is in full-stop mode when executing scripts. Your script looks super-easy (you sometimes save one trip to redis (when
current != 1
) so i have a feeling that this script will be counter productive in greater-then-trivial scale. Just do one or two calls from c# and dont bother with this script.