Pipelining
Предпосылки: Event loop, RTT (round-trip time — время от отправки запроса до получения ответа).
← Event loop | Логические базы →
Проблема: ожидание ответа на каждую команду
Стандартный обмен между клиентом и Redis выглядит так: клиент отправляет команду, ждёт ответа, отправляет следующую. При RTT 0.1 мс внутри одного дата-центра и 100 командах на одну страницу — 10 мс уходит только на ожидание сети, хотя Redis выполняет каждую команду за микросекунды. При RTT 1 мс между зонами доступности потери вырастают на порядок.
Pipelining: отправка без ожидания ответа
Pipelining — это отправка нескольких команд подряд, не дожидаясь ответа на каждую. Клиент записывает N команд в TCP-соединение, затем читает N ответов. Redis получает команды и выполняет их последовательно — в зависимости от объёма данных это может занять один или несколько оборотов event loop.
Без pipelining: С pipelining:
клиент → SET a 1 клиент → SET a 1
клиент ← OK SET b 2
клиент → SET b 2 SET c 3
клиент ← OK клиент ← OK
клиент → SET c 3 OK
клиент ← OK OK
3 RTT 1 RTTПри 100 командах pipelining сокращает общее время со 100 RTT до 1 RTT. На уровне Redis ничего не меняется — команды выполняются последовательно, как обычно. Выигрыш целиком за счёт сети.
Pipelining не даёт атомарности
Команды в pipeline выполняются последовательно, но между ними могут вклиниться команды от других клиентов. Если клиент A отправил pipeline из SET x 1; SET y 2, а клиент B одновременно отправил GET x, клиент B может увидеть новое значение x, но старое значение y.
Если нужна гарантия, что набор команд выполнится без вклинивания, используется EXEC — все команды между MULTI и EXEC выполняются как единое целое. MULTI/EXEC можно комбинировать с pipelining: отправить MULTI, команды и EXEC одним пакетом, получив и атомарность, и экономию на сетевых задержках.
Ограничение размера pipeline
Если pipelining так выгоден, почему не отправить все команды одним пакетом? Потому что Redis накапливает ответы в выходном буфере клиента, пока клиент их не прочитает. При pipeline из десятков тысяч команд объём накопленных ответов может вырасти настолько, что Redis превысит лимит client-output-buffer-limit (ограничение на размер выходного буфера одного соединения) и принудительно закроет соединение, либо исчерпает память сервера.
На практике pipeline разбивают на серии по 100–1000 команд: отправить пачку, прочитать ответы, отправить следующую. Это сохраняет выигрыш от pipelining (один RTT на пачку вместо одного RTT на команду) и не перегружает память Redis.
Когда pipelining не помогает
Pipelining работает, когда команды независимы — их можно отправить все сразу, не глядя на результаты. Если каждая следующая команда зависит от результата предыдущей (прочитать значение, посчитать что-то на его основе, записать результат), отправить их одним пакетом невозможно. В таких случаях нужен либо Lua-скрипт, который выполнит всю логику на стороне Redis, либо несколько последовательных round-trip’ов.
Sources
- Redis Documentation: Pipelining. https://redis.io/docs/manual/pipelining/
- Redis source:
src/networking.c, обработка клиентского буфера
← Event loop | Логические базы →