"After all, the engineers only needed to refuse to fix anything, and modern industry would grind to a halt." -Michael Lewis

Enable Massive Growth

How to Run a Lua Script against Redis using Lettuce

Apr 2021

The source code for what follows can be found on Github.

Running a lua script against redis is done using EVAL. The primary benefit of using a lua script is that the entire script is guaranteed to be run at once, and nothing else will interfere with it [it's atomic]. This allows for operating on multiple keys, or check-then-set type operations on the same key.

Executing a script using the redis cli looks like this:

> EVAL "return redis.call('set',KEYS[1],ARGV[1],'ex',ARGV[2])" 1 foo1 bar1 10
OK

This script is simple [and we don't need a script for this, it's just used as an example], it just calls redis and tells it to set the key foo1 to value bar1 with a time to live of 10 seconds.

We can verify that script works in our shell with something like:

$ redis-cli eval "return redis.call('set',KEYS[1],ARGV[1],'ex',ARGV[2])" 1 foo1 bar1 10; redis-cli ttl foo1; redis-cli get foo1
OK
(integer) 10
"bar1"

Lua Scripting with Lettuce

For a fast feedback loop, you can refer to either using embedded redis to test lettuce or using a redis test container to test lettuce as a starting point. Once we have that, testing the same lua script with lettuce can look something like this:

    public static final String SAMPLE_LUA_SCRIPT = "return redis.call('set',KEYS[1],ARGV[1],'ex',ARGV[2])";

    @Test
    public void executeLuaScript() {
        String script = SAMPLE_LUA_SCRIPT;

        StepVerifier.create(redisReactiveCommands.eval(script, ScriptOutputType.BOOLEAN,
                // keys as an array
                Arrays.asList("foo1").toArray(new String[0]),
                // other arguments
                "bar1", "10"
                )
        )
                .expectNext(true)
                .verifyComplete();

        StepVerifier.create(redisReactiveCommands.get("foo1"))
                .expectNext("bar1")
                .verifyComplete();

        StepVerifier.create(redisReactiveCommands.ttl("foo1"))
                .expectNextMatches(ttl -> 7 < ttl && 11 > ttl)
                .verifyComplete();
    }

This code uses the same lua script that we used in the cli in the example before this to redis along with arguments. The third argument in our eval command is the keys, the fourth are arbitrary arguments

This should get you started, and from here I would recommend you read through the eval section in the redis docs as well as read my next post about speeding up lua script execution by loading the script into the redis cache and referencing it, rather than re-sending it over the wire each time.

Nick Fisher is a software engineer in the Pacific Northwest. He focuses on building highly scalable and maintainable backend systems.