Earlier in this chapter, server-side Lua scripting was identified as a feature of Redis. This feature allows you to[2] create scripted extensions to the Redis database. In this section, we'll explore this functionality along with some short and simple scripts.
Let's start by expanding on our hello world example and write a short script to query that message. Lua code can be invoked with the EVAL command from the redis-cli:
127.0.0.1:6379> EVAL 'local text="Hello world!" return text' 0 "Hello world!"
The zero 0 on the end of the EVAL command tells Redis that we are not passing any keys to be used by Lua (more on that in a moment).
Lua scripts can invoke commands typically used in the redis-cli. This can be done with the redis.call() command. Let's use this command to return the value of our packt:welcome key:
127.0.0.1:6379> EVAL 'local text=redis.call("get","packt:welcome") return text' 0 "Hello world from getset!"
Of course, our last command really didn't have anything to do with getset. That's just the current value stored for the packt:welcome key (set in the prior section on redis-cli). Let's change that quickly:
127.0.0.1:6379> set packt:welcome "Hello world from Lua!"
Now, re-running our last Lua script makes a little more sense:
127.0.0.1:6379> EVAL 'local text=redis.call("get","packt:welcome") return text' 0 "Hello world from Lua!"
Data can be passed into Lua scripts in either keys or arguments. These values are stored in Lua as associative arrays called tables. There is one table available for the keys, and another for arguments. The values within can be referenced by a one-based, cardinal index.
Let's pass in packt:welcome as a key so that it doesn't have to be hardcoded into the script:
127.0.0.1:6379> EVAL 'local text=redis.call("get",KEYS[1]) return text' 1 packt:welcome "Hello world from Lua!"
This works, because we're telling Lua that we are passing one (1) key, which we are specifying after the 1. Then, our get command is altered to pull this key from the KEYS table.
Now, let's personalize this script by passing in a name as a parameter. This command will invoke the script to return a personalized welcome message for a user named Coriene:
127.0.0.1:6379> EVAL 'local text=redis.call("get",KEYS[1]) return "Dear " .. ARGV[1] .. ", " .. text' 1 packt:welcome Coriene "Dear Coriene, Hello world from Lua!"
As our script is starting to get more complex, we can put our Lua code into its own file, and save it as welcomeName.lua:
local msg=redis.call("get",KEYS[1])
local name=ARGV[1]
local output="Dear "..name..", "..msg
return output
That script can now be invoked from the command line, via the redis-cli, like this:
src/redis-cli -a currentHorseBatteryStaple --eval welcomeName.lua packt:welcome , Coriene "Dear Coriene, Hello world from Lua!"
Now that we have done a couple of simple examples, let's write something useful. In one of the earlier examples, we used the zrevrange command to return the values in a sorted set. This allowed us to get a list of players on the high score list for a particular video game, sorted by score:
127.0.0.1:6379> zrevrange games:joust 0 -1 WITHSCORES
We could write a one-line Lua script to abstract away the zrevrange command and save it as getHighScores.lua, like this:
return redis.call("zrevrange", KEYS[1], 0, -1,"WITHSCORES")
Here we can quickly return the contents of the games:joust sorted set by invoking the script from the command line:
src/redis-cli -a currentHorseBatteryStaple --eval getHighScores.lua games:joust
1) "Dad"
2) "58150"
3) "Connor"
4) "48850"
5) "Toria"
6) "29910"
Similarly, we could take it a step further, and create a script designed to look up scores for Joust. The following one-line script would be saved as getJoustScores.lua:
return redis.call("zrevrange", "games:joust", 0, -1, "WITHSCORES")
Then the high scores for Joust could be quickly returned:
src/redis-cli -a currentHorseBatteryStaple --eval getJoustScores.lua
1) "Dad"
2) "58150"
3) "Connor"
4) "48850"
5) "Toria"
6) "29910"
In another example, we used a list called packt:logins to keep track of the most recently logged-in users. Returning the contents of that list required the following command:
127.0.0.1:6379> lrange packt:logins 0 -1
One way in which we could leverage Lua scripting to our advantage would be to abstract the lrange command and its parameters. Let's create a simple Lua script as its own file, named getList.lua. That script file would contain a single line:
return redis.call("lrange", KEYS[1], 0, -1)
Returning the contents of the packt:logins list (or any list in our database, for that matter) is a simple matter of invoking the script:
src/redis-cli -a currentHorseBatteryStaple --eval getList.lua packt:logins
1) "aploetz 10.0.0.4 2017-06-24 16:31:58.171875"
2) "aploetz 10.0.0.4 2017-06-24 16:22:04.144998"
Remember to keep your Lua scripts as short and simple as possible. Due to Redis' single-threaded nature,[3] nothing else can run during script execution. Use with caution!