Прямой ответ
Redis — in-memory хранилище «ключ → структура», и большинство задач решается правильной структурой, а не JSON-строкой в string: hash — объект с полями (профиль пользователя: HSET/HGET/HINCRBY отдельных полей — чтение/изменение одного поля без сериализации всего объекта), list — очередь/стек простейшая (LPUSH+BRPOP — блокирующая выдача), set — уникальные множества с операциями (общие друзья, дедупликация — SADD/SINTER/SCARD), sorted set — лидерборды и планировщики (ZADD score → ZRANGEBYSCORE — «задачи, созревшие к выполнению» — delayed queue паттерн), stream — журнал с consumer groups (мини-Kafka: XADD/XREADGROUP с подтверждениями — гарантии доставки в облегчённом виде). Три паттерна поверх структур: кеш с обязательным TTL (запись без TTL — будущая утечка памяти: ключи живут вечно, память кончается — кеш-дисциплина), распределённая блокировка (SET key val NX PX — acquire атомарен; ценность-токен и Lua-скрипт release — не отпустить чужой блокиратор; Redlock-дебаты — для большинства задач одиночный Redis с NX достаточно при понимании ограничений), счётчики/рейт-лимиты (INCR + EXPIRE — атомарные окна — лимиты). И граница применимости: Redis — не БД (durability по умолчанию ослаблена: RDB-снапшоты теряют последние секунды, AOF everysec — до секунды; данные, потеря которых неприемлема, живут в Postgres — Redis для производных/временных/ускоряющих).
Структуры: что где применять
String (+INCR/SETEX) — кеш сериализованного значения, счётчики, флаги; SET k v NX PX 30000 — блокировка одной командой. Hash — «объект»: поля читаются/пишутся/инкрементятся независимо (HINCRBY stats:logins — счётчик в профиле без read-modify-write), экономия памяти на мелких полях (listpack-encoding), TTL — на весь hash (на поле — только обходными паттернами — знать ограничение). List — простая очередь (LPUSH producer / BRPOP consumer — блокирующее чтение без busy-polling) и capped-коллекции (LTRIM — «последние 100 событий»); ограничения — один потребитель на элемент, без подтверждений/повторов (упало между BRPOP и обработкой — сообщение потеряно — для надёжного нужен stream или внешняя очередь). Set — уникальность и алгебра множеств (SADD seen:id — дедуп обработанных событий; SINTER — пересечения; SRANDMEMBER — выборка), Sorted set — упорядоченные по score: лидерборды (ZINCRBY + ZREVRANGE), отложенные задачи (score = timestamp выполнения — воркер берёт ZRANGEBYSCORE 0 now LIMIT и переносит в рабочую очередь атомарно — ZPOPMIN-класс), скользящие окна (score = время события — ZREMRANGEBYSCORE чистит старое). Stream — append-only журнал: consumer groups (каждая группа читает независимо — паттерн Kafka в миниатюре), XACK подтверждений, pending-список (неподтверждённые — XAUTOCLAIM другим консьюмером при падении — at-least-once локально); XADD MAXLEN ~ 100000 — ограничение длины (журнал без лимита — память).
Паттерны: кеш, блокировки, лимиты
Кеш — cache-aside (читай кеш → miss → БД → запись с TTL — основа; все паттерны — write-through/invalidation — там же), обязательные детали: TTL всегда (бессрочные ключи — медленная смерть памяти), джиттер TTL (одинаковый TTL на пачке ключей — одновременное истечение → шквал в БД — cache stampede; ±10% разброса), версия в ключе при изменении схемы значения (user:v2:42 — смена формата без отравления старым), и план на недоступность Redis (кеш лёг — трафик в БД вырастет — деградация заложена: таймауты короткие, fail-open — resilience-мышление). Блокировка: SET lock:report 7f3a... NX PX 30000 — взята, если ключа нет (NX), с автоматическим истечением (PX — защита от «взял и умер»); release — Lua-скриптом «если значение моё — удалить» (иначе — снять чужую блокировку после своего таймаута — классическая гонка); продление — watchdog-паттерн для долгих операций; честные ограничения: одиночный Redis при failover может выдать две «эксклюзивные» блокировки (Redlock — попытка решения на N узлах — со спорами о корректности); для «ровно одна задача на кластер» надёжнее advisory-lock БД (Postgres-паттерн) — Redis-блокировка для коротких координаций, не для критичной взаимности. Лимиты/окна: INCR + EXPIRE (фиксированное окно), sorted set со score-временем (скользящее — алгоритмы — реализация).
Эксплуатация: память, стойкость, границы
Память: maxmemory + eviction-политика обязательны (allkeys-lru для кеша — вытеснение холодного; noeviction — отказ записи при заполнении — для данных-не-кеша; «Redis кончился по памяти и пишет ошибку» — обычно отсутствие maxmemory-политики), INFO memory/used_memory — в мониторинг (метрики); большие ключи (hash на миллионы полей, list-гигант) — зло для latency и вытеснения — дробить; SCAN вместо KEYS в проде (KEYS блокирует сервер на большой базе — классический самострел). Стойкость: RDB (снапшоты — быстрые рестарты, теряют минуты), AOF (лог операций — everysec: потеря ≤1 с), обычно оба; для чистого кеша стойкость можно отключить вовсе (данные производные — прогреются) — осознанное решение, записанное в ADR (документация решений). Границы: Redis не для — единственного хранилища бизнес-данных (durability/запросы), больших объёмов (память дорога против диска), сложных запросов (нет SQL — DuckDB/Postgres для аналитики), долгоживущих больших очередей с гарантиями (Kafka/кролик — сравнение); Redis идеален для — кеша, сессий, счётчиков, коротких очередей/стримов, блокировок-координаций, рейтингов — всё, что «горячее, временное или производное».
Практический сценарий: три роли Redis в одном сервисе
Сервис заказов подключает Redis последовательно под три задачи. (1) Кеш каталога: cache-aside на карточки товаров (TTL 300 с + джиттер, версионный ключ cat:v3:{id} — смена схемы сериализации без чистки); нагрузка на Postgres −60%, p95 карточки 12 мс; Redis лёг на стейдже (учебно) — сервис деградировал в «всё из БД» с ростом p95 до 400 мс, но работал — fail-open подтверждён. (2) Очередь уведомлений: сначала list (LPUSH/BRPOP) — хватило, пока уведомления не стали теряться при рестарте воркера (между BRPOP и отправкой) — перевод на stream + consumer group + XACK (неподтверждённые возвращаются — письма перестали пропадать; гарантии в действии) — идемпотентность отправки по event_id уже была. (3) Блокировка ночного отчёта: два воркера по cron запускали тяжёлый отчёт одновременно (двойная нагрузка на реплику) — SET lock:nightly uuid NX PX 1800000 + Lua-release — отчёт ровно один на кластер; блокировка выбрана Redis-овая, не БД-овая, сознательно: операция идемпотентна и «двойной запуск» — стоимость, а не катастрофа (записано в ADR). Итог: один Redis, три паттерна, ни одной попытки сделать из него основную БД — роли подобраны под свойства хранилища.
FAQ
Redis против Memcached в 2026? Memcached — только кеш string-значений, мультипоточность; Redis — структуры, персистентность, streams — почти всегда выбор по умолчанию; Memcached оправдан в нише «чистый кеш, максимум простоты на ядро» — экосистема и инструменты Redis шире.
Почему моя Redis-блокировка «не эксклюзивная»? Причины: release без проверки владельца (снята чужая), таймаут короче операции (блокировка истекла во время работы — два владельца одновременно — watchdog/больший PX), failover с асинхронной репликацией (реплика без ключа стала мастером); для жёсткой взаимности — БД-advisory lock или Redis с пониманием ограничений и идемпотентной операцией.
Как хранить сессии в Redis правильно?
Ключ session:{token} → hash полей (user_id, права, данные корзины), TTL с продлением на активность (EXPIRE при запросе — «скользящая сессия»), токен — случайный 256 бит (не угадываемый — аутентификация: JWT vs серверные сессии — там же); инвалидация — DEL ключа (мгновенный logout — преимущество перед stateless JWT).
Нужен ли кластер Redis приложению на 10 ГБ? Обычно нет: один инстанс 10–30 ГБ с maxmemory и репликой для HA покрывает большинство продуктов; Redis Cluster (шардирование) — от десятков ГБ и сотен тысяч ops/s — сложность (multi-key операции в пределах слота, миграции) значительная; managed-варианты снимают эксплуатацию до порога, где кластеризация — не ваша забота.
Источники: документация Redis (data types, patterns, python client) — ссылки в шапке.