I wrote and register this Redis Function:
local function stincr(KEYS, ARGS)
local value = redis.pcall('INCR', KEYS[1])
if value == 1 or value % 100 == 0 then
redis.call('ZADD', KEYS[2],'GT', tostring(value), KEYS[1])
end
return value;
end
redis.register_function('stincr', stincr)
Redis Functions are introduced in Redis 7. How can I call it with StackExchange.Redis?
As of right now StackExchange.Redis doesn't have any higher-level API wrapping up the functions API, however, you can just use the ad-hoc command API pretty easily. I modified your script to add the shebang in the beginning called for by redis and added it to script.lua:
#!lua name=mylib
local function stincr(KEYS, ARGS)
local value = redis.pcall('INCR', KEYS[1])
if value == 1 or value % 100 == 0 then
redis.call('ZADD', KEYS[2],'GT', tostring(value), KEYS[1])
end
return value;
end
redis.register_function('stincr', stincr)
Then loading/calling the function was pretty straight forward:
var script = File.ReadAllText("script.lua");
var muxer = ConnectionMultiplexer.Connect("localhost");
var db = muxer.GetDatabase();
db.Execute("FUNCTION", "LOAD", script);
var res = db.Execute("FCALL", "stincr", 2, "myNum", "myzset");
Related
Since Redis 6.2.7 (and Redis 7) an existing Lua script stopped working with the error message:
"ERR user_script:6: Attempt to modify a readonly table script: 2f405679dab26da46ec86d29bded48f66a99ff64, on #user_script:6."
The script is working fine with Redis 6.2.6. I did not find any breaking changes in the last Redis release notes.
Any clue ? Thanks !!
Here's the script:
-- returns valid task ID if successfull, nil if no tasks
local tenantId = unpack(ARGV)
local activeTenantsSet, activeTenantsList, tenantQueue = unpack(KEYS)
-- next task lua based function - return nil or taskId
function next ()
local task = redis.call('ZPOPMAX', tenantQueue)
if table.getn(task) == 0 then
redis.call('SREM', activeTenantsSet, tenantId)
redis.call('LREM', activeTenantsList, 0, tenantId)
return nil
end
redis.call('SADD', activeTenantsSet, tenantId)
redis.call('RPUSH', activeTenantsList, tenantId)
return task[1]
end
-------------
return next()
try adding 'local' in front of 'function next'
local function next ()
...
There's a fix for this in a new version of Sentry (the patch also applies cleanly to the lua scripts going back to some older versions):
https://github.com/getsentry/sentry/pull/34416/commits/7c57fe7b17f613fecc47a56c22fff3a20a958496
I Need a short advise please about LUA script for modification object in Redis.
I Have Redis List with entities like that:
{
"#class": "com.myproject.model.Book",
"bookId": "someId",
"author": "someAuthor"
}
now, I need to change my entity to allow multiple authors for certain book, and create migration script for this:
{
"#class": "com.myproject.model.Book",
"bookId": "someId",
"authors": [
"java.util.ArrayList",
[
"someAuthor"
]
]
}
What I Think I need to do with LUA:
local book
local cacheName --cache name in my case
local authorId;
local size = redis.call('LLEN', cacheName)
if size == 0
then
return -1
end
while size > 0
do
book = redis.call('LPOP', cacheName)
-- modify entity here
affectedEntitiesCount = affectedEntitiesCount + 1
redis.call('RPUSH', cacheName, book)
size = size - 1
end
return affectedEntitiesCount
But I have no idea how to modify book according requirements.
Can someone take a look and suggest?
solved:
local book
local cacheName
local authorPattern= '"author":"[^"]*"'
local authorId
local replacementAuthors = '"authors":["java.util.ArrayList",["%s"]]'
local size = redis.call('LLEN', cacheName)
if size == 0
then
return -1
end
while size > 0
do
book = redis.call('LPOP', cacheName)
authorId = string.match(string.match(book, authorPattern), [["authorId":"([^"]+)]])
replacedAuthors = string.format(replacedAuthors , authorId)
book = string.gsub(book, authorPattern, replacedAuthors)
affectedEntitiesCount = affectedEntitiesCount + 1
redis.call('RPUSH', cacheName, book)
size = size - 1
end
return affectedEntitiesCount
Since it appears that you're storing JSON-encoded values in your list, you can use the cjson library that's embedded in Redis' Lua engine.
For example:
...
book = cjson.decode(redis.call('LPOP', cacheName))
book['bookId'] = book['bookId']..'foo' -- concat 'foo' to bookId
...
redis.call('RPUSH', cacheName, cjson.encode(book))
Note: also make sure that you use the KEYS input array to parameterize the script's input key names (e.g. local cacheName = KEYS[1])
Now I am using this code to increment a value in spring boot :
String loginFailedKey = "admin-login-failed:" + request.getPhone();
Object loginFailedCount = loginFailedTemplate.opsForValue().get(loginFailedKey);
if (loginFailedCount != null && Integer.valueOf(loginFailedCount.toString()) > 3) {
throw PostException.REACH_MAX_RETRIES_EXCEPTION;
}
List<Users> users = userService.list(request);
if (CollectionUtils.isEmpty(users)) {
loginFailedTemplate.opsForValue().increment(loginFailedKey, 1);
throw PostException.LOGIN_INFO_NOT_MATCH_EXCEPTION;
}
is it possible to set an expire time when increment the key? If a new increment command happen, update the expire time. I read the docs and did not found the implement.
There is no direct way in Spring Boot.
One of the indirect ways is to use LUA srcipt.
For example:
RedisScript script = RedisScript.of(
"local i = redis.call('INCRBY', KEYS[1], ARGV[1])"
+ " redis.call('EXPIRE', KEYS[1], ARGV[2])"
+ " return i");
redisTemplate.execute(script, key, String.valueOf(increment),
String.valueOf(expiration));
I have cluster with several replicasets. I want to call some stored function on all nodes without calculate bucket_id, and after to map results. How should I do it?
You can use module cartridge.rpc function get_candidates for getting all nodes with some role, which you want to call and after to use module cartridge.pool function map_call for calling your function and mapping results. This function available from 1.2.0-17 version of cartridge. So your code could be like this:
local cartridge = require('cartridge')
local nodes = cartridge.rpc_get_candidates('my_role_name', { leaders_only = true, healthy_only = true })
local pool = require('cartridge.pool')
local results, err = pool.map_call('_G.my_function_name', { func_args }, { uri_list = nodes, timeout = 10 })
if (err ~= nil) then
#your error handling#
end
All function response will be saved to results variable and mapped for every URI. All errors will be saved to err variable as map with keys: line, class_name, err, file, suberrors, str
Another proposal.
If you use vshard and want to perform map-reduce over storages:
local replicaset, err = vshard.router.routeall()
for _, replica in pairs(replicaset) do
local _, err = replica:callrw('function', { args })
if err ~= nil then
return nil, err
end
end
Is there an atomic GET + EXPIRE command available for Redis? This would act as a sliding expiration value: attempt to get the value specified by the key, and then only if the key was found with this request, set the time to live for X seconds.
No, there isn't, but there's nothing preventing you from sending the two commands one after the other in a MULTI/EXEC block or using a Lua script. Using EXPIRE on a non-existent key does nothing.
Or, I use simple Lua script:
local val, err = redis.pcall('GET', KEYS[1])
if err then
return err
end
redis.call('EXPIRE', KEYS[1], ARGV[1])
return {val}
In Golang you can do:
import "github.com/go-redis/redis"
const lua = `
local val, err = redis.pcall('GET', KEYS[1])
if err then
return err
end
redis.call('EXPIRE', KEYS[1], ARGV[1])
return {val}
`
redisGetEx = redis.NewScript(lua)
result, err = redisGetEx.Run(redisClient, []string{"key"}, 1800).Result()