Using Lua to make bulk operations in Redis
Redis is awesome! Can be used for different purposes and has clients available in various languages.
Last week we had an issue where the keys were saving with a larger expiration
and the cpu usage of our redis instance started to grow reaching the limit.... So, after digging the cause we found that we had millions of keys with the expire
set to a larger number that we want and decided to re-set the expiration of those keys.
So, the first approach we take was run a bash
script using the redis-cli
#!/bin/bash
redis-cli --scan --pattern <prefix>:* | \
awk -v cmd_template="expire __key__ <expire_time>" '/^/ {cmd=cmd_template; gsub(/__key__/, $1, cmd); split(cmd, cmdargs," "); printf "*" length(cmdargs) "\r\n"; for(i=1; i<=length(cmdargs); i++) {printf "$" length(cmdargs[i]) "\r\n" cmdargs[i] "\r\n" }};' | \
redis-cli --pipe
The script was making his job...but it was slow, so we move to a different approach, running a lua
script and looking for a solution to bulk operations using lua
I found this post describing a similar scenario.
Next step was making small changes to the script in the post, so we end running with a cursor
of 1000 keys with this two script.
The first one is a bash
script used to load/call the lua
one.
#!/bin/bash
if [ $# -ne 3 ]
then
echo "Expire keys from Redis matching a pattern using SCAN & EXPIRE"
echo "Usage: $0 <host> <port> <pattern>"
exit 1
fi
cursor=-1
keys=""
while [[ $cursor -ne 0 ]]; do
if [[ $cursor -eq -1 ]]
then
cursor=0
fi
reply=$(redis-cli -h $1 -p $2 SCAN $cursor MATCH $3 COUNT 1000)
cursor=$(expr "$reply" : '\([0-9]*[0-9 ]\)')
echo "Cursor: $cursor"
keys=$(echo $reply | awk '{for (i=2; i<=NF; i++) print $i}')
[ -z "$keys" ] && continue
keya=( $keys )
count=$(echo ${#keya[@]})
redis-cli -h $1 -p $2 EVAL "$(cat expire.lua)" $count $keys
done
and the lua
local modified={};
for i,k in ipairs(KEYS) do
local ttl=redis.call('ttl', k);
if ttl >= 86400 then
redis.call('EXPIRE', k, 60)
modified[#modified + 1] = k;
end
end
return modified;
After run this script, we saw how the keys start expiring and the cpu came back to its normal metric. in this case not only redis
was awesome, but lua
also!!
You can check the original code in github, I saved this gem to the next I need to make bulk operations in redis
.