RE:NODE

Базы данных14 мин чтения

Redis: когда он нужен и как запустить его самостоятельно

Сессии, кэши, очереди и ограничение частоты запросов - вот настоящие причины. Что уже умеет ваша база данных и как установить, защитить и рассчитать Redis на VDS.

Обновлено

0 прочтений

Redis быстрый, и именно поэтому его добавляют в проекты, которым он не был нужен. Это вторая служба, которую нужно установить, защитить, мониторить, рассчитать и из которой можно потерять данные, и место в проекте она должна заслужить, выполняя конкретную работу, с которой ничто из уже запущенного справляется плохо. «База данных медленная» - это не такая работа; обычно это отсутствующий индекс.

Есть четыре задачи, для которых он действительно подходящий инструмент, и у всех одна и та же форма: небольшие порции состояния, общие для нескольких процессов, потерю которых в неудачный день вы переживёте. Сессии, кэши, очереди и счётчики. Если ваша причина есть в этом списке, добавляйте. Если нет, эта статья сэкономит вам один демон. А если всё же добавите, запускать его придётся самим: продукта Redis здесь нет, поэтому вторая половина статьи о том, как установить, настроить и защитить его на машине, где у вас есть root.

Что такое Redis на самом деле#

Не кэш. Кэш - это одна из вещей, которые с его помощью можно построить. Redis - это сервер структур данных в памяти: он держит ваши данные в RAM, при желании пишет копию на диск и говорит на небольшом текстовом протоколе по TCP-порту 6379. Команды работают с типизированными структурами, а не с безликими блобами, и в этом вся польза.

ТипЧто вы хранитеТипичное применение
StringБайты или числоКэшированный JSON, счётчики через INCR
HashПары «поле - значение»Сессия, запись о пользователе
ListУпорядоченная последовательностьПростая очередь на LPUSH и BRPOP
SetНеупорядоченные уникальные элементыПользователи онлайн, дедупликация
Sorted setЭлементы с оценкамиТаблицы лидеров, отложенные задачи, скользящие окна
StreamЖурнал только для добавления с идентификаторамиКонвейеры событий с группами потребителей

Команды выполняются в одном потоке. Поэтому любая команда атомарна без каких-либо ваших усилий, и именно поэтому он так хорош для счётчиков и блокировок, а заодно и поэтому одна медленная команда блокирует всё остальное на сервере. KEYS * на миллионе ключей - это не медленный запрос, а короткая авария. Всегда используйте SCAN.

Второе определяющее свойство: всё живёт в памяти. Диск - это копия, а не хранилище. В этом источник и скорости, и риска, и именно поэтому «могу ли я это потерять?» - первый вопрос, который стоит задать о любых данных, которые вы туда кладёте.

Четыре задачи, ради которых его стоит добавлять#

Сессии, общие для нескольких процессов. Как только вы запускаете больше одного процесса приложения, состояние сессии внутри процесса перестаёт работать: пользователь входит на worker 1 и остаётся анонимом на worker 2. Общее хранилище сессий решает это одной строкой конфигурации. Это самая частая честная причина добавить Redis.

Кэш для чего-то действительно дорогого. Дорогое - это запрос, который не удаётся оптимизировать индексом, отчёт, агрегирующий миллион строк, или обращение к чужому API, за которое вы платите. Шаблон называется cache-aside: смотрим в кэш, а при промахе вычисляем значение и сохраняем его с продуманным TTL.

javascript
const key = `v1:report:${teamId}:${day}`;let report = await redis.get(key);if (!report) {  report = JSON.stringify(await buildReport(teamId, day));  await redis.set(key, report, "EX", 900);   // 15 minutes}

Две детали отличают кэш от обузы. Ставьте в ключ префикс версии, чтобы смена формы значения означала смену префикса, а не сброс всего. И задавайте TTL всему, даже тому, что, как вам кажется, никогда не меняется, потому что кэш без срока годности - это второй источник истины, за которым никто не следит.

Очередь для работы, которая не должна выполняться во время запроса. Отправка почты, изменение размера изображения, обращение к медленной внешней службе. Запрос записывает задачу и возвращается; процесс-исполнитель подхватывает её. BullMQ на Node и Celery или RQ на Python работают поверх Redis и берут на себя повторные попытки, отложенный запуск и очередь недоставленных задач, которые вы иначе написали бы плохо.

Счётчики, которые должны быть общими. Классический случай - ограничение частоты запросов: счётчик внутри процесса умножает ваш лимит на число исполнителей и сбрасывается при каждом deploy. INCR со сроком жизни - это две команды, и они корректны для всех процессов сразу. В статье ограничение частоты: где ставить лимиты разобрано, что считать и по какому ключу.

сессия и кэшсессия и кэшзаписи, которые должны сохранитьсязадачи в очередирезультатыПроцесс приложения 1без состоянияПроцесс приложения 2без состоянияRedisсессии, кэш, счётчикиИсполнительберёт задачиPostgreSQLважные данные
Что Redis на самом деле держит для вас

Обратите внимание, чего на этой схеме нет: ничего такого, потерю чего вы бы пережили с трудом. Истиной по-прежнему владеет база данных.

Причины, которые не являются причинами#

«База данных медленная». Сначала выясните почему. Выполните запрос с EXPLAIN ANALYZE, найдите последовательное сканирование большой таблицы, добавьте индекс и измерьте снова. Кэширование запроса без индекса прячет проблему до тех пор, пока кэш не остынет - а это случается при deploy, при перезапуске и ровно в тот момент, когда трафик подскакивает и все промахи приходят сразу. Как читать EXPLAIN ANALYZE - это день, потраченный дешевле, чем эксплуатация кэша.

«Им пользуются все». У всех ещё и больше одного сервера, график дежурств и тестовое окружение.

«Будет быстрее». Обращение к локальному Redis занимает несколько десятых миллисекунды. Как и поиск по первичному ключу в PostgreSQL с прогретым кэшем. Если ваша страница открывается за 800 мс, то обращение к базе - не те самые 800 мс.

«Он нам всё равно понадобится». Добавить его позже - дело одного дня. Эксплуатировать его год, когда он не был нужен, - это год.

И одна настоящая опасность, а не слабый довод: хранить то, что нельзя потерять. Redis можно настроить на сохранение данных, но можно и наоборот; кэш с политикой вытеснения по замыслу выбросит ваши данные, когда память на исходе, а настройки сохранения по умолчанию теряют до секунды записей при жёсткой остановке. Если на вопрос «что случится, если этот ключ исчезнет» ответ «клиент пожалуется», то данные принадлежат PostgreSQL или MongoDB, а не сюда.

Что у вас уже есть без него#

Прежде чем добавлять службу, проверьте, не делают ли эту работу те, что уже запущены. Обычно делают, и в масштабах, далеко превосходящих те, до которых доходит большинство проектов.

  • Сессии в одном процессе. Память процесса корректна и быстрее всего. При нескольких процессах подписанная cookie, несущая саму сессию, вообще не требует состояния на сервере, а таблица сессий в базе данных без труда справляется с тысячами пользователей.
  • Кэширование перед страницей. Cache-Control, ETag и правильно настроенный веб-сервер вообще не пускают запросы к вашему приложению, а это быстрее любого кэша, к которому может обратиться ваш код. Нужные заголовки описаны в статье заголовки HTTP-кэширования простыми словами.
  • Кэш внутри процесса. Ограниченный LRU внутри приложения - lru-cache на Node, functools.lru_cache или cachetools на Python - ничего не стоит, не требует сетевого обращения и подходит для всего небольшого, горячего и одинакового для всех пользователей, например для конфигурации или справочной таблицы. Его предел в том, что у каждого процесса своя копия.
  • Очередь в PostgreSQL. SELECT ... FOR UPDATE SKIP LOCKED даёт корректную транзакционную очередь задач в таблице, и у неё есть свойство, которого нет у Redis: постановка задачи в очередь и фиксация данных, которые её породили, происходят в одной транзакции. На несколько сотен задач в минуту её более чем достаточно, а как это сделать, показано в статье фоновые задачи на небольшом сервере.
  • Ограничение частоты в веб-сервере. limit_req в nginx применяет лимит ещё до того, как в дело вступит ваше приложение, и не требует вообще никакого хранилища.
sql
-- A queue that needs no second serviceUPDATE jobs SET status = 'running', started_at = now()WHERE id = (  SELECT id FROM jobs  WHERE status = 'queued' AND run_after <= now()  ORDER BY run_after  FOR UPDATE SKIP LOCKED  LIMIT 1)RETURNING id, payload;

Установка Redis на VDS#

RE:NODE не продаёт Redis: линейка баз данных - это PostgreSQL и MongoDB, а тарифы для приложений запускают стартовую команду из вашего репозитория, а не второй демон. Так что Redis вы запускаете на машине, где у вас есть root, то есть на VDS или выделенном сервере. Это не шаг назад: один экземпляр Redis - одна из самых простых вещей в эксплуатации, а на вашей собственной машине он может слушать только интерфейс loopback, что убирает большинство способов, которыми что-то идёт не так. Взамен вы берёте на себя всю машину: патчи, файрвол, мониторинг и backup, о чём сказано в статье выбор между VDS и игровой панелью.

На Debian или Ubuntu:

bash
$ sudo apt update && sudo apt install redis-server$ sudo systemctl enable --now redis-server$ redis-cli pingPONG

Пакет дистрибутива кладёт конфигурацию в /etc/redis/redis.conf, запускает его под systemd от пользователя redis и уже привязывает к localhost. Если нужна версия новее, чем в вашем дистрибутиве, проект Redis публикует собственные репозитории apt и rpm; на машине, где уже работают контейнеры, docker run с именованным томом ничуть не хуже - см. Docker на VDS.

Настройки, которые стоит изменить в первый же день, все лежат в /etc/redis/redis.conf:

/etc/redis/redis.conf
bind 127.0.0.1 -::1protected-mode yesport 6379requirepass a-long-random-string-from-a-password-managermaxmemory 512mbmaxmemory-policy allkeys-lruappendonly yesappendfsync everysecsave 900 1save 300 10save 60 10000

Затем две настройки ядра, на отсутствие которых Redis пожалуется в собственном логе. Overcommit памяти должен быть включён, иначе фоновое сохранение может не удаться на машине с малым запасом памяти; а transparent huge pages вызывают всплески задержки.

bash
$ echo 'vm.overcommit_memory = 1' | sudo tee /etc/sysctl.d/99-redis.conf$ sudo sysctl -p /etc/sysctl.d/99-redis.conf$ cat /sys/kernel/mm/transparent_hugepage/enabled

После перезапуска посмотрите лог командой journalctl -u redis-server и исправьте всё, о чём он предупреждает, а не ждите встречи с этим под нагрузкой. У первого часа на любой новой машине более широкий чек-лист - обновления, пользователь не root, ключи SSH, файрвол - он в статье первый час на новом VDS, а как удерживать рядом с ним собственное приложение, рассказано в статье службы systemd для ваших приложений.

Замечание об имени. В 2024 году Redis сменил лицензию, из-за чего и появился Valkey - форк под крылом Linux Foundation, а в 2025 году лицензирование снова изменилось. Для одного самостоятельно размещённого экземпляра ничто из этого на практике ничего не меняет: Valkey говорит на том же протоколе на том же порту, с теми же командами и тем же файлом конфигурации, и ваша клиентская библиотека не заметит, к какому из них подключилась.

Сохранение данных: RDB, AOF и что можно потерять#

Redis предлагает два механизма, и понимание разницы между ними - это разница между осознанным риском и неприятным сюрпризом.

Снимки RDB записывают весь набор данных в один файл через заданные промежутки, которыми управляют строки save выше - «через 900 секунд, если изменился хотя бы 1 ключ» и так далее. Снимок делается форком процесса, поэтому он дёшев по процессору и потенциально дорог по памяти: дочерний процесс с copy-on-write в худшем случае может потребовать почти столько же памяти ещё раз. Восстановление быстрое, а файл легко скопировать в другое место.

AOF, журнал только для добавления, записывает каждую команду записи по мере её выполнения и проигрывает журнал при запуске. appendfsync everysec - значение по умолчанию и разумный выбор: вы можете потерять до одной секунды записей при жёсткой остановке. always выполняет fsync на каждую запись и работает намного медленнее. no оставляет это ядру и может потерять десятки секунд.

Включайте оба. RDB даёт компактный файл, который можно унести как backup; AOF даёт куда меньшее окно потерь. Ни один из них не делает Redis системой учёта: штатное завершение сохраняет данные, а kill, отключение питания или остановка процесса ядром из-за нехватки памяти - нет. Арифметика здесь та же, что у любого другого хранилища с отложенной записью: данные, которые должны пережить сбой, принадлежат тому месту, где их зафиксировала транзакция, а копии, которые должны пережить сбой, - за пределами машины, о чём подробнее сказано в статье backup и восстановление баз данных.

Память, maxmemory и вытеснение#

maxmemory - самая важная настройка в файле, а её значение по умолчанию - «без ограничений», то есть Redis будет расти, пока не вмешается ядро. Задайте её явно, заметно ниже объёма памяти машины - хорошая отправная точка это половина, когда включено сохранение, - и выберите политику, соответствующую задаче.

ПолитикаЧто делаетПодходит для
noevictionОтклоняет записи с ошибкой, когда память заполненаОчереди, сессии, которые нельзя терять
allkeys-lruВытесняет ключ, к которому дольше всего не обращалисьЧистый кэш
allkeys-lfuВытесняет ключ, которым реже всего пользовалисьКэш с горячим подмножеством
volatile-lruВытесняет давно не использовавшийся среди ключей с TTLСмешанное использование, если TTL заданы
volatile-ttlВытесняет ключ, который истекает раньше всехСмешанное использование

Ловушка - в двух последних строках. Политика volatile-* рассматривает только ключи, у которых задан срок жизни. Если ни у чего в вашей базе нет TTL, она ведёт себя точно как noeviction, и записи начинают падать с ошибкой нехватки памяти, пока сервер выглядит наполовину простаивающим. Либо задайте TTL всему кэшируемому, либо используйте allkeys-lru, а то, что нельзя терять, храните в другом месте.

Расчёт размера - это измерение, а не арифметика. У каждого ключа перед значением накладные расходы около ста байт, так что миллион крошечных ключей - совсем не маленький набор данных. Инструменты:

bash
$ redis-cli info memory | grep -E 'used_memory_human|maxmemory_human'$ redis-cli info stats | grep -E 'evicted_keys|keyspace_hits|keyspace_misses'$ redis-cli --bigkeys$ redis-cli --latency

Коэффициент попаданий - число попаданий, делённое на сумму попаданий и промахов, - это то число, которое говорит, оправдывает ли кэш своё существование. Если он ниже примерно 80 процентов, что-то не так с вашими TTL или ключами. Растущий evicted_keys означает, что maxmemory слишком мал для рабочего набора, и это настоящий сигнал, в отличие от графиков процессора. Как выбирать именно такие числа, рассказано в статье мониторинг, который что-то вам сообщает.

Защита и ошибка, из-за которой серверы майнят#

Redis без аутентификации, доступный из интернета, - это не утечка, а shell. В набор команд входит смена рабочего каталога и имени файла дампа, так что злоумышленник может записать произвольный файл от имени пользователя Redis; классический вариант записывает ключ SSH в authorized_keys и заходит по нему. Автоматические сканеры находят открытые экземпляры в течение нескольких минут после их появления, а ставят они обычно майнер.

Защита короткая, и она не необязательная:

  1. Привяжите к localhost, если только чему-то на другой машине он действительно не нужен. bind 127.0.0.1 -::1 - значение по умолчанию в большинстве пакетов; не трогайте его. Если другому хосту нужно подключаться, используйте приватный интерфейс или SSH-туннель, но никогда не публичный адрес.
  2. Задайте `requirepass` длинным случайным значением и храните его в окружении вашего приложения, а не в репозитории - где ему место, описано в статье переменные окружения и секреты.
  3. Закройте порт на файрволе. Входящий трафик запрещён по умолчанию, разрешены несколько портов, которые вы собирались публиковать, и 6379 среди них нет. Правила есть в руководстве по файрволу UFW.
  4. Используйте пользователей ACL, если клиентов больше одного приложения. Redis 6 и новее умеет выдавать каждому клиенту имя пользователя с ограниченным набором команд, так что ваше веб-приложение не сможет вызвать CONFIG или FLUSHALL, даже если его взломают.
  5. Никогда не выставляйте его через обратный прокси. Redis говорит на собственном протоколе, а не на HTTP, и то, что он окажется за веб-сервером, безопасным его не делает.

Оставленный protected-mode yes - полезная страховка: без пароля и без явного bind Redis отказывается принимать подключения откуда-либо, кроме интерфейса loopback. Это ремень безопасности, а не стратегия.

FAQ#

Нужен ли Redis для небольшого сайта?

Почти наверняка нет. Один процесс приложения с кэшем внутри процесса, заголовки HTTP-кэширования впереди и правильно проиндексированная база данных покрывают сайт с тысячами посетителей в день. Добавляйте Redis, когда у вас больше одного процесса, которым нужно общее состояние, или есть задача, которую хочется убрать из пути запроса.

Можно ли использовать Redis как основную базу данных?

Можно, и большинству не стоит. В нём есть сохранение данных, но настройки по умолчанию теряют до секунды записей при жёсткой остановке, политика вытеснения может удалять ключи по замыслу, а всё должно помещаться в память. Используйте его для состояния, которое можно восстановить, а важные записи держите в базе данных, которая фиксирует транзакции на диске.

Где запускать Redis, если мой хостинг его не продаёт?

На машине, где у вас есть root: на VDS или выделенном сервере. Установите пакет дистрибутива, привяжите его к localhost, задайте пароль и лимит maxmemory и пусть ваше приложение подключается через интерфейс loopback. Это ещё и самая безопасная из доступных конфигураций, потому что порт вообще никогда не выставляется наружу.

Redis или Valkey?

Для одного самостоятельно размещённого экземпляра подойдёт любой. Valkey - это форк под Linux Foundation, созданный после смены лицензии в 2024 году; он говорит на том же протоколе, использует тот же файл конфигурации и работает с теми же клиентами. Берите тот, что есть в пакетах вашего дистрибутива, и идите дальше.

Сколько памяти ему выделить?

Достаточно для вашего рабочего набора плюс накладные расходы и не больше примерно половины машины, потому что фоновое сохранение делает форк процесса, и копии на короткое время может понадобиться много дополнительной памяти. Задайте maxmemory явно, следите за evicted_keys и поднимайте лимит, когда это число начнёт расти.

Почему мои записи падают с ошибкой нехватки памяти?

Потому что maxmemory достигнут, а политика ничего не вытесняет. Либо политика noeviction, либо это политика volatile-*, а ни у одного из ваших ключей нет TTL, что равносильно тому же. Задайте TTL, переключитесь на allkeys-lru или поднимите лимит.


Комментарии

Полностью анонимно: без аккаунта, без почты, без cookie. Мы храним имя, которое вы ввели, текст и время - больше ничего. Количество ссылок ограничено, разметка не отображается.

0/2000