Redis: быстрое хранилище в памяти

Redis: быстрое хранилище в памяти

Redis - in-memory key-value хранилище, написанное на C. Применяется для кеширования, сессий, rate limiting, очередей, leaderboard, real-time счётчиков. Типичная latency на одной операции - 0.1ms, throughput - 100K+ ops/sec на ноутбуке. В отличие от PostgreSQL, нет JOIN и сложных запросов - только простые операции по ключу.

Архитектура: single-threaded event loop

Главный command processing thread у Redis - единственный. Это упрощает модель: атомарность операций «из коробки», нет race conditions внутри. Параллелизм достигается через event loop (epoll/kqueue) и фоновые I/O треды для дисковых операций (с Redis 6+).

Следствие: одна медленная команда (KEYS * на миллионы ключей, LRANGE 0 -1 на больших списках) блокирует весь сервер. Используйте SCAN вместо KEYS, LRANGE 0 100 с пагинацией.

Запуск через Docker

docker run -d --name redis -p 6379:6379 redis:7-alpine
docker exec -it redis redis-cli

Типы данных

Пять основных типов данных Redis: string, hash, list, set, sorted set с типичными сценариями

Strings - ключ/значение

SET user:1:name "Alice"
GET user:1:name
INCR counter            # атомарный инкремент
SET session:abc "data" EX 3600
TTL session:abc         # секунды до expire

Counter via INCR - атомарный. Не нужна блокировка, не нужен MUTEX. Используется для просмотров страниц, лайков, ID-генерации.

Hashes - поля внутри ключа

HSET user:1 name "Alice" email "alice@mail.com" age 30
HGETALL user:1
HINCRBY user:1 age 1

Дешевле хранить много полей в одном hash, чем создавать N отдельных ключей. Redis оптимизирует мелкие hash-ы: ziplist-кодирование экономит память.

Lists - двусторонняя очередь

LPUSH queue:tasks "task1" "task2"
RPOP queue:tasks            # достать с конца
BRPOP queue:tasks 5         # blocking - ждать до 5 секунд
LRANGE queue:tasks 0 -1

BLPOP/BRPOP блокирующие - основа простых message queue. Для надёжной доставки используйте Redis Streams.

Sets - уникальные элементы

SADD tags:post:1 "go" "backend" "tutorial"
SMEMBERS tags:post:1
SINTER tags:post:1 tags:post:2  # пересечение
SCARD tags:post:1               # размер

Полезно для уникальных счётчиков (UV), пересечений (общие интересы), членства (есть ли user в группе).

Sorted Sets - упорядоченные по score

ZADD leaderboard 100 "alice" 85 "bob" 95 "carol"
ZRANGE leaderboard 0 -1 WITHSCORES
ZRANK leaderboard "alice"        # место в рейтинге
ZRANGEBYSCORE leaderboard 90 100

Идеально для leaderboard, time-based индексов, sliding window rate limiter (в уроке 4).

TTL и expiry

SET key value EX 60          # 60 секунд
SET key value PX 5000        # 5000 миллисекунд
EXPIRE key 30                # установить TTL после SET
PERSIST key                  # убрать TTL
TTL key                      # -1 = без TTL, -2 = ключ не существует

Redis удаляет ключи двумя способами: lazy (при попытке доступа) и active (фоновая задача каждые 100ms сканирует случайные 20 ключей). Поэтому ключ с истёкшим TTL может оставаться в памяти короткое время, не появляясь в GET.

Memory management

redis-cli CONFIG SET maxmemory 256mb
redis-cli CONFIG SET maxmemory-policy allkeys-lru

Политики при достижении лимита:

  • noeviction - новые SET возвращают ошибку (по умолчанию)
  • allkeys-lru - выкидывать наименее недавно использованный ключ (типично для кеша)
  • allkeys-lfu - наименее часто используемый (Redis 4+)
  • volatile-lru - только из ключей с TTL
  • allkeys-random - случайный

Для кеша обычно allkeys-lru или allkeys-lfu. Для очередей и сессий - noeviction, иначе можете потерять важные данные.

Eviction: при переполнении Redis выбирает жертву по LRU, LFU, TTL или возвращает ошибку

Pub/Sub

-- Подписчик
SUBSCRIBE notifications

-- Отправитель
PUBLISH notifications "new order #123"
Если подписчика нет - сообщение потеряно. Если подписчик медленный - Redis накапливает буфер и в конце концов отключает его. Для надёжной доставки используй Redis Streams (с consumer groups, ack-ом, retention) или RabbitMQ.

Persistence: RDB vs AOF

RDB снимок памяти периодически, AOF лог всех команд - на бою обычно включают оба

  • RDB - снимки всей памяти на диск через интервалы. Быстрый рестарт, но при крахе теряется до N минут данных.
  • AOF (Append Only File) - лог каждой write-команды. С appendfsync everysec теряется не больше 1 секунды. Файл больше RDB.
  • RDB + AOF - рекомендуется для продакшена: AOF для durability, RDB для быстрого рестарта.
# redis.conf
save 900 1           # RDB: каждые 15 мин если ≥1 изменение
save 300 10
appendonly yes       # включить AOF
appendfsync everysec # компромисс безопасность/скорость

Replication и Sentinel

Master принимает запись, асинхронно стримит лог репликам, чтение масштабируется по ним

Master-slave репликация: read-нагрузку можно распределить на реплики. Sentinel - кластер из 3+ узлов мониторинга, автоматический failover при падении master. Redis Cluster - шардирование на 16384 hash-слота, рост по горизонтали.

Команды диагностики

INFO memory          # used_memory, fragmentation, evicted_keys
INFO clients         # connected_clients, blocked_clients
INFO stats           # total_commands_processed, instantaneous_ops_per_sec
SLOWLOG GET 10       # последние 10 медленных команд
CLIENT LIST          # активные коннекты
DBSIZE               # кол-во ключей в текущей БД
MEMORY USAGE key     # размер значения в байтах

SLOWLOG особенно полезен: в проде он быстро покажет, какие команды блокируют event loop. Порог настраивается через slowlog-log-slower-than (по умолчанию 10000 микросекунд = 10ms).

Типичные ошибки

  • KEYS * в проде - линейный обход всей БД, блокирует single-threaded event loop на сотни мс. На большой базе кладёт сервис целиком. Для итерации - только SCAN с курсором.
  • FLUSHALL/FLUSHDB под рукой у приложения - одной командой стираем все данные. В проде запретить через rename-command FLUSHALL "" в redis.conf. То же для DEBUG, CONFIG, SHUTDOWN.
  • Хранение без TTL - Redis превращается в «вечный кеш», память переполняется. У каждого ключа с кеш-семантикой должен быть EX/PEX или EXPIRE сразу после SET. Запомнить ставить EX в момент записи проще, чем расчищать потом.
  • maxmemory-policy noeviction без алёрта на OOM - при заполнении памяти все SET/HSET начнут возвращать ошибку. Если кеш - выбирай allkeys-lru/allkeys-lfu; если важные данные - volatile-lru + мониторинг used_memory.
  • AOF без appendfsync everysec - appendfsync always режет throughput на порядок (fsync на каждый write), а no теряет до 30 секунд при крахе. Стандарт прода - everysec.
  • Большие значения (>100KB) - гигантский HASH или JSON-блоб в одном ключе блокирует event loop при чтении/записи. Дробить на части или хранить как Stream/List с пагинацией.
  • Pub/Sub как очередь сообщений - у Pub/Sub нет персистентности и at-least-once. Подписчик был офлайн → сообщения потеряны. Для очередей - Streams или внешний брокер (RabbitMQ/Kafka).

Мини-практика

Запусти Redis в Docker. Создай хеш пользователя через HSET, добавь его в sorted set leaderboard со score, установи TTL для сессии через SET key val EX 1800. Проверь через TTL и EXISTS. Поэкспериментируй с CONFIG SET maxmemory-policy allkeys-lru и пронаблюдай за вытеснением при заполнении памяти большим количеством случайных ключей.

Зарегистрируйтесь бесплатно, чтобы пройти квиз, решить задание с автопроверкой и вести прогресс.