Learning Platform
Глоссарий Troubleshooting
Урок 14.03 · 24 мин
Начальный
dockerredisdata-engineering

Redis в контейнере

Redis — это in-memory key-value хранилище, написанное на C, single-threaded для команд, безумно быстрое (миллион ops/sec на одном ядре). Внешне выглядит как HashMap, доступный по сети, с поддержкой строк, списков, sets, hashes, sorted sets, streams. Внутри — событийный цикл на epoll/kqueue, всё в RAM, периодическая сериализация на диск.

Для Junior DE Redis встречается в трёх ролях: брокер очереди Celery (Airflow, Prefect используют Celery executor), кэш для метаданных pipeline (Schema registry, feature store), rate limiter (когда пишем DAG, которые дёргают внешний API с лимитами).


Базовый запуск

Официальный образ — redis. Стабильные теги: redis:7, redis:7.4, redis:7-alpine. Между debian и alpine разница в размере: redis:7 около 130 МБ, redis:7-alpine около 50 МБ. Для Redis alpine — отличный выбор, никаких extensions с native-кодом тут нет, проблем не будет.

Минимальный запуск:

docker run -d \
  --name redis \
  -p 6379:6379 \
  redis:7-alpine

И всё. Redis уже работает, слушает порт 6379, никакого пароля. Проверим через redis-cli внутри контейнера:

docker exec -it redis redis-cli

127.0.0.1:6379> PING
PONG

127.0.0.1:6379> SET hello "world"
OK

127.0.0.1:6379> GET hello
"world"

127.0.0.1:6379> exit

С хоста — тоже работает, если установлен redis-cli (brew install redis на macOS):

redis-cli -h localhost -p 6379 PING
# PONG
WARNING

В дефолтной конфигурации Redis запускается без пароля и слушает все интерфейсы внутри контейнера. Если ты сделаешь -p 6379:6379 на сервере с публичным IP — это открытый Redis в интернет. До 2017 года в интернете были тысячи открытых Redis-серверов с данными ботнетов и криптомайнерами в redis-cli config set dir /var/spool/cron && config set dbfilename root && save. Сейчас Redis 7+ добавил protected mode по умолчанию (отказывается работать с внешних адресов без пароля), но не полагайся на это — всегда ставь пароль.


Пароль через requirepass

Самый простой способ — передать команду:

docker run -d \
  --name redis \
  -p 6379:6379 \
  redis:7-alpine \
  redis-server --requirepass "verystrongpassword"

Команда после имени образа заменяет CMD из Dockerfile. По умолчанию там redis-server, мы передаём redis-server --requirepass "verystrongpassword". Теперь:

docker exec -it redis redis-cli

127.0.0.1:6379> PING
(error) NOAUTH Authentication required.

127.0.0.1:6379> AUTH verystrongpassword
OK

127.0.0.1:6379> PING
PONG

Или сразу с паролем:

docker exec -it redis redis-cli -a verystrongpassword

127.0.0.1:6379> PING
PONG

В URI пароль кодируется так: redis://:verystrongpassword@localhost:6379/0 (нет username, только пароль, поэтому двоеточие перед паролем).


AOF vs RDB persistence

По умолчанию Redis 7 пишет на диск в режиме RDB — snapshot всей базы в файл /data/dump.rdb раз в N секунд (если изменилось M ключей). Это компромисс: быстро, маленький файл, но при креше теряешь всё, что было между snapshot’ами.

AOF (Append Only File) — каждая write-команда дописывается в файл /data/appendonly.aof. При рестарте Redis проигрывает AOF и восстанавливает состояние. Минусы: больше места на диске (10-20x относительно RDB), медленнее запись (хотя fsync можно настроить). Плюсы: при креше теряешь максимум 1 секунду данных (или 0, если fsync=always, но это медленно).

RDB vs AOF persistence
RDB snapshotРаз в N секунд Redis форкает процесс, дочерний пишет dump.rdb с полным состоянием. CoW защищает от блокировки родителя
vs
AOF appendКаждая write-команда (SET, DEL, INCR) дописывается в appendonly.aof. При рестарте Redis проигрывает AOF и восстанавливает состояние
RDB: 1MBRDB-файл компактный, потому что хранит финальное состояние, не историю изменений
AOF: 50MBAOF растёт пропорционально количеству write-операций. Периодически rewrite-проход компактирует его
RDB: при креше -60s данныхЕсли последний snapshot был минуту назад, и Redis упал — потеряли всё что между
AOF: при креше -1sС fsync=everysec теряешь максимум секунду. С fsync=always — 0, но в 10 раз медленнее
RDB: быстрый restartВосстановление = mmap + load бинарного формата. Несколько секунд для гигабайтной базы
AOF: медленный restartВосстановление = проигрывание всех команд из лога. Может занять минуты на больших объёмах

Для большинства DE-задач (кэш, очередь Celery) данные не критичны — можно дефолтный RDB. Если Redis — единственное хранилище (например, rate limiter в проде, и потеря данных = race condition), включай AOF.

Включение AOF:

docker run -d \
  --name redis \
  -p 6379:6379 \
  -v redis-data:/data \
  redis:7-alpine \
  redis-server --requirepass "pass" --appendonly yes

Volume redis-data:/data нужен независимо от RDB/AOF — без него persistence бессмысленна, файлы умрут с контейнером.


Что внутри /data

Заглянем в data dir:

docker exec -it redis ls -la /data

Без AOF:

total 12
drwxr-xr-x 2 redis  redis  4096 May 15 09:42 .
drwxr-xr-x 1 root   root   4096 May 15 09:40 ..
-rw-r--r-- 1 redis  redis    93 May 15 09:42 dump.rdb

С AOF (после --appendonly yes):

total 16
drwxr-xr-x 3 redis  redis  4096 May 15 09:45 .
drwxr-xr-x 1 root   root   4096 May 15 09:40 ..
-rw-r--r-- 1 redis  redis    93 May 15 09:42 dump.rdb
drwxr-xr-x 2 redis  redis  4096 May 15 09:45 appendonlydir

В Redis 7 AOF переехал в директорию appendonlydir/ (multi-part AOF): там лежат appendonly.aof.1.base.rdb, appendonly.aof.1.incr.aof, appendonly.aof.manifest. Раньше был один файл — теперь base + incr, чтобы упростить rewrite.


Типичные use cases для DE

1. Rate limiter

DAG’и часто дёргают внешние API с rate limits (1000 запросов в минуту на ключ). Redis-based rate limiter — стандарт.

import redis
import time

r = redis.Redis(host="localhost", port=6379, password="pass", decode_responses=True)

def allow_request(api_key: str, limit: int = 1000, window: int = 60) -> bool:
    key = f"ratelimit:{api_key}:{int(time.time()) // window}"
    count = r.incr(key)
    if count == 1:
        r.expire(key, window)
    return count <= limit

Каждый вызов делает атомарный INCR, который возвращает новое значение. Если первый раз — ставим TTL. Если превысили — отказываем. Простота на 5 строк, скорость 200k проверок/сек на одном ядре.

2. Кэш метаданных

Airflow DAG, который для каждого row дёргает Schema Registry — медленно. С Redis-кэшем:

def get_schema(table: str) -> dict:
    cached = r.get(f"schema:{table}")
    if cached:
        return json.loads(cached)
    schema = fetch_from_registry(table)
    r.setex(f"schema:{table}", 300, json.dumps(schema))  # 5 минут TTL
    return schema

3. Брокер Celery

Airflow с CeleryExecutor использует Redis или RabbitMQ как брокер очереди. Конфиг:

broker_url = "redis://:pass@redis:6379/0"
result_backend = "redis://:pass@redis:6379/1"

DB 0 для очереди, DB 1 для результатов. Redis даёт 16 виртуальных DB по умолчанию — это просто префиксы ключей, изоляции на уровне памяти нет.


Compose-пример с Redis

services:
  redis:
    image: redis:7-alpine
    command: redis-server --requirepass "${REDIS_PASSWORD}" --appendonly yes
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "$$REDIS_PASSWORD", "PING"]
      interval: 5s
      timeout: 3s
      retries: 5

  app:
    image: my-etl
    depends_on:
      redis:
        condition: service_healthy
    environment:
      REDIS_URL: "redis://:${REDIS_PASSWORD}@redis:6379/0"

volumes:
  redis-data:

Обрати внимание на $$REDIS_PASSWORD в healthcheck — двойной знак доллара экранирует переменную, чтобы compose не подставлял её на этапе парсинга, а оставил в команде для shell внутри контейнера.


Память и eviction policy

Что происходит, когда процесс исчерпывает RAM

Redis — in-memory, и это значит, что когда RAM кончится — он либо упадёт, либо начнёт удалять ключи. По умолчанию maxmemory не задан — Redis ест столько, сколько есть. Для кэша всегда задавай лимит и eviction policy:

docker run -d \
  --name redis \
  -p 6379:6379 \
  redis:7-alpine \
  redis-server \
    --maxmemory 512mb \
    --maxmemory-policy allkeys-lru

allkeys-lru = когда дойдёт до 512 МБ, удаляй наименее недавно использованный ключ. Альтернативы:

  • noeviction — отказывать в write-командах (дефолт). Для кэша не подходит.
  • allkeys-lru — LRU на всех ключах. Стандарт для кэша.
  • volatile-lru — LRU только на ключах с TTL. Для смешанных use cases (кэш + persistent).
  • allkeys-lfu — LFU (Least Frequently Used). Для очень неравномерных нагрузок.
Redis в DE-пайплайне (типичные роли)
Rate limiterINCR с TTL — счётчик запросов в окно. 200k проверок/сек на одном ядре
Schema cacheSETEX с TTL 5 минут — кэш ответов от Schema Registry, чтобы не дёргать его на каждый row
Celery brokerLPUSH/BRPOP — очередь задач между Airflow scheduler и workers
Distributed lockSET key value NX EX 60 — distributed lock с TTL. Защита от race condition в параллельных DAG runs

Попробуй сам

  1. Запусти Redis с паролем, AOF и лимитом памяти:
    docker run -d --name redis \
      -p 6379:6379 \
      -v lab-redis-data:/data \
      redis:7-alpine \
      redis-server --requirepass labpass --appendonly yes --maxmemory 64mb --maxmemory-policy allkeys-lru
  2. Подключись через docker exec -it redis redis-cli -a labpass. Положи 100 ключей: MSET k1 v1 k2 v2 ... (или цикл из bash).
  3. Перезапусти контейнер (docker restart redis), проверь, что ключи на месте — это работает потому что AOF восстанавливает состояние из /data/appendonlydir/.
  4. Сделай INFO memory, посмотри used_memory_human и maxmemory_human.
  5. Из Python (pip install redis) подключись и сделай 10000 SET-операций в цикле. Замерь время. Должно быть около 0.5 секунды (50k ops/sec в неоптимизированном клиенте).

Проверка знанийKnowledge check
Что такое AOF в Redis, чем он отличается от RDB, и в каком случае DE стоит включать AOF на проде?
ОтветAnswer
RDB (default) — это snapshot всей базы Redis в файл dump.rdb, делается периодически (раз в N секунд, если изменилось M ключей). Компактный файл, быстрый restart, но при креше теряешь все данные между snapshot'ами (десятки секунд). AOF (Append Only File) — каждая write-команда дописывается в appendonly.aof (в Redis 7 это директория appendonlydir/ с multi-part файлами). При рестарте Redis проигрывает лог и восстанавливает состояние. Плюсы: при креше теряешь максимум 1 секунду (fsync=everysec) или 0 (fsync=always). Минусы: файл в 10-20 раз больше, чем RDB, медленнее запись, дольше restart на больших объёмах. DE стоит включать AOF (--appendonly yes), когда Redis — единственное хранилище важных данных: production rate limiter, distributed lock, persistent очередь. Для кэша и брокера Celery достаточно RDB — потеря данных не критична, их можно перевычислить или переотправить.

Проверьте понимание

Результат: 0 из 0
Аналитический
Вопрос 1 из 4. Чем фундаментально отличаются RDB и AOF persistence в Redis 7, и какой режим лучше для production rate limiter, где потеря данных = race condition?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5