Как работают скрипты в Redis и обеспечивают ли они атомарность операций
Что проверяют
Понимаете ли вы, что именно гарантирует атомарность скрипта и чего она не даёт: отката при ошибке нет. Следом обычно просят сравнить скрипты с MULTI/EXEC и WATCH или спрашивают, что будет с сервером при медленном скрипте.
Как отвечать
Скрипт пишется на Lua и отправляется командой EVAL script numkeys key... arg..., внутри команды вызываются через redis.call(). Команды Redis выполняются одним потоком, и на время скрипта сервер не обслуживает других клиентов. Никто не увидит промежуточного состояния и не вклинится между чтением и записью, в этом смысле скрипт атомарен.
Важные детали:
- нет отката: при ошибке посреди скрипта уже выполненные записи остаются.
redis.call()прерывает скрипт,redis.pcall()возвращает ошибку значением; - блокировка сервера: долгий скрипт останавливает всех. После порога
busy-reply-threshold(раньшеlua-time-limit, по умолчанию 5 секунд) остальные получаютBUSY, а скрипт можно снятьSCRIPT KILL, если он ещё ничего не записал; - ключи через
KEYS: все ключи передаются аргументами. В Redis Cluster они должны лежать в одном слоте, для этого есть hash tags вида{user:42}:balance; - кеш скриптов:
SCRIPT LOADвозвращает SHA1, дальше вызов идёт черезEVALSHA. Кеш не переживает перезапуск, поэтому на ошибкуNOSCRIPTклиент повторяет черезEVAL. С Redis 7 есть функции (FUNCTION LOAD,FCALL), которые сохраняются и реплицируются вместе с данными.
В Node.js это берут на себя клиенты. В ioredis redis.defineCommand('incrBy', { numberOfKeys: 1, lua }) создаёт метод, который вызывает EVALSHA и сам откатывается на EVAL. В node-redis аналог это defineScript в опциях createClient.
Где спотыкаются
Забывают, что GET по отсутствующему ключу возвращает в Lua false, а не nil. Арифметика с ним роняет скрипт, нужен tonumber() с проверкой.
Пишут if not redis.call('EXISTS', key). EXISTS возвращает число, а в Lua 0 считается истиной, поэтому условие не выполнится никогда.
Путают атомарность с транзакционностью и ждут, что при ошибке на третьей команде первые две откатятся, как в SQL. Не откатятся, поэтому все проверки делают до первой записи.