WATCH и CAS

Тема дорожной карты · Redis

WATCH — команда Redis, обеспечивающая оптимистичную блокировку и паттерны compare-and-swap (CAS) в in-memory хранилище, позволяя клиентам условно выполнять транзакцию только в том случае, если наблюдаемые ключи не были изменены другим клиентом за это время. Паттерн выглядит так: вызов WATCH key [key ...], чтение текущих значений, открытие блока MULTI, постановка записей в очередь и вызов EXEC — если любой наблюдаемый ключ был изменён между WATCH и EXEC, Redis прерывает транзакцию, возвращая nil-ответ, и приложение повторяет CAS-цикл. Этот подход оптимистичного параллелизма хорошо подходит для сценариев распределённого кэша, таких как неблокирующее обновление счётчиков, вычитание запасов или пополнение токен-бакетов, где конкуренция невысока и повторные попытки дёшевы. WATCH автоматически снимается после EXEC или DISCARD, а вызов UNWATCH до EXEC отменяет наблюдение без выполнения транзакции. Для более сложной compare-and-swap логики с несколькими условиями вместо транзакций на основе WATCH часто предпочтительнее Lua-скриптинг через EVAL, поскольку он устраняет циклы повторных попыток, выполняясь атомарно на Redis-сервере.

Как это работает

WATCH и CAS имеет два инструмента атомарности. MULTI/EXEC ставит команды в очередь; они выполняются как один блок — но нет rollback (Redis не ACID-D, изоляции тоже нет, если читаете между командами). WATCH добавляет optimistic locking — EXEC валится, если watched-ключ изменился. EVAL / EVALSHA исполняет Lua-скрипты атомарно на сервере; скрипт — одна неделимая операция. Lua — правильный инструмент для compound read+write, которые должны быть атомарными.

Когда применять

MULTI/EXEC — для "выполнить эти N команд как единицу без других команд между". WATCH + MULTI/EXEC — для compare-and-swap. Lua (EVAL) — для сложной атомарной логики (rate limiter, debounce, distributed semaphore). Всегда кешируйте SHA (EVALSHA) вместо повторной отправки тела скрипта — экономит bandwidth и парсинг.

Типичные ошибки

Ловушки WATCH и CAS: расчёт, что MULTI/EXEC — транзакция в SQL-смысле (нет — без rollback); долгие Lua-скрипты блокируют однопоточный сервер (всё ждёт EXEC); не обрабатывают nil от EXEC (WATCH-конфликт — retry); Lua-код, мутирующий ключ на основе текущего значения без WATCH (race condition при разделении на команды).

Связанные понятия

Полезные ресурсы