Redis Handbook
  • Introduction
  • Installation and Setup
  • Data Types
  • Commands
  • Events
  • Designing Database
  • Lua Scripts
  • Debugging
  • NodeJS
Powered by GitBook
On this page

Was this helpful?

Lua Scripts

Lua scripts are used to perform code fragments in Redis.

Performance Issues

Lets say we want to implement this logic for a game. We want to get random user details from redis. We have set with user indexes where key is index and value is time they were selected last time. Then we have set of users where key is index an value is JSON with values.

We want to get random user indexes first, lets say 100. Then use this list of users and get random user index. Then we use this index to get value from set that contains indexes and JSON details about user. The last step, after all is successful, we want to update the timestamp.

  async getUser(user) {
    const userIndex = await this.userRedisService.getUserIndex(user)

    if (!userIndex) {
      throw new Q.Errors.NoUserAvailableError('Not able to find user index for user', { user })
    }

    const user = await this.userRedisService.findByIndex(userIndex)
    if (!user) {
      throw new Q.Errors.NoUserAvailableError('Not able to find user for user', { user })
    }
    user.userIndex = userIndex
    if (user && user.unlimited) {
      return user
    }

    await this.userRedisService.updateTimestamp(user, userIndex)

    return user
  }

This will cause at least 3 calls to Redis. When we execute hundreds or thousands of concurrent requests, we will see performance decrease here. Each call can take about 20-60ms when we call this with 100 concurrent requests. We need to move this into one atomic step using Lua script.

#!/bin/bash
redis-cli eval "local user = KEYS[1] \
local timestamp =  KEYS[2] \
local from = KEYS[3] \
local to = KEYS[4] \
local penalty = KEYS[5] \
local indexes = redis.call('zrangebyscore', user, '-inf', timestamp, 'limit', from, to) \
local tableLength = table.getn(indexes) \
if tableLength == 0 then \
    return 'NO_USERS_AVAILABLE' \
end \
local index = indexes[math.random(tableLength)] \
local user = redis.call('hget', 'users', index) \
redis.call('zadd', user, timestamp + penalty, index) \
return user" 5 user:game1 $(date +%s000) 0 100 300000
➜ redis-cli SCRIPT LOAD "local user = KEYS[1] \
local timestamp =  KEYS[2] \
local from = KEYS[3] \
local to = KEYS[4] \
local penalty = KEYS[5] \
local indexes = redis.call('zrangebyscore', user, '-inf', timestamp, 'limit', from, to) \
local tableLength = table.getn(indexes) \
if tableLength == 0 then \
    return 'NO_USERS_AVAILABLE' \
end \
local index = indexes[math.random(tableLength)] \
local user = redis.call('hget', 'users', index) \
redis.call('zadd', user, timestamp + penalty, index) \
return user"
"some-hash"
redis-cli evalsha some-hash 5 user:game1 $(date +%s000) 0 100 300000

We can also run the script without installing it first. The following script returns what we have passed in parameter.

redis-cli eval "local user = KEYS[1] \
return user" 1 user:game1
PreviousDesigning DatabaseNextDebugging

Last updated 5 years ago

Was this helpful?

We should use command for development of the command. I like to put the Lua script into a bash file so I can edit the script and run it from command line easily.

When we are done with the script, we need to load it to Redis so it can be compiled only once (not every time we call the script). We use for this.

Then we call the script using its SHA using .

EVAL
SCRIPT LOAD
EVALSHA