В прошлом уроке скрипт Lua склеивал чтение и запись в одну поездку на сервер. Но бывает задача проще: отправить много независимых команд. Например, ночью обнулить счётчики просмотров у десяти тысяч книг или прогреть кэш после перезапуска. Каждая команда выполняется за микросекунды, а вся пачка почему-то идёт секунды. В уроке 6 мы назвали виновника, сеть, и показали redis-cli --pipe. Сегодня считаем, откуда берутся секунды, и учимся отправлять пачки из кода.
Лифт и грузчик
Грузчик переносит сто коробок с первого этажа на десятый. Если везти по одной коробке, лифт сто раз съездит вверх и вниз. Сама выгрузка коробки занимает секунду, а поездка минуту. Почти всё время уходит на лифт. Если загрузить в лифт двадцать коробок, поездок станет пять.
Redis это быстрая выгрузка, а сеть это лифт. Время поездки запроса до сервера и ответа обратно называют RTT, от английского round-trip time.
Считаем
Пусть Redis выполняет SET за 2 микросекунды, а RTT равен 1 миллисекунде. Это реальная цифра для двух серверов в соседних стойках или для облака.
- По одной команде: 10 000 × (1 мс + 0,002 мс) ≈ 10 секунд.
- Пачкой: одна поездка плюс 10 000 × 0,002 мс ≈ 1 мс + 20 мс = 21 мс.
Разница в пятьсот раз, и вся она в ожидании. Redis эти десять секунд почти не работал.
Проверим на настоящем Redis. Мы запустили клиент в соседнем контейнере и искусственно добавили 1 мс задержки в сеть (RTT вышел около 1,2 мс). Утилита redis-benchmark отправила 20 000 команд SET через одно соединение (-c 1), с разной глубиной конвейера -P:
$ redis-benchmark -h redis -q -t set -n 20000 -c 1 -P 1
SET: 812.41 requests per second, p50=1.191 msec
$ redis-benchmark -h redis -q -t set -n 20000 -c 1 -P 10
SET: 8153.28 requests per second, p50=1.191 msec
$ redis-benchmark -h redis -q -t set -n 20000 -c 1 -P 100
SET: 72727.27 requests per second, p50=1.311 msec
$ redis-benchmark -h redis -q -t set -n 20000 -c 1 -P 1000
SET: 363636.38 requests per second, p50=1.871 msec
По одной команде вышло 812 в секунду: ровно столько поездок по 1,2 мс помещается в секунду. Пачка из 10 команд дала в 10 раз больше, из 100 в 90 раз. На тысяче рост замедлился: теперь время уходит уже на саму работу, а не на сеть. У тебя числа будут другими, а картина та же.
Что такое конвейер
Без конвейера клиент и сервер ходят по очереди: запрос, ответ, запрос, ответ. С конвейером клиент отправляет все команды сразу, сервер выполняет их по порядку и отвечает пачкой.
без конвейера с конвейером
клиент SET a ──▶ клиент SET a, SET b, SET c ──▶
сервер ◀── OK сервер ◀── OK, OK, OK
клиент SET b ──▶
сервер ◀── OK
клиент SET c ──▶
сервер ◀── OK
Специальной команды для этого нет. Конвейер это способ писать в соединение: протокол Redis позволяет отправить следующую команду, не прочитав ответ на предыдущую. Всю работу делает клиент.
Из redis-cli: --pipe
Для разовой загрузки большого файла с командами в redis-cli есть режим --pipe. Сравним его с обычной подачей того же файла, где redis-cli ждёт ответ на каждую строку. Файл из 10 000 команд, та же сеть с RTT около 1,2 мс:
seq 1 10000 | sed 's/.*/SET book:&:views 0/' > cmds.txt
time redis-cli -h redis < cmds.txt > /dev/null
time redis-cli -h redis --pipe < cmds.txt
real 0m 12.16s
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 10000
real 0m 0.02s
Двенадцать секунд против двух сотых. --pipe годится для миграций и первичной загрузки данных. В приложении конвейер включают в клиентской библиотеке.
урок из курса
Урок из курса «Redis с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.
Оплата пока не подключена: премиум и курсы открываются промокодом.