Go Redis Lua Scripting
redis.Script
go-redis supports Lua scripting with redis.Script, for example, the following script implements INCRBY command in Lua using GET and SET commands:
var incrBy = redis.NewScript(`
local key = KEYS[1]
local change = ARGV[1]
local value = redis.call("GET", key)
if not value then
value = 0
end
value = value + change
redis.call("SET", key, value)
return value
`)
You can then run the script like this:
keys := []string{"my_counter"}
values := []interface{}{+1}
num, err := incrBy.Run(ctx, rdb, keys, values...).Int()
Internally, go-redis uses EVALSHA to execute the script and fallbacks to EVAL if the script does not exist.
You can find the example above at GitHub. For a more realistic example, check redis_rate which implements a leacky bucket rate-limiter.
Lua and Go types
Underneath, Lua's number type is a float64 number that is used to store both ints and floats. Because Lua does not distinguish ints and floats, Redis always converts Lua numbers into ints discarding the decimal part, for example, 3.14 becomes 3. If you want to return a float value, return it as a string and parse the string in Go using Float64 helper.
| Lua return | Go interface{} |
|---|---|
number (float64) | int64 (decimal part is discarded) |
string | string |
false | redis.Nil error |
true | int64(1) |
{ok = "status"} | string("status") |
{err = "error message"} | errors.New("error message") |
{"foo", "bar"} | []interface{}{"foo", "bar"} |
{foo = "bar", bar = "baz"} | []interface{}{} (not supported) |
Debugging Lua scripts
The easiest way to debug your Lua scripts is using redis.log function that writes the message to the Redis log file or redis-server output:
redis.log(redis.LOG_NOTICE, "key", key, "change", change)
If you prefer a debugger, check Redis Lua scripts debugger.
Passing multiple values
You can use for loop in Lua to iterate over the passed values, for example, to sum the numbers:
local key = KEYS[1]
local sum = redis.call("GET", key)
if not sum then
sum = 0
end
local num_arg = #ARGV
for i = 1, num_arg do
sum = sum + ARGV[i]
end
redis.call("SET", key, sum)
return sum
The result:
sum, err := sum.Run(ctx, rdb, []string{"my_sum"}, 1, 2, 3).Int()
fmt.Println(sum, err)
// Output: 6 nil
Loop continue
Lua does not support continue statements in loops, but you can emulate it with a nested repeat loop and a break statement:
local num_arg = #ARGV
for i = 1, num_arg do
repeat
if true then
do break end -- continue
end
until true
end
Error handling
By default, redis.call function raises a Lua error and stops program execution. If you want to handle errors, use redis.pcall function which returns a Lua table with err field:
local result = redis.pcall("rename", "foo", "bar")
if type(result) == 'table' and result.err then
redis.log(redis.LOG_NOTICE, "rename failed", result.err)
end
To return a custom error, use a Lua table:
return {err = "error message goes here"}