Часто Redis «медленный» не из-за самого Redis, а из-за тысяч сетевых round-trip'ов. Пайплайны и батчирование снимают это ограничение в разы.
Почему каждый запрос — это RTT
Без пайплайна цикл такой: отправить команду → ждать ответ → отправить следующую. При 0.5 мс RTT и 1000 команд это 0.5 секунды чистого ожидания, хотя сам Redis исполнил команды за микросекунды. Пайплайн отправляет пачку команд, не дожидаясь ответов, и читает ответы одним заходом:
# Псевдокод: 1000 GET одним round-trip'ом
pipe = redis.pipeline(transaction=False)
for key in keys:
pipe.get(key)
results = pipe.execute()
Пайплайн ≠ транзакция
Команды пайплайна могут исполняться вперемешку с командами других клиентов. Пайплайн — только сетевая оптимизация; атомарность — это MULTI/EXEC или Lua (урок 5.3).
Размер батча
- Практический диапазон: 100–1000 команд на батч. Гигантский пайплайн накапливает ответы в буферах обеих сторон (следите за client output-buffer);
- Для чистых чтений одной команды лучше
MGET/MSET— одна команда вместо N; - Смешанные операции — пайплайн.
Батчирование и структуры данных
| Анти-паттерн | Решение |
|---|---|
| 1000 × GET в цикле | MGET или пайплайн |
| 1000 × SET в цикле | MSET или пайплайн |
| HGET поля по одному | HMGET всех нужных полей |
| 10 × SADD по одному | один SADD с 10 элементами |
| ZADD по одному | ZADD с пачкой пар score-member |
Клиентская гигиена
- Пулы соединений: создание соединения — дорогая операция; переиспользуйте;
- Таймауты: всегда задавайте connect/operation timeouts — зависший сокет без таймаута уронит пул потоков приложения;
- Обработка ошибок: OOM (noeviction), NOAUTH, MOVED, READONLY — клиент должен уметь ретраить/переподключаться с бэкоффом, а не сыпать стек-трейсами;
- Десериализация: тяжёлый JSON-парсинг на стороне приложения часто дороже самого GET — считайте весь путь;
- Сжатие: для значений > 1 КБ сжимайте (gzip/snappy) — экономия трафика и памяти в 3–10 раз.
Серверные параметры сети
# redis.conf
tcp-backlog 511 # очередь приёма соединений
timeout 0 # не рвать idle-соединения (пул сам следит)
tcp-keepalive 300
maxclients 10000 # и ulimit -n соответствующий
Практика
- Замерьте 1000 GET: по одному, MGET, пайплайном по 100. Зафиксируйте три цифры и объясните разницу.
- Поднимите RTT: доступ к Redis через сеть с 20 мс latency (docker в другой сети/simulate) — перепроверьте, что пайплайн спасает именно там, где RTT большой.
- Добавьте в свой клиентский слой пул соединений + таймауты + ретраи с джиттером; напишите тест «Redis недоступен 2 секунды» — приложение должно пережить без каскадного падения.
Мини-квиз
1. Зачем MGET, если есть пайплайн? — Одна команда вместо N: меньше парсинга, буферов и сетевых пакетов.
2. Гарантирует ли пайплайн атомарность? — Нет; только батч-отправку. Атомарность — MULTI/EXEC или Lua.
3. Первая причина «Redis тормозит из приложения, но redis-benchmark быстрый»? — Множественные RTT без батчирования/пайплайна, отсутствие пула соединений.