Redis Lua Scripting: How To Get Started With?
Posted October 25, 2017 - 4 min read
Topics:
redis
lua
transactions
Previously we have discussed Redis transactions and have learned that Redis provides two approaches for using transactions: MULTI/EXEC and Lua scripts.
Lua scripts are more flexible as they allow to use the full potential of the Lua programing language and overcome the limitation of MULTI/EXEC which does not give us the ability to use the result of a given command in the next command in the context of the same transaction.
Within this post we are going to learn how to use Lua scripts.
Redis Lua Scripting Overview
Lua scripts are executed in the Redis server and allow to efficiently access and manipulate stored data by reducing network latency and resources.
A Lua script is executed as a single block operation and while being executed it blocks the server.
To work with Lua scripts, Redis provides SCRIPT LOAD
and EVALSHA
commands.
Loading Scripts
SCRIPT LOAD script_content
SCRIPT LOAD
command allows to upload a Lua script to Redis server in order to be parsed and stored into the scripts cache
.
SCRIPT LOAD
returns the SHA1 digest of the script and may be called multiple times with the same script without warring about any side effects. It will always return the same hash value.
This may be useful for your application as it does not have to hash and track the scripts itself.
redis> SCRIPT LOAD "return 'Hello world!'"
"47a013e660d408619d894b20806b1d5086aab03b"
Executing Scripts
EVALSHA script_digest keys_count [key [key ...]] [arg [arg ...]]
The EVALSHA
command allows to execute a script and accepts the following arguments:
script_digest
- The script digest which has been returned after runningSCRIPT LOAD
command.keys_count
- The number of Redis keys that are being passed in within the arguments.[key [key ...]]
- Any number of Redis keys. Redis keys are passed in afterkeys_count
and before any the other argument.[arg [arg ...]]
- Any other arguments (values) that are used in your script.
A Lua script may use one or more Redis keys to access stored data.
It is recommended to always pass in Redis keys as arguments and not to hardcode them in the script itself.
Otherwise, in case of a cluster configuration for example, when hardcoded, Redis would not be able to parse the keys and figure out in which node the data is stored.
In addition to key names, the script arguments also include any other parameter that may be used during the script execution.
Redis keys and other arguments that has been passed in to a given script are stored respectively under KEYS
and ARGV
global variables that are accessible to the script.
redis> EVALSHA 47a013e660d408619d894b20806b1d5086aab03b 0
"Hello world!"
Real World Usage Example
Lua is very intuitive and simple to learn, and you should not be scarred of it.
We will use Node.js
, TypeScript
, and node-redis
to demonstrate how to work with Redis scripts from our application.
Let’s create a simple Lua script and save it, for example, under /redis-scripting/extend.lua
.
The content of this file is:
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("PEXPIRE", KEYS[1], ARGV[2])
end
return 0
In the script above, before setting a timeout on KEYS[1]
we always check that its value is equal to ARGV[1]
.
Each time we start our application, we need to make sure that such script is uploaded to Redis server.
import { promises } from 'fs';
import { createClient } from 'redis';
const client = createClient();
const read = async (path: string): Promise<string> => {
const content = await promises.readFile(path);
return content.toString();
};
const load = async (content: string): Promise<string> => {
return new Promise((resolve, reject) => {
client.script('load', content, (err, digest) => {
if (err) reject(err);
else resolve(digest);
});
});
};
const exec = async (
digest: string,
key: string,
value: string,
timeout: number,
): Promise<number> => {
return new Promise((resolve, reject) => {
client.evalsha([digest, 1, key, value, timeout], (err, res) => {
if (err) reject(err);
else resolve(res);
});
});
};
read('/redis-scripting/extend.lua')
.then((content) => load(content))
.then((digest) => exec(digest, 'some_key', 'some_key_value', 3000))
.then((result) => {
if (result) console.log('Timeout has been set');
else console.log('Timeout has not been set');
});
Within this example, we have read the content of our Lua script, uploaded it to Redis server, and after that we have executed it.
You should pay attention to how the client.script
and client.evalsha
methods are used.
For more details about these methods you should refer to node-redis documentation.
Conclusion
As we have seen, Redis scripting is not a hard thing to learn and allows you to add new features to Redis.
Nevertheless, one downside of Redis scripting is that it monopolizes the server.
You should always remember that while your script is running everything else on the server is put on hold.