Command Palette

Search for a command to run...

ES·EN

Nivel 2 · 25 min

Lua Scripting en Redis

Redis permite ejecutar scripts Lua de forma atómica — el script completo se ejecuta sin interrupciones, como una transacción. Esto resuelve problemas de race conditions que no se pueden solucionar con comandos individuales ni con MULTI/EXEC cuando necesitás leer y decidir antes de escribir.

Atomicidad de Lua en Redis

Cuando ejecutás EVAL script, Redis ejecuta el script en un solo thread, sin que otros comandos puedan intercalarse. Esto garantiza atomicidad completa: podés leer un valor, hacer una decisión basada en él y escribir el resultado — todo sin race conditions. EVAL es diferente de MULTI/EXEC: con MULTI/EXEC no podés leer en el medio de la transacción para tomar decisiones condicionales.

Rate limiting con Lua

Un caso de uso canónico es el rate limiter: verificar si el usuario pasó el límite de requests y, si no lo hizo, incrementar el contador en una sola operación atómica. Sin Lua, este patrón tiene un race condition entre el GET y el INCR. El script Lua lee el valor actual, decide, incrementa y establece TTL — todo atómicamente. Implementación: ventana fija, ventana deslizante con Sorted Set.

EVALSHA y cache de scripts

EVAL envía el script completo cada vez — ineficiente para scripts frecuentes. SCRIPT LOAD carga el script en Redis y devuelve un SHA1. Luego usás EVALSHA sha1 para ejecutar por hash. Redis cachea el script en todos los nodos. Si el script no está cacheado (nodo reiniciado), Redis devuelve NOSCRIPT — tu cliente debe volver a EVAL y cargar. Esto es importante en Cluster mode donde distintos nodos pueden tener distintos caches.

Puntos clave

  • Lua en Redis garantiza atomicidad completa — lee + decide + escribe sin race conditions posibles.
  • EVALSHA + SCRIPT LOAD evitan enviar el script completo en cada llamada — ahorro de bandwidth y parsing.
  • En Redis Cluster, los scripts solo pueden operar en claves del mismo hash slot — usá hash tags para garantizarlo.

Code example

-- Rate limiter: atomico, sin race condition
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local current = redis.call(''INCR'', key)
if current == 1 then
  redis.call(''EXPIRE'', key, window)
end
if current '>' limit then
  return 0  -- rechazado
else
  return 1  -- permitido
end