Транзакции и пайплайнинг
Иногда нужно выполнить несколько команд как единое целое или отправить их пачкой. Redis даёт для этого два разных механизма.
Транзакция отвечает на вопрос «выполнить всё вместе?». Пайплайнинг отвечает на вопрос «отправить всё разом по сети?». Это не одно и то же.
Два механизма, которые часто путают: транзакции (MULTI/EXEC) и пайплайнинг. Первый — про атомарность группы команд. Второй — про производительность сети. Разберём оба.
Транзакции: MULTI / EXEC
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET account:a 100
QUEUED
127.0.0.1:6379> INCRBY account:b 50
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) (integer) 50
MULTI начинает транзакцию. Команды не выполняются сразу — они ставятся в очередь (QUEUED). EXEC выполняет их все подряд, атомарно: между ними не вклинится команда другого клиента. DISCARD отменяет транзакцию.
Важно понимать: транзакции Redis — это не транзакции SQL. Здесь нет отката (rollback) при ошибке выполнения одной команды. Если команда синтаксически корректна, но падает в рантайме (например, INCR над строкой), остальные всё равно выполнятся.
Оптимистичная блокировка: WATCH
WATCH следит за ключом. Если ключ изменился до EXEC, транзакция отменяется. Это «оптимистичная блокировка» — основа для безопасного read-modify-write:
WATCH balance
val = GET balance # читаем
MULTI
SET balance <новое> # если balance не менялся — применится
EXEC # иначе вернёт nil, повторяем
Пайплайнинг: меньше сетевых кругов
Пайплайнинг — это отправка нескольких команд одним пакетом, не дожидаясь ответа на каждую. Это резко снижает влияние сетевой задержки:
Без пайплайна: С пайплайном: send CMD1 send CMD1 wait ответ send CMD2 все разом send CMD2 send CMD3 wait ответ recv 3 ответа send CMD3 wait ответ 1 round-trip вместо 3 = 3 round-trip
Если у вас 100 команд и пинг до сервера 1 мс, без пайплайна это 100 мс ожидания. С пайплайном — около 1 мс. Разница колоссальна.
Как работает под капотом
При MULTI Redis копит команды в очереди клиента, проверяя только синтаксис. На EXEC вся очередь выполняется как один непрерывный блок — другие клиенты ждут. WATCH реализует механизм CAS (compare-and-swap): сервер запоминает «версию» наблюдаемых ключей, и если она изменилась, EXEC возвращает nil. Пайплайнинг же — чисто клиентская оптимизация транспорта: сервер просто получает несколько команд в буфере и отвечает на все.
Частые ошибки
- Ждать от MULTI/EXEC отката как в SQL. Его нет. Проверяйте корректность данных заранее.
- Путать транзакцию и пайплайн. Пайплайн не делает команды атомарными — он лишь группирует их по сети.
- Сложная условная логика в транзакции. MULTI/EXEC не умеет ветвиться по промежуточным результатам — для этого нужны Lua-скрипты.
Best practices
- Для атомарной группы команд —
MULTI/EXEC, при необходимости сWATCH. - Для массовых операций — пайплайнинг, чтобы убрать сетевые задержки.
- Когда нужна логика «прочитать, решить, записать» атомарно — используйте Lua-скрипты (об этом в разделе про лимиты).
Итог: MULTI/EXEC группирует команды атомарно, но без SQL-отката. WATCH даёт оптимистичную блокировку через CAS. Пайплайнинг — это про сеть, а не про атомарность. Для сложной условной логики используйте Lua.
Когда нужен Lua вместо транзакции
Граница между MULTI/EXEC и Lua-скриптами часто вызывает путаницу, поэтому зафиксируем её чётко. Транзакция умеет только выполнить заранее известный набор команд атомарно. Она не может посмотреть на промежуточный результат и решить, что делать дальше.
# Это НЕЛЬЗЯ выразить транзакцией — нужна ветка по результату:
# "прочитать счётчик; если меньше лимита — увеличить и разрешить,
# иначе отказать"
# В Lua это естественно:
EVAL "local c = redis.call('INCR', KEYS[1])
if c == 1 then redis.call('EXPIRE', KEYS[1], ARGV[1]) end
if c > tonumber(ARGV[2]) then return 0 end
return 1" 1 limit:user42 60 100
Скрипт читает счётчик, ставит TTL на первом обращении и сравнивает с лимитом — всё это атомарно, как одна команда. Транзакция так не умеет, потому что не ветвится по данным. Поэтому правило: нужна простая группировка нескольких команд — берите MULTI/EXEC; нужна логика «прочитал, решил, записал» — берите Lua.
И помните про третий, ортогональный инструмент — пайплайнинг. Его можно комбинировать с любым из двух: отправить пачкой хоть набор обычных команд, хоть несколько транзакций, экономя сетевые круги.