Redis – Procedimientos Almacenados con Lua

Redis permite la ejecución de código dentro de la base de datos, mediante Lua, de forma similar a los procedimientos almacenados de las bases de datos relacionales, pudiendo trabajar con las claves de forma eficiente evitando su transferencia a través de la red, así como programar transacciones complejas. Lua es un lenguaje de Scripting ligero, potente, y eficiente, un proyecto Open Source con licencia MIT, y un lenguaje que podremos utilizar para escribir código de servidor en Redis.

Continuando con nuestra serie sobre Redis, ahora que ya hemos hablado sobre las diferentes estructuras de datos que soporta Redis (Strings, Hashes, Lists, Sets, y Sorted Sets), del problema y posibles soluciones de la búsqueda sobre claves en función de sus Atributos, las Transacciones en Redis, el mecanismo de Publicación y Suscripción en Redis, y los índices geoespaciales, llega el momento de hablar sobre la ejecución de código de servidor en Redis con Lua (similar a los Procedimientos Almacenados).

Principalmente hay dos formas de ejecutar comandos Redis desde Lua, redis.call y redis.pcall, que se diferencian principalmente en como gestionan los errores devueltos por Redis, y por defecto se tratará como un único comando de forma atómica:

  • redis.call simplemente propagará el error a Lua, produciendo que EVAL falle en caso de haber error.
  • redis.pcall devolverá una estructura representando la respuesta del error, que podríamos gestionar posteriormente de forma programática.

Podemos utilizar el comando EVAL para ejecutar un Script de Lua, indicándole el número de claves (Keys), las claves (Keys) y los argumentos que le queremos pasar a dicho Script para su ejecución. A continuación se muestran varios comandos de ejemplo, a modo ilustrativo.

eval "return 10" 0
eval "local cont=10 return cont" 0

set cont-key 10
eval "local cont=redis.call('GET', KEYS[1]) return cont" 1 cont-key

set user:100002 frank
eval "return redis.call('GET', KEYS[1])" 1 user:100002
eval "return redis.call('GET', 'user:100002')" 0

hset product:tv-samsung-led42 type tv manufacturer samsung size 42 tv-type led price 412 stock 12
eval "return redis.call('HGET', KEYS[1], ARGV[1])" 1 product:tv-samsung-led42 manufacturer
eval "return redis.call('HGET', 'product:tv-samsung-led42', 'manufacturer')" 0
eval "return redis.call('HMGET', KEYS[1], ARGV[1], ARGV[2])" 1 product:tv-samsung-led42 manufacturer price

eval "return redis.call('HSCAN', KEYS[1], ARGV[1])" 1 product:tv-samsung-led42 0

A continuación se muestra el resultado de ejecución de los anteriores comandos en redis-cli.

Lua proporciona estructuras de control (condicionales, bucles, etc.), operadores, etc., como cualquier otro lenguaje de programación, y tiene también sus detalles peculiares, por ejemplo, los arrays en Lua son basado en 1 en lugar de 0, y Lua tiene un único tipo de dato numérico (no distingue entre int y float, que suele implicar perder los decimales al trabajar con float, excepto que los devolvamos a Redis como String).

Al ejecutar un Script Lua en Redis, es necesario transferirlo a través de la red, parsearlo/compilarlo, para luego ejecutarlo y devolver los valores que corresponda. Dado que la transferencia de los Scripts a través de la red y el parseo/compilación son tareas costosas, Redis mantiene una caché de Scripts compilados, de tal modo que un Script que sea ejecutado repetidas veces se verá beneficiado de la misma. Así, podemos utilizar el comando SCRIPT LOAD para subir un Script y compilarlo, el cual, nos devolverá un Hash, que luego lo podremos usar para ejecutar dicho Script utilizando el comando EVALSHA pasándole dicho hash (en lugar de todo el Script, que acabaría de nuevo siendo transferido por la red).

También podemos comprobar si un Script existe con el comando SCRIPT EXISTS, indicándole los hash de los Scripts que deseamos comprobar, y el comando SCRIPT FLUSH borrará la caché de Scripts de Redis. Si un Script se ejecuta con algún bug y/o no termina su ejecución, podemos utilizar el comando SCRIPT KILL para finalizar su ejecución. Además, tenemos el comando SCRIPT DEBUG que permite invocar un depurado interactivo para Lua (no recomendable en Producción).

script load "local cont=redis.call('GET', KEYS[1]) return cont"
evalsha "1429b1a746381bcb7731b1081d5da696608d2ac2" 1 cont-key
script exists "1429b1a746381bcb7731b1081d5da696608d2ac2"
script flush
script exists "1429b1a746381bcb7731b1081d5da696608d2ac2"

A continuación se muestra el resultado de ejecución de los anteriores comandos en redis-cli.

La ejecución de un Script Lua en Redis es atómica, sin embargo, por defecto no debe superar los 5 segundos (esta configuración se puede cambiar), pues en caso de exceder los 5 seg, el Script se abortará sin haber terminado, pudiendo dejar los datos inconsistentes, dejando registrando esta situación en el Log (por si lo queremos monitorizar).

Despedida y Cierre

Hasta aquí llega este Post, en el que hemos hecho una introducción al uso de Lua para la ejecución de código de servidor en Redis, de forma similar a los procedimientos almacenados de las bases de datos relacionales, una técnica que nos puede ser de utilidad en muchos casos.

Poco más por hoy. Como siempre, confío que la lectura resulte de interés.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

9 + 2 =