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
1 2 3 4 | #!/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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #!/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
1 2 3 4 5 6 7 8 9 10 11 | 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
.
Also posted in collected notes.
Author Javier Viola
LastMod 2020-07-03