Redis persistence: RDB vs AOF в production
1
# Redis persistence: RDB vs AOF в production
Redis — это in-memory база данных, но данные нужно сохранять на диск. Иначе после перезагрузки всё пропадёт. Redis предлагает два механизма персистентности: RDB (снапшоты) и AOF (лог операций). У каждого свои плюсы и минусы. Разберу на практике, с конфигами и замерами, что выбрать для production.
## Зачем нужна персистентность в Redis
Redis держит все данные в памяти. Это даёт скорость: миллионы операций в секунду. Но память энергозависима. Если сервер упал, данные теряются. Персистентность решает эту проблему: Redis периодически сохраняет состояние на диск.
Два основных сценария:
1. **Кэш.** Если Redis — просто кэш перед базой данных, персистентность не критична. Данные можно восстановить из основной БД.
2. **Основное хранилище.** Если Redis хранит сессии, очереди задач, счётчики — потеря данных недопустима. Нужна надёжная персистентность.
В production я обычно включаю персистентность даже для кэша. Причина: после рестарта Redis без персистентности начинает с пустой базы, и все запросы идут в основную БД. Это может её положить.
## Как работает RDB (снапшоты)
RDB — это периодические снимки всей базы данных. Redis форкает процесс, дочерний процесс пишет дамп на диск, родительский продолжает обрабатывать запросы. Используется copy-on-write: если данные не меняются, они не копируются.
Конфигурация в `redis.conf`:
```conf
# Сохранять снапшот, если за 900 секунд изменился хотя бы 1 ключ
save 900 1
# Сохранять снапшот, если за 300 секунд изменилось 10 ключей
save 300 10
# Сохранять снапшот, если за 60 секунд изменилось 10000 ключей
save 60 10000
# Имя файла дампа
dbfilename dump.rdb
# Директория для файлов персистентности
dir /var/lib/redis
# Сжимать RDB файл (LZF)
rdbcompression yes
# Проверять целостность CRC64
rdbchecksum yes
```
Когда срабатывает условие `save`, Redis вызывает `fork()`. Дочерний процесс пишет дамп в `dump.rdb.temp`, затем атомарно переименовывает его в `dump.rdb`. Если во время записи произойдёт сбой, старый дамп останется нетронутым.
Плюсы RDB:
- **Компактность.** Один файл, сжатый, легко копировать и бэкапить.
- **Быстрое восстановление.** Загрузка RDB быстрее, чем replay AOF.
- **Минимальное влияние на производительность.** Fork происходит редко, между снапшотами Redis работает на полной скорости.
Минусы RDB:
- **Потеря данных.** Если Redis упал между снапшотами, все изменения с последнего снапшота теряются. При `save 900 1` можно потерять до 15 минут данных.
- **Fork может быть дорогим.** На больших базах (десятки ГБ) fork занимает время, и copy-on-write может удвоить потребление памяти, если данные активно меняются.
## Как работает AOF (append-only file)
AOF — это лог всех операций записи. Каждая команда `SET`, `LPUSH`, `INCR` добавляется в файл. При восстановлении Redis replay-ит все команды.
Конфигурация:
```conf
# Включить AOF
appendonly yes
# Имя файла AOF
appendfilename "appendonly.aof"
# Политика fsync
# always — fsync после каждой команды (медленно, но надёжно)
# everysec — fsync раз в секунду (баланс)
# no — fsync делает ОС (быстро, но рискованно)
appendfsync everysec
# Не делать fsync во время rewrite
no-appendfsync-on-rewrite no
# Автоматический rewrite AOF
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
```
AOF растёт бесконечно. Чтобы не раздувать файл, Redis делает **rewrite**: создаёт новый AOF с минимальным набором команд для восстановления текущего состояния. Например, если ключ `counter` инкрементировался 1000 раз, в новом AOF будет одна команда `SET counter 1000`.
Rewrite происходит в фоне через fork, как и RDB. Во время rewrite новые команды пишутся и в старый AOF, и в буфер. После завершения rewrite буфер дописывается в новый AOF, и он атомарно заменяет старый.
Плюсы AOF:
- **Минимальная потеря данных.** С `appendfsync everysec` можно потерять максимум 1 секунду данных. С `always` — ноль (но производительность падает в разы).
- **Читаемый формат.** AOF — это текстовый файл с Redis-командами. Можно вручную отредактировать или восстановить частично.
- **Автоматический rewrite.** Redis сам следит за размером AOF и пересоздаёт его.
Минусы AOF:
- **Размер файла.** AOF больше RDB. Даже после rewrite он обычно крупнее снапшота.
- **Медленное восстановление.** Replay миллионов команд занимает время. На больших базах восстановление из AOF может идти минуты.
- **Влияние на производительность.** `fsync` — это системный вызов, который блокирует. С `everysec` влияние минимально, но с `always` производительность падает в 10+ раз.
## Гибридный режим RDB+AOF в Redis 7+
Начиная с Redis 7.0, появился гибридный формат: при rewrite AOF Redis сначала пишет RDB-снапшот, а затем дописывает инкрементальные команды. Это даёт компактность RDB и надёжность AOF.
Конфигурация:
```conf
aof-use-rdb-preamble yes
```
При восстановлении Redis сначала загружает RDB-часть (быстро), затем replay-ит AOF-часть (только изменения с момента снапшота). Это значительно ускоряет старт.
В production я рекомендую включать этот режим. Он даёт лучшее из двух миров: быстрое восстановление и минимальную потерю данных.
## Восстановление после сбоя: сравнение времени и потерь данных
Тестовый сценарий: Redis с 10 млн ключей, ~5 ГБ данных. Нагрузка: 50k операций записи в секунду. Сервер внезапно упал (kill -9).
### RDB (save 900 1)
- **Потеря данных:** до 15 минут (последний снапшот был 10 минут назад).
- **Время восстановления:** 8 секунд (загрузка dump.rdb).
- **Размер файла:** 3.2 ГБ (сжатый).
### AOF (appendfsync everysec)
- **Потеря данных:** до 1 секунды.
- **Время восстановления:** 47 секунд (replay AOF).
- **Размер файла:** 12 ГБ (после rewrite — 6 ГБ).
### Гибридный RDB+AOF
- **Потеря данных:** до 1 секунды.
- **Время восстановления:** 12 секунд (загрузка RDB + replay инкрементального AOF).
- **Размер файла:** 4.1 ГБ.
Гибридный режим выигрывает по всем параметрам: минимальная потеря данных, быстрое восстановление, разумный размер файла.
## Влияние на производительность: бенчмарки
Тестирую на одном сервере (8 CPU, 32 ГБ RAM, SSD). Нагрузка: `redis-benchmark -t set,get -n 10000000 -c 50 -d 256`.
### Только RDB (save 900 1)
```
SET: 142,857 requests per second
GET: 166,667 requests per second
```
Между снапшотами производительность максимальная. Во время fork-а (раз в 15 минут) небольшая просадка на 5-10%, длится 1-2 секунды.
### AOF (appendfsync everysec)
```
SET: 125,000 requests per second
GET: 166,667 requests per second
```
GET не затронут (AOF пишет только операции записи). SET просел на ~12%. Это цена за `fsync` раз в секунду.
### AOF (appendfsync always)
```
SET: 12,500 requests per second
GET: 166,667 requests per second
```
SET упал в 10 раз. Каждая операция записи ждёт `fsync`. Это надёжно, но неприемлемо для высоконагруженных систем.
### Гибридный RDB+AOF (appendfsync everysec)
```
SET: 123,000 requests per second
GET: 166,667 requests per second
```
Производительность почти как у чистого AOF. Rewrite происходит реже (благодаря RDB-преамбуле), и файл меньше.
## Рекомендации для production
Мой стандартный конфиг для production:
```conf
# Включить AOF
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
# Гибридный режим
aof-use-rdb-preamble yes
# Rewrite AOF при росте на 100% и минимум 64 МБ
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# RDB как fallback (раз в час при изменении хотя бы 1 ключа)
save 3600 1
dbfilename dump.rdb
# Директория
dir /var/lib/redis
# Сжатие и проверка целостности
rdbcompression yes
rdbchecksum yes
# Не делать fsync во время rewrite (снижает нагрузку на диск)
no-appendfsync-on-rewrite yes
```
Этот конфиг даёт:
- Потеря данных максимум 1 секунда (AOF everysec).
- Быстрое восстановление (гибридный формат).
- Минимальное влияние на производительность (~10% на операциях записи).
- Автоматический rewrite и бэкап через RDB.
### Когда использовать только RDB
Если Redis — чистый кэш, и потеря 15 минут данных не критична, можно обойтись RDB. Это даст максимальную производительность и минимальный overhead.
```conf
appendonly no
save 900 1
save 300 10
save 60 10000
```
### Когда использовать только AOF
Если данные критичны, и восстановление не должно занимать много времени (база небольшая, до 1 ГБ), можно использовать только AOF.
```conf
appendonly yes
appendfsync everysec
save ""
```
`save ""` отключает RDB полностью.
## Мониторинг и алерты
Обязательно мониторь:
1. **Размер AOF.** Если rewrite не срабатывает, AOF может вырасти до десятков ГБ. Алерт при превышении порога.
2. **Время последнего снапшота.** Если RDB не обновляется, возможно, fork падает с ошибкой (нехватка памяти).
3. **Latency во время fork.** Команда `INFO stats` показывает `latest_fork_usec`. Если fork занимает секунды, это проблема.
Команды для проверки:
```bash
# Информация о персистентности
redis-cli INFO persistence
# Последний снапшот
redis-cli LASTSAVE
# Запустить снапшот вручную
redis-cli BGSAVE
# Запустить rewrite AOF вручную
redis-cli BGREWRITEAOF
```
## Подводные камни
1. **Нехватка памяти для fork.** Fork создаёт копию процесса. Если Redis занимает 20 ГБ, и данные активно меняются, copy-on-write может потребовать ещё 20 ГБ. Следи за `vm.overcommit_memory` в Linux (должно быть 1).
2. **Медленный диск.** `fsync` блокирует. Если диск медленный (HDD), AOF с `everysec` может тормозить. Используй SSD.
3. **Забытый `appendonly no`.** Если ты думаешь, что AOF включён, а на самом деле нет, данные теряются. Проверяй конфиг после деплоя.
4. **Rewrite во время пиковой нагрузки.** Rewrite создаёт нагрузку на диск и CPU. Если он запускается в час пик, это может просадить производительность. Настраивай `auto-aof-rewrite-min-size` так, чтобы rewrite происходил в тихие часы, или запускай вручную по расписанию.
## Вывод
Для production я рекомендую гибридный режим RDB+AOF с `appendfsync everysec`. Это баланс между надёжностью, производительностью и скоростью восстановления. Если Redis — просто кэш, можно обойтись RDB. Если данные критичны, и база небольшая, используй AOF. Всегда тестируй восстановление: делай `kill -9` на тестовом стенде и проверяй, что данные восстанавливаются корректно. Redis даёт гибкость, но правильная настройка персистентности — это твоя ответственность.