Lua Scripting with redis.Script

Redis allows for the execution of server-side Lua scripts, which is powerful for creating custom atomic operations. Redigo provides the redis.Script helper type to make working with these scripts simple and efficient.

The Script Helper

A key challenge with Lua scripting in Redis is managing the script cache. Redis caches compiled scripts and allows you to execute them via their SHA1 hash (EVALSHA) to save bandwidth. The redis.Script helper automates this process.

Creating a Script

You create a Script object with redis.NewScript, providing the number of keys the script expects and its source code.

var zpopScript = redis.NewScript(1, `
    local r = redis.call('ZRANGE', KEYS[1], 0, 0)
    if r ~= nil then
        r = r[1]
        redis.call('ZREM', KEYS[1], r)
    end
    return r
`)
  • keyCount (the first argument): This tells Redigo how many of the script's arguments are key names. Redis uses this to enable features like Redis Cluster. If you don't know the number of keys in advance, you can use -1, but you will have to provide the key count yourself when executing the script.
  • src (the second argument): The Lua script source code as a string.

The NewScript function automatically calculates the SHA1 hash of the script for you.

Executing a Script

The script.Do() method is the primary way to execute a script. It implements an optimistic execution strategy:

  1. It first attempts to execute the script using EVALSHA with its cached SHA1 hash.
  2. If the server responds with a NOSCRIPT error (meaning the script is not in its cache), Redigo automatically resends the command using EVAL, which sends the full script source. The server then executes and caches the script.
  3. Subsequent calls to Do() for the same script on the same server will succeed on the first EVALSHA attempt.

This pattern provides the performance benefits of EVALSHA without the manual burden of checking if the script is loaded.

// zpopScript defined as above
c, err := redis.Dial("tcp", ":6379")
// handle err
defer c.Close()

// Add some data to a sorted set
c.Do("ZADD", "myzset", 1, "one", 2, "two", 3, "three")

// Execute the script. Redigo handles EVALSHA/EVAL logic.
// The arguments to Do are the connection, followed by the script's KEYS and ARGV.
member, err := redis.String(zpopScript.Do(c, "myzset"))
if err != nil {
    // handle error
}

fmt.Println("Popped member:", member) // Popped member: one

Other Script Methods

  • s.Load(c Conn): Explicitly loads the script into the Redis cache using SCRIPT LOAD. This is useful if you want to ensure a script is available before a critical, time-sensitive operation.

  • s.Send(c Conn, keysAndArgs ...interface{}): Sends an EVAL command for the script but does not wait for the reply (for use in pipelining).

  • s.SendHash(c Conn, keysAndArgs ...interface{}): Sends an EVALSHA command for the script but does not wait for the reply. Use this only when you are certain the script is already loaded to avoid NOSCRIPT errors.