Redis: полная рабочая карта

Redis часто объясняют фразой «быстрая key-value база в памяти». Формально это верно, но практически почти бесполезно. Из такого определения непонятно, почему там есть списки, множества, очереди, блокировки, consumer groups, репликация и кластер.

Нормальная модель такая:

Redis является отдельным сервером структур данных. Клиент посылает ему атомарные команды, а Redis хранит типизированные значения преимущественно в оперативной памяти.

По состоянию на 11 июля 2026 года актуальная ветка Redis Open Source имеет версию 8.8. В Redis 8 встроены JSON, Search, Time Series, вероятностные структуры и векторный поиск, которые раньше ассоциировались с отдельным Redis Stack. Однако для понимания Redis сначала нужно освоить классическое ядро: ключи, TTL, строки, hashes, lists, sets, sorted sets, streams, память, persistence, replication и cluster. ([hub.docker.com][1])


1. Что Redis представляет собой физически

У тебя работает процесс:

redis-server

Приложение подключается к нему по TCP, обычно на порт 6379, и посылает команды:

SET user:42:name Igor
GET user:42:name
INCR article:15:views
HSET user:42 name Igor age 38

Внутри Redis существует пространство ключей:

key -> value

Но value не обязательно является строкой.

user:42:name       -> String
user:42            -> Hash
online-users       -> Set
leaderboard        -> Sorted Set
payments           -> Stream
notifications      -> List

Ключ всегда является строковым или бинарным идентификатором. Значение имеет конкретный Redis-тип. Команда должна соответствовать типу значения.

Например:

SET user:42 Igor
HSET user:42 name Igor

Вторая команда завершится ошибкой WRONGTYPE, потому что user:42 уже содержит String, а не Hash.

Redis не является объектной базой, где внутри одного ключа произвольно лежит что угодно. У каждого ключа есть один определенный тип.


2. Почему Redis быстрый

Главные причины:

  1. Рабочий набор находится в оперативной памяти.
  2. Redis выполняет операции непосредственно над своими структурами данных.
  3. Большинство основных команд имеют сложность O(1) или O(log N).
  4. Команды в основном выполняются последовательно одним главным потоком, поэтому внутри обычной команды не нужны тяжелые блокировки между потоками.
  5. Сервер использует неблокирующую обработку сетевых соединений.
  6. Несколько команд можно отправить pipeline-пакетом, сократив количество сетевых круговых поездок.

Современный Redis использует дополнительные потоки для фоновых операций и части ввода-вывода, однако с точки зрения исполнения большинства команд он остается преимущественно однопоточным. Поэтому две команды не изменяют один ключ одновременно: сервер последовательно исполняет сначала одну, затем другую. Но из этого же следует обратная сторона: одна тяжелая команда способна задержать всех остальных клиентов. ([Redis][2])

Схема:

Client A: GET user:1
Client B: INCR counter
Client C: HSET user:2 name Alex

                   ┌─────────────────┐
                   │ Redis main loop │
                   └────────┬────────┘
                            │
                      GET user:1
                            │
                      INCR counter
                            │
                 HSET user:2 name Alex

Поэтому фраза «Redis быстрый» не означает, что любая команда над любым объемом данных безопасна.

GET small-key

обычно дешевая операция.

А вот:

KEYS *
SMEMBERS enormous-set
HGETALL enormous-hash
LRANGE enormous-list 0 -1
SUNION huge-set-1 huge-set-2

могут надолго занять главный поток.

Redis представляет собой скоростную кассу с одним очень быстрым кассиром. Если один посетитель привезет к кассе товарный поезд, вся очередь встанет.


3. Что Redis умеет решать

Redis применяют не для одной задачи, а для нескольких классов задач:

Задача Что используется
Кэширование String, Hash, TTL, eviction
Сессии String или Hash с TTL
Счетчики INCR, INCRBY
Rate limiting счетчики, Sorted Set, Lua, INCREX в Redis 8.8
Очереди List или Stream
Event log Stream
Pub/Sub-уведомления Pub/Sub
Таблица лидеров Sorted Set
Уникальные элементы Set
Онлайн-пользователи Set или Bitmap
Распределенная блокировка SET NX PX и безопасное освобождение
Геопоиск Geospatial commands
Приблизительный подсчет уникальных значений HyperLogLog
JSON-документы Redis JSON
Временные ряды Redis Time Series
Полнотекстовый и векторный поиск Redis Search, Vector Sets

Главная ошибка состоит в том, чтобы увидеть в Redis просто «очень быстрый HashMap». На самом деле сила Redis находится в серверных атомарных операциях над структурами данных.

Например, можно забрать значение, изменить его в Java и записать обратно:

int value = Integer.parseInt(redis.get("counter"));
redis.set("counter", Integer.toString(value + 1));

Но между GET и SET другой процесс тоже может прочитать старое значение. Обновления затрут друг друга.

Правильно:

INCR counter

Redis атомарно увеличит счетчик без клиентской гонки.


4. Запускаем Redis в Docker

Для локального изучения:

services:
  redis:
    image: redis:8.8
    container_name: redis
    restart: unless-stopped

    ports:
      - "127.0.0.1:6379:6379"

    volumes:
      - redis-data:/data

    command:
      - redis-server
      - --appendonly
      - "yes"
      - --appendfsync
      - everysec
      - --save
      - "60"
      - "1000"

volumes:
  redis-data:

Запуск:

docker compose up -d

Подключение:

docker exec -it redis redis-cli

Проверка:

PING

Ответ:

PONG

Порт опубликован только на 127.0.0.1, поэтому Redis доступен с этой машины, но не выставлен наружу. Это существенно: официальный Docker-образ предупреждает, что при публикации Redis-порта без пароля сервер может оказаться открыт для внешнего подключения. ([Redis][3])


5. Ключи

5.1. Именование

Redis не задает схему ключей. Ее создает приложение.

Обычно используют формат:

<domain>:<entity>:<id>:<attribute>

Примеры:

user:42
user:42:session
article:15:views
payment:781
payment:781:lock
cache:product:558
rate-limit:user:42:login

Двоеточие не обладает специальной семантикой. Это соглашение для человека, клиента и инструментов наблюдения.

Хороший ключ:

order:781:status

Плохие варианты:

status
object
data
x

Ключ должен сообщать:

  1. Какому домену он принадлежит.
  2. Какую сущность представляет.
  3. Какой идентификатор используется.
  4. Какова цель хранения.

5.2. Основные команды ключей

EXISTS user:42
TYPE user:42
DEL user:42
UNLINK user:42
RENAME old-key new-key
EXPIRE user:42 60
TTL user:42
PERSIST user:42

DEL удаляет ключ и освобождает его память синхронно. Для очень большого объекта это может задержать главный поток.

UNLINK немедленно отсоединяет ключ от keyspace, а фактическое освобождение памяти выполняет в другом потоке. Для больших ключей UNLINK безопаснее с точки зрения задержек. ([Redis][4])


6. TTL: время жизни ключа

TTL является одной из центральных возможностей Redis.

SET session:abc user-42 EX 3600

Ключ автоматически исчезнет приблизительно через час.

То же самое отдельной командой:

SET session:abc user-42
EXPIRE session:abc 3600

Проверка:

TTL session:abc

Возможные результаты:

3572    ключ существует и истечет через 3572 секунды
-1      ключ существует, но TTL не установлен
-2      ключ не существует

Миллисекунды:

PEXPIRE session:abc 5000
PTTL session:abc

Абсолютное время:

EXPIREAT key 1780000000
PEXPIREAT key 1780000000000

Удаление TTL:

PERSIST session:abc

Атомарная запись с TTL предпочтительнее последовательности SET плюс EXPIRE:

SET verification:code:42 918224 EX 300 NX

Здесь одновременно происходят:

  1. Запись значения.
  2. Установка срока жизни.
  3. Проверка, что ключ еще не существует.

SET поддерживает EX, PX, NX, XX, GET, KEEPTTL и другие условия. Обычная перезапись ключа может убрать старый TTL, а KEEPTTL позволяет его сохранить. ([Redis][5])


7. Основные структуры данных

7.1. String

String хранит последовательность байтов. Это может быть текст, число, JSON, бинарный объект, изображение или сериализованная структура.

SET user:42:name Igor
GET user:42:name

Установка только при отсутствии:

SET user:42:name Igor NX

Установка только при наличии:

SET user:42:name Igor XX

С TTL:

SET cache:article:15 "{...json...}" EX 300

Счетчики:

SET article:15:views 0
INCR article:15:views
INCRBY article:15:views 10
DECR article:15:views

INCR атомарен.

String подходит для:

  • кэшированного ответа;
  • токена;
  • сессии;
  • счетчика;
  • feature flag;
  • idempotency key;
  • сериализованного объекта.

Redis String является бинарно-безопасным значением и поддерживает не только GET/SET, но и счетчики, диапазоны байтов, битовые операции и условную запись. ([Redis][6])


7.2. Hash

Hash представляет объект как набор полей:

user:42
  name  -> Igor
  age   -> 38
  city  -> Moscow

Команды:

HSET user:42 name Igor age 38 city Moscow
HGET user:42 name
HMGET user:42 name city
HGETALL user:42
HINCRBY user:42 loginCount 1
HDEL user:42 city
HEXISTS user:42 name
HLEN user:42

Hash удобен, когда поля объекта нужно читать и обновлять отдельно.

HSET payment:781 status CREATED amount 5000 currency RUB
HSET payment:781 status COMPLETED

В String с JSON пришлось бы:

  1. Прочитать весь JSON.
  2. Десериализовать.
  3. Изменить поле.
  4. Сериализовать.
  5. Записать весь объект.

Но у Hash есть важное ограничение: обычный TTL привязан ко всему ключу user:42, а не независимо к каждому полю.

Hash не заменяет реляционную таблицу. У Redis нет внешних ключей, joins и привычных SQL-ограничений.


7.3. List

List является упорядоченной последовательностью строк.

LPUSH notifications message-1
LPUSH notifications message-2
RPUSH notifications message-3

LRANGE notifications 0 -1

Получение с удалением:

LPOP notifications
RPOP notifications

Блокирующее ожидание:

BLPOP notifications 30

Клиент будет ждать до 30 секунд, пока элемент не появится.

Применения:

  • стек;
  • очередь;
  • deque;
  • ограниченная лента последних событий.

Например, хранение последних ста действий пользователя:

LPUSH user:42:activity event-json
LTRIM user:42:activity 0 99

Но List не является полноценным надежным брокером сообщений. После LPOP элемент исчезает. Если worker забрал сообщение и умер до обработки, сообщение потеряно, если ты отдельно не построил механизм подтверждений и восстановления.

Для надежной очереди обычно лучше Redis Streams.


7.4. Set

Set является неупорядоченным множеством уникальных строк.

SADD online-users user-1 user-2 user-3
SADD online-users user-2

Повторный user-2 не создаст дубликат.

SISMEMBER online-users user-2
SMEMBERS online-users
SCARD online-users
SREM online-users user-2

Операции над множествами:

SINTER group:a group:b
SUNION group:a group:b
SDIFF group:a group:b

Применения:

  • уникальные теги;
  • роли пользователя;
  • участники группы;
  • онлайн-пользователи;
  • уже обработанные идентификаторы;
  • пересечение интересов.
SADD user:42:roles admin editor
SISMEMBER user:42:roles admin

Проверка принадлежности, добавление и удаление элементов выполняются эффективно, а Redis также поддерживает пересечение, объединение и разность множеств. ([Redis][7])


7.5. Sorted Set

Sorted Set содержит уникальные элементы, каждому из которых соответствует числовой score.

Igor   -> 1500
Alex   -> 1200
Maria  -> 1800

Команды:

ZADD leaderboard 1500 Igor
ZADD leaderboard 1200 Alex
ZADD leaderboard 1800 Maria

По убыванию:

ZREVRANGE leaderboard 0 -1 WITHSCORES

Позиция:

ZREVRANK leaderboard Igor

Изменение счета:

ZINCRBY leaderboard 100 Igor

Диапазон по score:

ZRANGE leaderboard 1000 1600 BYSCORE WITHSCORES

Применения:

  • leaderboard;
  • очередь приоритетов;
  • расписание задач;
  • индексация по времени;
  • sliding-window rate limiter;
  • ранжирование;
  • поиск ближайших событий.

Для запланированных заданий score может быть Unix timestamp:

ZADD scheduled-jobs 1783785600 job-781

Worker получает все наступившие задания:

ZRANGE scheduled-jobs -inf 1783785600 BYSCORE

Sorted Set сохраняет уникальность элементов и одновременно упорядочивает их по score, поэтому хорошо подходит для рейтингов, приоритетных очередей и диапазонных запросов. ([Redis][8])


7.6. Stream

Stream является журналом событий, похожим на append-only log.

Добавление:

XADD payments * paymentId 781 status CREATED amount 5000

Redis создаст идентификатор:

1783785600123-0

Чтение:

XRANGE payments - +

Ожидание новых сообщений:

XREAD BLOCK 10000 STREAMS payments $

Stream хранит сообщения, поэтому потребитель может читать историю повторно.

Consumer Group

Создание группы:

XGROUP CREATE payments payment-workers 0 MKSTREAM

Чтение из группы:

XREADGROUP GROUP payment-workers worker-1 \
  COUNT 10 BLOCK 5000 \
  STREAMS payments >

После обработки:

XACK payments payment-workers 1783785600123-0

Сообщение, полученное группой, но еще не подтвержденное, находится в Pending Entries List.

Проверка:

XPENDING payments payment-workers

Это принципиально отличается от Pub/Sub:

Pub/Sub:
сообщение пришло прямо сейчас
подписчик отключен -> сообщение потеряно

Stream:
сообщение записано в журнал
consumer может прочитать его позже
группа отслеживает pending и acknowledgements

Redis Streams поддерживает журнал, consumer groups, повторное чтение и подтверждение обработки. Pub/Sub использует семантику at-most-once: если подписчик не получил сообщение из-за ошибки или отключения, Redis его не восстановит. ([Redis][9])

Stream против Kafka

Stream и Kafka похожи формой, но не тождественны.

Redis Stream:

  • проще поднять;
  • удобно использовать рядом с кэшем и transient state;
  • хорошо подходит для умеренных потоков и локальной событийной обработки;
  • сообщения хранятся внутри Redis memory model;
  • retention нужно контролировать.

Kafka:

  • изначально построена как распределенный долговременный commit log;
  • рассчитана на большие объемы, партиции и длительное хранение;
  • имеет другую модель масштабирования и дискового хранения.

Stream не нужно автоматически называть «маленькой Kafka». Это отдельная структура с частично пересекающимися сценариями.


7.7. Bitmap

Bitmap фактически хранится внутри String, но Redis позволяет работать с отдельными битами.

SETBIT active:2026-07-11 42 1
GETBIT active:2026-07-11 42
BITCOUNT active:2026-07-11

Если каждому пользователю соответствует числовой ID, один бит обозначает активность пользователя.

Применения:

  • дневная активность;
  • feature flags;
  • посещаемость;
  • булевы признаки для миллионов объектов.

Bitmap особенно эффективен, когда множество идентификаторов плотное и каждому объекту соответствует один бит. ([Redis][10])


7.8. HyperLogLog

HyperLogLog приблизительно подсчитывает количество уникальных значений, используя мало памяти.

PFADD visitors user-1 user-2 user-3
PFADD visitors user-2
PFCOUNT visitors

Результат будет оценкой количества уникальных пользователей.

Это нужно, когда точное множество хранить слишком дорого, а небольшая статистическая погрешность допустима:

  • уникальные посетители;
  • уникальные IP;
  • количество различных поисковых запросов;
  • cardinality огромного потока.

7.9. Geospatial

GEOADD cities 37.6173 55.7558 Moscow
GEOADD cities 30.3351 59.9343 Saint-Petersburg

Поиск объектов в радиусе:

GEOSEARCH cities \
  FROMLONLAT 37.6173 55.7558 \
  BYRADIUS 700 km \
  WITHDIST

Применения:

  • ближайшие магазины;
  • курьеры;
  • точки выдачи;
  • географические объекты.

7.10. Redis 8: JSON, Search, Time Series, Vector Sets

В Redis 8 единая поставка включает дополнительные структуры:

  • JSON;
  • полнотекстовый и структурированный поиск;
  • Time Series;
  • Bloom Filter;
  • Cuckoo Filter;
  • Count-Min Sketch;
  • Top-K;
  • t-digest;
  • Vector Sets;
  • с Redis 8.8 также Array.

Это уже следующий слой. Он не отменяет классические структуры, а добавляет специализированные модели. ([Redis][7])


8. Атомарность

Одна Redis-команда выполняется как единая операция относительно других команд.

Например:

INCR counter

атомарен.

А последовательность:

GET counter
SET counter new-value

не атомарна как единое целое.

Между командами может вмешаться другой клиент:

Client A: GET counter -> 10
Client B: GET counter -> 10
Client A: SET counter 11
Client B: SET counter 11

Ожидали: 12
Получили: 11

9. Transactions: MULTI, EXEC, WATCH

9.1. MULTI/EXEC

MULTI
INCR account:1:operations
DECR account:1:balance
EXEC

После MULTI команды не исполняются сразу, а помещаются в очередь. EXEC запускает их последовательно без вклинивания команд другого клиента. ([Redis][11])

Но Redis-транзакция не является полной аналогией SQL-транзакции.

Главные свойства:

  • команды внутри EXEC исполняются последовательно;
  • другой клиент не вклинится между ними;
  • Redis не предоставляет привычный автоматический rollback результатов уже исполненных команд;
  • результат каждой команды возвращается отдельно;
  • проверка и вычисление на клиенте требуют WATCH или server-side script/function.

9.2. Optimistic locking через WATCH

WATCH account:1:balance
GET account:1:balance

Приложение вычисляет новое значение.

MULTI
SET account:1:balance 900
EXEC

Если после WATCH другой клиент изменил наблюдаемый ключ, EXEC не выполнит транзакцию. Клиент должен повторить операцию.

Это compare-and-set модель.


10. Lua scripts и server-side functions

Когда нужно атомарно:

  1. Прочитать несколько ключей.
  2. Проверить условие.
  3. Изменить данные.
  4. Вернуть результат.

Можно выполнить Lua-скрипт:

EVAL "
local current = redis.call('GET', KEYS[1])
if not current then
    return 0
end

if tonumber(current) <= 0 then
    return -1
end

redis.call('DECR', KEYS[1])
return 1
" 1 inventory:product:42

Скрипт исполняется на стороне Redis и не допускает вклинивания другой команды в середину логики.

Но длинный Lua-скрипт блокирует обработку других команд. Скрипт должен быть маленьким, ограниченным и вычислительно предсказуемым. Lua не является способом перенести внутрь Redis половину бизнес-сервиса.


11. Pipeline

Без pipeline:

client -> SET a 1 -> server
client <- OK      <- server

client -> SET b 2 -> server
client <- OK      <- server

client -> SET c 3 -> server
client <- OK      <- server

Три сетевых round trip.

С pipeline:

client -> SET a 1
          SET b 2
          SET c 3 -> server

client <- OK
          OK
          OK      <- server

Pipeline сокращает сетевые задержки, отправляя несколько команд без ожидания ответа после каждой. ([Redis][12])

Важно:

Pipeline не является транзакцией.

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

Pipeline = меньше сетевых поездок
MULTI/EXEC = изолированная последовательность команд
Lua = атомарная серверная логика

12. Кэширование

Самый распространенный паттерн называется cache-aside.

1. Приложение запрашивает cache:product:42.
2. Redis возвращает значение -> отдаем клиенту.
3. Redis не нашел ключ -> читаем PostgreSQL.
4. Записываем результат в Redis с TTL.
5. Отдаем клиенту.

Псевдокод:

Product product = redis.get("cache:product:" + id);

if (product != null) {
    return product;
}

product = database.findProduct(id);

redis.set(
    "cache:product:" + id,
    serialize(product),
    Duration.ofMinutes(10)
);

return product;

12.1. Инвалидация

После изменения продукта:

UPDATE product in PostgreSQL
DELETE cache:product:42

Следующее чтение восстановит кэш.

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

12.2. Cache stampede

Ключ истек. Одновременно пришло 1000 запросов.

1000 запросов -> Redis miss
1000 запросов -> PostgreSQL

Redis перестал защищать базу именно в момент нагрузки.

Защита:

  • локальный single-flight;
  • короткая блокировка на перестроение;
  • soft TTL;
  • background refresh;
  • случайный разброс TTL;
  • stale-while-revalidate.

12.3. Cache penetration

Клиенты постоянно спрашивают несуществующие объекты:

product:999999999

Каждый раз Redis miss, затем обращение к базе.

Решение: кэшировать факт отсутствия на небольшой срок.

SET cache:product:999999999 "__NOT_FOUND__" EX 30

12.4. Cache avalanche

Огромное количество ключей имеет одинаковый TTL и истекает одновременно.

Плохо:

TTL каждого ключа = ровно 3600 секунд

Лучше:

TTL = 3600 + random(0..600)

13. Ограничение памяти и eviction

Redis хранит данные в RAM, поэтому необходимо отвечать на вопрос:

Что произойдет, когда память закончится?

Конфигурация:

maxmemory 2gb
maxmemory-policy allkeys-lfu

Основные политики:

Политика Поведение
noeviction не удаляет ключи, новые записи получают ошибку
allkeys-lru вытесняет давно не использованные ключи
allkeys-lfu вытесняет редко используемые ключи
allkeys-random удаляет случайные ключи
volatile-lru LRU только среди ключей с TTL
volatile-lfu LFU только среди ключей с TTL
volatile-random случайный ключ среди ключей с TTL
volatile-ttl ключ с наименьшим оставшимся TTL

В Redis 8.8 также присутствуют allkeys-lrm и volatile-lrm, ориентированные на время последней модификации. Если ни у одного ключа нет TTL, политики volatile-* фактически ведут себя как noeviction. ([Redis][13])

Практическая логика:

Redis только кэш:
    allkeys-lfu или allkeys-lru

Redis содержит важное состояние:
    noeviction

Redis смешивает постоянные данные и кэш:
    архитектурно опасно

Лучше не смешивать в одном инстансе:

cache keys, которые можно удалить

и:

critical state, которое удалять нельзя

Иначе одна eviction policy должна одновременно обслуживать два несовместимых смысла.


14. Big keys

Big key означает не только огромную String.

Это может быть:

String размером 200 MB
Hash с миллионом полей
Set с миллионом участников
List с миллионом элементов
Stream без retention

Проблемы big keys:

  • тяжелое чтение;
  • тяжелое удаление;
  • задержка репликации;
  • сетевые всплески;
  • дорогая миграция между cluster nodes;
  • fork и copy-on-write pressure;
  • одна команда может блокировать остальных;
  • неравномерное распределение памяти по shards.

Поиск:

redis-cli --bigkeys

Информация:

MEMORY USAGE some-key
OBJECT ENCODING some-key

redis-cli --bigkeys постепенно сканирует keyspace и показывает крупнейшие ключи по типам. ([Redis][14])

Не следует использовать в production:

KEYS *

Для обхода keyspace:

SCAN 0 MATCH user:* COUNT 100

Продолжать нужно с курсором, который вернул Redis, пока курсор снова не станет 0.

SCAN работает инкрементально и не требует одним вызовом обходить всю базу, но во время изменяющегося keyspace может вернуть один элемент несколько раз. Клиент обязан допускать дубликаты. ([Redis][15])


15. Persistence: что переживет перезапуск

Redis работает в памяти, но может сохранять состояние на диск.

Есть четыре режима:

  1. Без persistence.
  2. RDB.
  3. AOF.
  4. RDB и AOF одновременно. ([Redis][16])

15.1. Без persistence

Redis restart -> данные исчезли

Это допустимо для полностью реконструируемого кэша.

15.2. RDB

Redis периодически создает snapshot всего dataset.

save 60 1000

Означает: создать snapshot, если за 60 секунд произошло как минимум 1000 изменений.

Плюсы:

  • компактный файл;
  • удобно делать резервные копии;
  • быстрое восстановление;
  • сравнительно низкая постоянная нагрузка на диск.

Минусы:

  • между snapshots можно потерять последние изменения;
  • snapshot использует fork;
  • большой dataset создает copy-on-write нагрузку;
  • возможны latency spikes.

15.3. AOF

Redis записывает изменяющие команды в append-only log:

SET user:1 Igor
INCR counter
HSET payment:781 status COMPLETED

При запуске Redis воспроизводит журнал.

appendonly yes
appendfsync everysec

Режимы:

Настройка Смысл
appendfsync always fsync после записей, безопаснее, но дорого
appendfsync everysec fsync примерно раз в секунду
appendfsync no момент фактической записи решает ОС

При everysec в аварии можно потерять примерно одну секунду последних записей. AOF со временем переписывается, чтобы журнал не рос бесконечно. ([Redis][16])

15.4. Persistence не является backup

Если приложение выполнило:

FLUSHALL

Redis честно запишет эту команду в AOF.

Если данные испорчены логически, репликация также распространит повреждение.

Поэтому нужны:

  • отдельные snapshots;
  • копии вне Redis-машины;
  • проверка восстановления;
  • retention;
  • наблюдение за состоянием AOF/RDB.

Реплика не заменяет backup. AOF не заменяет backup. RAID не заменяет backup. Три разных механизма защищают от разных разрушений.


16. Replication

Топология:

            writes
Client ─────────────► Primary
                        │
                        ├────────► Replica 1
                        └────────► Replica 2

Primary принимает записи и передает поток изменений replicas.

Репликация по умолчанию асинхронная:

Client -> Primary: SET x 1
Primary -> Client: OK
Primary -> Replica: SET x 1

OK может быть возвращен до того, как replica гарантированно применила изменение.

Следствие:

При аварийном failover возможно потерять уже подтвержденную запись.

Команда WAIT позволяет дождаться подтверждения от заданного количества replicas:

SET payment:781 COMPLETED
WAIT 1 1000

Но даже WAIT не превращает Redis в строго согласованную CP-систему. Он сокращает окно риска, но не создает транзакционный консенсус. ([Redis][17])

Реплики можно использовать для чтения, но появляется eventual consistency:

записал в primary
сразу прочитал из replica
увидел старое значение

Нужно заранее определить:

  • какие чтения допускают lag;
  • какие должны идти в primary;
  • что происходит после failover;
  • допустима ли потеря подтвержденной записи.

17. Sentinel

Sentinel решает high availability для схемы primary плюс replicas.

          ┌────────────┐
          │ Sentinel 1 │
          ├────────────┤
          │ Sentinel 2 │
          ├────────────┤
          │ Sentinel 3 │
          └──────┬─────┘
                 │ наблюдают
                 ▼
              Primary
              /     \
         Replica   Replica

Sentinel:

  1. Наблюдает за primary и replicas.
  2. Определяет недоступность primary.
  3. Договаривается с другими Sentinel.
  4. Выбирает replica.
  5. Повышает ее до primary.
  6. Сообщает клиентам новый адрес primary.

Один Sentinel бессмыслен как механизм отказоустойчивости, потому что сам становится single point of failure. Sentinel задуман как распределенная система из нескольких процессов, принимающих решение совместно. ([Redis][18])

Sentinel не шардирует данные.

Sentinel = high availability без sharding

18. Redis Cluster

Redis Cluster решает две задачи:

  1. Sharding.
  2. High availability.

Keyspace делится на 16 384 hash slots.

CRC16(key) mod 16384 -> slot

Пример:

Node A -> slots 0..5500
Node B -> slots 5501..11000
Node C -> slots 11001..16383

Клиент вычисляет slot ключа и обращается к нужной node. Cluster-aware client умеет обрабатывать перенаправления MOVED и ASK.

Redis Cluster автоматически распределяет keyspace между узлами и использует replicas для failover, но при крупных сбоях, например потере большинства primary nodes, кластер может стать недоступен. Redis Cluster также не гарантирует строгую согласованность и при некоторых отказах способен потерять подтвержденные записи. ([Redis][19])

18.1. Multi-key operations

Команда:

MGET user:1 user:2

в Cluster допустима только тогда, когда ключи находятся в одном slot.

Для этого используются hash tags:

user:{42}:profile
user:{42}:settings
user:{42}:sessions

При вычислении slot Redis использует содержимое {42}, поэтому все три ключа попадут в один slot.

MGET user:{42}:profile user:{42}:settings

будет возможен.

Но hash tags способны создать hot shard, если слишком много данных случайно привязать к одному значению.


19. Sentinel против Cluster

Свойство Один Redis Sentinel Cluster
Простота высокая средняя низкая
Репликация вручную да да
Автоматический failover нет да да
Sharding нет нет да
Несколько primary нет нет да
Cluster-aware client не нужен Sentinel-aware нужен
Multi-key ограничения нет нет есть

Выбор:

Небольшой кэш:
    один Redis

Нужна HA, dataset помещается на одну машину:
    primary + replicas + Sentinel

Нужно распределять dataset или write load:
    Redis Cluster

Не следует поднимать Cluster только потому, что слово звучит солиднее. У Cluster появляется целый лес: slots, redirections, resharding, replica placement, failover, split brain conditions, cross-slot restrictions и cluster-aware clients.


20. Pub/Sub

Publisher:

PUBLISH notifications "hello"

Subscriber:

SUBSCRIBE notifications

Все активные подписчики получат сообщение.

Но Redis не сохраняет сообщение для отключенных subscribers. Нет ack, replay и pending list.

Применения:

  • live notifications;
  • cache invalidation;
  • обновление UI;
  • сигнал «что-то изменилось»;
  • необязательные transient events.

Не подходит для:

  • платежей;
  • задач, которые обязательно должны быть выполнены;
  • событий, требующих replay;
  • обработки с подтверждением.

Для этого нужен Stream или отдельный брокер.


21. Распределенная блокировка

Базовое получение lock:

SET lock:payment:781 random-owner-token NX PX 10000

Здесь:

  • NX означает создать только при отсутствии;
  • PX 10000 задает TTL 10 секунд;
  • random-owner-token идентифицирует владельца.

Нельзя освобождать блокировку простым:

DEL lock:payment:781

Сценарий:

Worker A получил lock на 10 секунд.
Worker A завис.
TTL истек.
Worker B получил тот же lock.
Worker A ожил и выполнил DEL.
Worker A удалил lock Worker B.

Освобождение должно атомарно проверить owner token:

if redis.call("GET", KEYS[1]) == ARGV[1] then
    return redis.call("DEL", KEYS[1])
else
    return 0
end

Redis документирует как single-instance lock, так и Redlock для нескольких независимых узлов. Однако блокировка с TTL не превращает бизнес-операцию в математически невозможную для повторного исполнения. Процесс может зависнуть дольше TTL, сеть может задержать ответы, а операция во внешней системе может продолжиться после утраты lock. ([Redis][20])

Для критической целостности нужны дополнительные механизмы:

  • idempotency key;
  • unique constraint;
  • version column;
  • fencing token;
  • транзакция основной базы;
  • проверка состояния самой предметной сущности.

Redis-lock координирует попытки. Он не заменяет инвариант.


22. Rate limiting

Fixed window

INCR rate:user:42:2026-07-11T17:30
EXPIRE rate:user:42:2026-07-11T17:30 60

Разрешаем, пока значение не превысило лимит.

Проблема: INCR и EXPIRE должны выполняться атомарно. Иначе процесс может упасть между командами, оставив вечный счетчик.

Решения:

  • Lua;
  • transaction;
  • специализированная команда;
  • в Redis 8.8 команда INCREX.

Sliding window через Sorted Set

Score равен timestamp:

ZADD rate:user:42 1783785600123 request-id
ZREMRANGEBYSCORE rate:user:42 -inf 1783785540123
ZCARD rate:user:42
EXPIRE rate:user:42 60

Вся последовательность должна быть Lua-скриптом или server-side function, иначе несколько клиентов смогут нарушить границу.

Redis 8.8 добавил INCREX, объединяющий увеличение счетчика, ограничения и expiration в одной атомарной операции. ([Redis][21])


23. Производительность клиента

Очень часто bottleneck находится не внутри Redis, а между приложением и Redis.

23.1. Не создавать соединение на каждый запрос

Плохо:

HTTP request
  connect Redis
  authenticate
  execute GET
  disconnect

Нужно:

  • долгоживущие соединения;
  • connection pooling там, где он действительно нужен;
  • отдельные соединения для blocking commands;
  • отдельное соединение для Pub/Sub;
  • cluster-aware client для Redis Cluster.

23.2. Настроить timeouts

Нужны разные ограничения:

  • connect timeout;
  • command timeout;
  • socket timeout;
  • pool acquisition timeout;
  • retry policy.

Бесконечный timeout превращает Redis failure в зависание всего HTTP thread pool.

23.3. Retry не всегда безопасен

Сценарий:

Client -> INCR counter
Redis исполнил INCR
Ответ потерялся
Client повторил INCR

Итог: увеличение произошло дважды.

Повтор чтения обычно безопасен. Повтор изменения зависит от идемпотентности.

23.4. Pipelining

Для сотен независимых команд:

1000 x GET по одному -> 1000 round trips
pipeline 1000 GET    -> значительно меньше round trips

Но нельзя создавать безграничные pipelines. Redis и клиент должны удерживать команды и ответы в памяти. Обычно используются ограниченные batches.


24. Наблюдение

Минимальный набор:

INFO
INFO memory
INFO stats
INFO clients
INFO replication
INFO persistence
INFO commandstats

Что смотреть:

used_memory
used_memory_rss
mem_fragmentation_ratio
connected_clients
blocked_clients
evicted_keys
expired_keys
keyspace_hits
keyspace_misses
instantaneous_ops_per_sec
rejected_connections
master_repl_offset
replica lag
aof_last_write_status
rdb_last_bgsave_status

Slowlog

SLOWLOG GET 20
SLOWLOG LEN

Slowlog измеряет время исполнения команды внутри Redis и не включает сетевую передачу ответа клиенту. Поэтому высокая application latency при пустом slowlog может означать сетевую проблему, очередь соединений, паузы клиента или перегрузку инфраструктуры. ([Redis][22])

Latency monitor

CONFIG SET latency-monitor-threshold 100
LATENCY LATEST
LATENCY DOCTOR

Redis имеет встроенный latency monitor, который регистрирует различные классы задержек, включая медленные команды, fork и фоновые операции. ([Redis][23])

Проверка с CLI

redis-cli --latency
redis-cli --bigkeys
redis-cli --stat

25. Безопасность

Redis не следует публиковать прямо в интернет.

Минимум:

  1. Привязка к внутреннему интерфейсу.
  2. Firewall.
  3. ACL users.
  4. Пароли или сертификаты.
  5. TLS для недоверенной сети.
  6. Запрет опасных administrative commands обычным приложениям.
  7. Отдельные пользователи для разных сервисов.
  8. Не хранить secrets в открытом виде без осознанной модели угроз.

ACL позволяет ограничивать:

  • доступные команды;
  • доступные шаблоны ключей;
  • Pub/Sub channels;
  • пользователей и пароли. ([Redis][24])

Пример концептуально:

user payment-service:
    разрешено GET, SET, HGET, HSET
    разрешены ключи payment:*
    запрещены FLUSHALL, CONFIG, SHUTDOWN

TLS поддерживается Redis Open Source, но конкретный binary должен быть собран или поставлен с TLS support. ([Redis][25])


26. Команды, которые требуют особой осторожности

KEYS *
FLUSHALL
FLUSHDB
CONFIG SET
DEBUG
SHUTDOWN
SAVE
MONITOR
SMEMBERS huge-set
HGETALL huge-hash
LRANGE huge-list 0 -1
SORT huge-list
SUNION huge-set-a huge-set-b
EVAL long-running-script

MONITOR удобен для отладки, но под нагрузкой создает серьезный overhead и может раскрывать аргументы команд.

SAVE выполняет snapshot синхронно. Для production обычно используют BGSAVE.

FLUSHALL является настоящим огнеметом. Он делает ровно то, что написано на корпусе.


27. Типовые архитектурные ошибки

Ошибка 1. Redis без TTL как бесконечный склад

Каждый запрос создает новый ключ.
Ни один ключ не истекает.
Память медленно заполняется.
Через месяц Redis падает или начинает eviction.

Для каждого класса ключей должно быть известно:

Кто создает?
Кто удаляет?
Есть ли TTL?
Можно ли вытеснить?
Как восстановить?

Ошибка 2. Смешивание кэша и критических данных

cache:product:42
payment:idempotency:781
session:abc
business-counter

Все лежит в одном Redis с allkeys-lru.

При нехватке памяти Redis может удалить ключ, который приложение считало важным.

Ошибка 3. Использование Redis как базы «потому что быстро»

Сначала нужно определить:

  • нужны ли joins;
  • нужны ли constraints;
  • нужна ли строгая durability;
  • нужна ли история изменений;
  • нужна ли транзакция с другими сущностями;
  • допустима ли потеря последних записей;
  • что произойдет при failover;
  • как делается backup и restore.

Redis может быть primary database для подходящей модели, но это должно быть сознательным решением, а не результатом опьянения скоростью.

Ошибка 4. Неограниченный Stream

XADD events * ...

бесконечно.

Нужно retention:

XADD events MAXLEN ~ 100000 * field value

Иначе журнал растет вместе с памятью.

Ошибка 5. Один гигантский Hash

all-users -> Hash с десятками миллионов fields

Получается один hot key, который невозможно нормально шардировать по пользователям.

Лучше:

user:1
user:2
user:3

если модель операций не требует общей структуры.

Ошибка 6. Ожидание exactly-once

Redis Stream, lock или retry сами по себе не создают exactly-once business effect.

Сообщение может быть:

  • обработано, но не подтверждено;
  • доставлено повторно;
  • подтверждено после частичного выполнения;
  • повторно выдано после смерти consumer.

Consumer должен быть идемпотентным.


28. Что нужно знать на собеседовании

Нужно уметь без воды объяснить:

  1. Почему Redis быстрый.
  2. Что означает преимущественно однопоточное исполнение.
  3. Чем Redis отличается от Memcached.
  4. Какие структуры данных существуют.
  5. String против Hash.
  6. List против Stream.
  7. Pub/Sub против Stream.
  8. Что такое TTL.
  9. Что такое eviction.
  10. LRU против LFU.
  11. RDB против AOF.
  12. Почему replication не гарантирует отсутствие потери записей.
  13. Sentinel против Cluster.
  14. Что такое hash slots и hash tags.
  15. Почему KEYS * опасен.
  16. Что такое big key и hot key.
  17. Pipeline против transaction.
  18. MULTI/EXEC против Lua.
  19. Как устроен cache-aside.
  20. Что такое cache stampede.
  21. Как строится distributed lock.
  22. Почему простой DEL lock некорректен.
  23. Почему retries могут продублировать запись.
  24. Как контролировать память.
  25. Какие метрики смотреть.

29. Практическая последовательность изучения

Этап 1. Базовые ключи

SET name Igor
GET name
TYPE name
EXISTS name
DEL name

Этап 2. TTL

SET session:1 user-42 EX 10
TTL session:1
GET session:1

Подождать 10 секунд:

GET session:1

Этап 3. Структуры

HSET user:42 name Igor age 38
HGETALL user:42

SADD user:42:roles admin editor
SMEMBERS user:42:roles

ZADD leaderboard 100 Igor 120 Alex
ZREVRANGE leaderboard 0 -1 WITHSCORES

LPUSH jobs job-1 job-2
RPOP jobs

Этап 4. Атомарность

Открыть два терминала и одновременно выполнять:

INCR counter

Затем сравнить с клиентской схемой GET плюс SET.

Этап 5. Transaction

MULTI
INCR a
INCR b
EXEC

Затем изучить WATCH.

Этап 6. Stream

XGROUP CREATE payments workers 0 MKSTREAM
XADD payments * paymentId 1 status CREATED
XREADGROUP GROUP workers worker-1 STREAMS payments >
XPENDING payments workers
XACK payments workers <message-id>

Этап 7. Persistence

SET persistent:test hello

Перезапустить:

docker compose restart redis

Проверить:

GET persistent:test

Этап 8. Memory и eviction

В отдельном учебном контейнере:

CONFIG SET maxmemory 10mb
CONFIG SET maxmemory-policy allkeys-lru

Заполнять Redis и наблюдать:

INFO memory
INFO stats

Этап 9. Диагностика

INFO
SLOWLOG GET 10
MEMORY USAGE some-key
redis-cli --bigkeys
redis-cli --latency

30. Итоговая модель Redis

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

1. Data structures
   String, Hash, List, Set, Sorted Set, Stream

2. Time
   TTL, expiration, scheduled values, retention

3. Atomicity
   atomic commands, MULTI/EXEC, WATCH, Lua

4. Memory
   maxmemory, eviction, big keys, hot keys

5. Reliability
   RDB, AOF, replication, Sentinel, Cluster

Главная формула:

Redis силен не потому, что просто хранит данные в RAM. Он силен потому, что выполняет атомарные серверные операции над специализированными структурами данных.

И одновременно его главная опасность:

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

Прокрутить вверх