Redis Lua
Tags: redis, tutorial, lua, api
Published: 2022-07-25 23:42:00


# Context

Do you know that you can run a _script_ in Redis? If you don't know maybe it is time to try it out. This scripting ability is intended to optimize performance for Redis operation. However, you probably can achieve the same performance using Redis Pipeline feature. Depending on the use cases, this scripting ability is available if you have very compelling use case.

This article will show you various Redis Lua scripts use case that will make your life easier and its potential use cases where it might fit in daily life. 

## 1. HGet Members

### Description
If you have a hash and a set, where a set defines specific hash keys.

### Example
Let's say you have vehicles, 3 Honda cars, 1 Mercedes car, and 2 Scooters. We can this script to pull the counts of the Cars. 

```shell
> hset cars honda 3
(integer) 1
> hset cars mercedes 1
(integer) 1
> hset motorbikes scooters 1
(integer) 1
> sadd cars honda mercedes
(integer) 2
> evalsha 700c06c5ce9835bf9eef2198c8bc4d268b3b5095 2 cars produce
1) 1) "honda"
   2) "3"
2) 1) "mercedes"
   2) "1"
```

Lua source:
```lua
local fields = redis.call("SMEMBERS", KEYS[2])
local values = redis.call("HMGET", KEYS[1], unpack(fields))
local result = {}
for i,k in ipairs(fields) do result[i] = {k, values[i]} end
return result
```


## 2. HINCR IF-EXISTS

### Description
If you need to increment existing value in Hash map with additional check on the key existance.

### Example

```shell
> evalsha 3cb0d65c399fda8a1d4eb93b2fe85311671bc969 1 top_scorer budi 1
(nil)
> hset top_scorer budi 80
(integer) 1
> evalsha 3cb0d65c399fda8a1d4eb93b2fe85311671bc969 1 top_scorer budi 1
(integer) 91
> evalsha 3cb0d65c399fda8a1d4eb93b2fe85311671bc969 1 top_scorer budi 1
(integer) 92
```

Lua source:

```lua
if redis.call("HEXISTS", KEYS[1], ARGV[1]) == 1 then
  return redis.call("HINCRBY", KEYS[1], ARGV[1], ARGV[2])
else
  return nil
end
```

## 3. HMSET-Extended

### Description
In hash set, you usually need to set value one by one. You can leverage Lua to set multiple key from the args.

### Example
```shell
> evalsha 8c4bbf1b37784149128a5b96e55073538a7401d9 1 mykey foo 1 bar 2
(integer) 1
> hgetall mykey
1) "foo"
2) "1"
3) "bar"
4) "2"
> evalsha 8c4bbf1b37784149128a5b96e55073538a7401d9 1 mykey foo 1 bar 3 baz 4
(integer) 0
> hgetall mykey
1) "foo"
2) "1"
3) "bar"
4) "2"
``` 

Lua source:
```lua
if redis.call('exists', KEYS[1]) == 0 then
    redis.call('hmset', KEYS[1], unpack(ARGV))
    return 1
end
return 0
```

## 4. Simple Rate Limit

### Description
If you use Redis for rate limiting, Lua can help you putting together logic your rate limit logic. 

### Example

```shell
> evalsha 089ccf077629d371793d5e928a3f06e9e483eb08 1 ratelimit:127.0.0.1 5 10000
(integer) 0
> evalsha 089ccf077629d371793d5e928a3f06e9e483eb08 1 ratelimit:127.0.0.1 5 10000
(integer) 0
> evalsha 089ccf077629d371793d5e928a3f06e9e483eb08 1 ratelimit:127.0.0.1 5 10000
(integer) 0
> evalsha 089ccf077629d371793d5e928a3f06e9e483eb08 1 ratelimit:127.0.0.1 5 10000
(integer) 0
> evalsha 089ccf077629d371793d5e928a3f06e9e483eb08 1 ratelimit:127.0.0.1 5 10000
(integer) 0
> evalsha 089ccf077629d371793d5e928a3f06e9e483eb08 1 ratelimit:127.0.0.1 5 10000
(integer) 1
```

Lua source:

```lua
local cnt = redis.call('INCR', KEYS[1])
if cnt > tonumber(ARGV[1])
then
  return 1
end
if cnt == 1
then
  redis.call('PEXPIRE', KEYS[1], ARGV[2])
end
return 0
```

## 5. Value in Multiple Sets
### Description
If you have use case, where you need to check value in multiple sets. This Lua script is definitely helpful for the implementation and avoid networking call.


### Example

```shell
> sadd users alice bob
(integer) 2
> sadd admin jenny
(integer) 1
> evalsha d7550c872f553141096d5134c027af5eeed283db 2 users admin alice
(integer) 1
> evalsha d7550c872f553141096d5134c027af5eeed283db 3 users admin guests jenny
(integer) 1
> evalsha d7550c872f553141096d5134c027af5eeed283db 2 users admin abner
(integer) 0
```

Lua source:

```lua

-- Is an item in any of several sets? Call with:
-- EVALSHA  n set1 set2 ... setn key
for i=1,#KEYS do
  if redis.call('sismember', KEYS[i], ARGV[1]) == 1 then
    return 1
  end
end
return 0
```

---
_Source:_ https://www.redisgreen.com/library/