Una guía rápida para la creación de scripts de Redis Lua

Redis es una cuadrícula en memoria popular que se utiliza para la comunicación entre procesos y el almacenamiento de datos. Es posible que haya escuchado que le permite ejecutar scripts de Lua, pero aún no está seguro de por qué. Si esto te suena, sigue leyendo.

Prerrequisitos

Debería tener Redis instalado en su sistema para seguir esta guía. Puede ser útil consultar la referencia de comandos de Redis mientras lee.

¿Por qué necesito Lua Scripts?

En resumen: aumento de rendimiento. La mayoría de las tareas que realiza en Redis implican muchos pasos. En lugar de realizar estos pasos en el idioma de su aplicación, puede hacerlo dentro de Redis con Lua.

  • Esto puede resultar en un mejor rendimiento.
  • Además, todos los pasos dentro de un script se ejecutan de forma atómica. Ningún otro comando de Redis puede ejecutarse mientras se ejecuta un script.

Por ejemplo, utilizo scripts de Lua para cambiar cadenas JSON almacenadas en Redis. Describo esto en detalle más cerca del final de este artículo.

Pero no conozco a ninguna Lua

No te preocupes, Lua no es muy difícil de entender. Si conoce algún idioma de la familia C, debería estar de acuerdo con Lua. Además, proporciono ejemplos prácticos en este artículo.

Muestrame un ejemplo

Comencemos ejecutando scripts a través de redis-cli . Empiece con:

redis-cli

Ahora ejecute el siguiente comando:

eval “redis.call(‘set’, KEYS[1], ARGV[1])” 1 key:name value

El comando EVAL es lo que le dice a Redis que ejecute el script que sigue. La ”redis.call(‘set’, KEYS[1], ARGV[1])”cadena es nuestro script que es funcionalmente idéntico al setcomando de Redis . Tres parámetros siguen al texto del guión:

  1. El número de claves proporcionadas
  2. Nombre clave
  3. Primer argumento

Los argumentos del script se dividen en dos grupos: KEYS y ARGV .

Especificamos cuántas claves requiere el script con el número inmediatamente siguiente. En nuestro ejemplo, es 1 . Inmediatamente después de este número, debemos proporcionar estas claves, una tras otra. Son accesibles como tabla KEYS dentro del script. En nuestro caso, contiene un valor único key:nameen el índice 1 .

Tenga en cuenta que las tablas indexadas de Lua comienzan con el índice 1, no con 0 .

Podemos proporcionar cualquier número de argumentos después de las claves, que estarán disponibles en Lua como la tabla ARGV . En este ejemplo, proporcionamos un único ARGV -argument: string value. Como ya adivinó, el comando anterior establece la clave key:nameen valor value.

Se considera una buena práctica proporcionar claves que el script usa como CLAVES y todos los demás argumentos como ARGV . Por lo tanto, no debe especificar KEYS como 0 y luego proporcionar todas las claves dentro de la tabla ARGV .

Comprobemos ahora si el script se completó correctamente. Vamos a hacer esto ejecutando otro script que obtiene la clave de Redis:

eval “return redis.call ('get', KEYS [1])” 1 clave: nombre

La salida debería ser ”value”, lo que significa que la secuencia de comandos anterior estableció correctamente la clave “key:name”.

¿Puedes explicar el guión?

Nuestro primer script consta de una sola declaración: la redis.callfunción:

redis.call(‘set’, KEYS[1], ARGV[1])

Con redis.callpuedes ejecutar cualquier comando de Redis. El primer argumento es el nombre de este comando seguido de sus parámetros. En el caso del setcomando, estos argumentos son clave y valor . Todos los comandos de Redis son compatibles. Según la documentación:

Redis usa el mismo intérprete de Lua para ejecutar todos los comandos

Nuestro segundo script hace un poco más que ejecutar un solo comando, también devuelve un valor:

eval “return redis.call(‘get’, KEYS[1])” 1 key:name

Todo lo que devuelve el script se envía al proceso de llamada. En nuestro caso, este proceso es redis-cli y verá el resultado en la ventana de su terminal.

¿Algo más complejo?

Una vez usé scripts de Lua para devolver elementos de un mapa hash en un orden particular. El orden en sí fue especificado por claves hash almacenadas en un conjunto ordenado.

Primero configuremos nuestros datos ejecutando estos comandos en redis-cli :

hmset hkeys key:1 value:1 key:2 value:2 key:3 value:3 key:4 value:4 key:5 value:5 key:6 value:6 zadd order 1 key:3 2 key:1 3 key:2

Estos comandos crean un mapa hash en la clave hkeysy un conjunto ordenado en la clave orderque contiene las claves seleccionadas hkeysen un orden específico.

Es posible que desee consultar la referencia de los comandos hmset y zadd para obtener más detalles.

Ejecutemos el siguiente script:

eval “local order = redis.call(‘zrange’, KEYS[1], 0, -1); return redis.call(‘hmget’,KEYS[2],unpack(order));” 2 order hkeys

Debería ver el siguiente resultado:

“value:3” “value:1” “value:2”

Which means that we got values of the keys we wanted and in the correct order.

Do I have to specify full script text to run it?

No! Redis allows you to preload a script into memory with the SCRIPT LOAD command:

script load “return redis.call(‘get’, KEYS[1])”

You should see an output like this:

“4e6d8fc8bb01276962cce5371fa795a7763657ae”

This is the unique hash of the script which you need to provide to the EVALSHA command to run the script:

evalsha 4e6d8fc8bb01276962cce5371fa795a7763657ae 1 key:name

Note: you should use actual SHA1 hash returned by the SCRIPT LOAD command, the hash above is only an example.

What did you mention about changing JSON?

Sometimes people store JSON objects in Redis. Whether it is a good idea or not is another story, but in practice, this happens a lot.

If you have to change a key in this JSON object, you need to get it from Redis, parse it, change the key, then serialize and set it back to Redis. There are a couple of problems with this approach:

  1. Concurrency. Another process can change this JSON between our get and set operations. In this case, the change will be lost.
  2. Performance. If you do these changes often enough and if the object is rather big, this might become the bottleneck of your app. You can win some performance by implementing this logic in Lua.

Let’s add a test JSON string to Redis under key obj:

set obj ‘{“a”:”foo”,”b”:”bar”}’

Now let’s run our script:

EVAL ‘local obj = redis.call(“get”,KEYS[1]); local obj2 = string.gsub(obj,”(“ .. ARGV[1] .. “\”:)([^,}]+)”, “%1” .. ARGV[2]); return redis.call(“set”,KEYS[1],obj2);’ 1 obj b bar2

Now we will have the following object under key obj:

{“a”:”foo”,”b”:”bar2"}

You can instead load this script with the SCRIPT LOAD command:

SCRIPT LOAD ‘local obj = redis.call(“get”,KEYS[1]); local obj2 = string.gsub(obj,”(“ .. ARGV[1] .. “\”:)([^,}]+)”, “%1” .. ARGV[2]); return redis.call(“set”,KEYS[1],obj2);’

and then run it like this:

EVALSHA  1 obj b bar2

Some notes:

  • The .. is the string concatenation operator in Lua.
  • We use a RegEx pattern to match key and replace its value. If you don’t understand this Regular Expression, you can check my recent guide.
  • One difference of the Lua RegEx flavor from most other flavors is that we use % as both backreference mark and escape character for RegEx special symbols.
  • We still escape with \ and not % because we escape Lua string delimiter, not RegEx special symbol.

Should I always use Lua scripts?

No. I recommend only using them when you can prove that it results in better performance. Always run benchmarks first.

If all you want is atomicity, then you should check Redis transactions instead.

Also, your script shouldn’t be too long. Remember that while a script is running, everything else is waiting for it to finish. If your script takes quite some time, it can cause bottlenecks instead of improving performance. The script stops after reaching a timeout (5 seconds by default).

Last Word

For more information on Lua check lua.org.

You can check my node.js library on GitHub for some examples of Lua scripts (see src/lua folder). You can also use this library in node.js to change JSON objects without writing any Lua scripts yourself.

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Thank you for reading this article. Questions and comments are much appreciated. You are also welcome to follow me on Twitter.