Раздел 37 · Redis с нуля

Конвейеризация: убираем задержки сети

junior~20 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

В прошлом уроке скрипт 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 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.