Eviction
Предпосылки: Внутренние кодировки, LRU (least recently used), LFU (least frequently used).
← Внутренние кодировки | Проектирование ключей →
Проблема: память конечна
Redis хранит всё в RAM. Приложение создаёт ключи, ключи копятся, потребление растёт. Без ограничений Redis растёт, пока операционная система не убьёт процесс (OOM Killer) — и тогда теряются все данные. Чтобы этого не произошло, директива maxmemory устанавливает верхнюю границу потребления (например, maxmemory 4gb). Когда потребление достигает лимита, Redis должен решить: какие ключи удалить, чтобы освободить место для новых записей? Этот выбор определяет политика вытеснения (maxmemory-policy). Вытеснение происходит синхронно перед каждой write-командой: Redis проверяет потребление памяти, и если оно превышает maxmemory, удаляет ключи согласно политике до тех пор, пока не освободит достаточно места (или не вернёт ошибку при noeviction).
Восемь политик вытеснения
Параметр maxmemory-policy определяет, какие ключи удалять при нехватке памяти. Redis предоставляет восемь вариантов.
noeviction (по умолчанию) — не удалять ничего. При попытке записи Redis возвращает ошибку OOM command not allowed when used memory > 'maxmemory'. Команды чтения продолжают работать. Подходит, когда Redis хранит данные, которые нельзя восстановить, и лучше получить ошибку, чем потерять данные.
allkeys-lru — удалять любой ключ, который давно не использовался (least recently used). Хороший выбор для кеша общего назначения.
volatile-lru — удалять по LRU только ключи с установленным TTL. Ключи без TTL не трогаются. Подходит, когда Redis одновременно хранит кеш (с TTL) и важные данные (без TTL).
allkeys-lfu — удалять любой ключ, который редко используется (least frequently used). Лучше LRU для рабочих нагрузок с «горячими» и «холодными» ключами, потому что учитывает частоту обращений, а не только время последнего доступа.
volatile-lfu — удалять по LFU только ключи с TTL.
allkeys-random — удалять случайный ключ. Простейшая стратегия, минимальная нагрузка на CPU.
volatile-random — удалять случайный ключ с TTL.
volatile-ttl — удалять ключ с наименьшим оставшимся TTL (тот, который скоро истечёт и так). Подходит для сессий и временных данных.
Приближённые LRU и LFU
Redis не реализует точный LRU — это потребовало бы связного списка всех ключей с O(1) перемещением при каждом обращении. Вместо этого Redis использует приближённый алгоритм: при необходимости вытеснения выбирает maxmemory-samples случайных ключей (по умолчанию 5) и удаляет из них тот, который дольше всего не использовался. С увеличением maxmemory-samples точность растёт, но растёт и нагрузка на CPU. Значение 10 даёт результат, близкий к точному LRU.
Для LRU каждый redisObject хранит 24-битную метку времени последнего доступа (поле lru) с точностью до секунды. Для LFU те же 24 бита разделены на 16 бит timestamp последнего уменьшения и 8 бит логарифмического счётчика обращений (значения 0–255). Счётчик растёт не линейно: при каждом обращении он инкрементируется с вероятностью 1 / (old_counter * lfu_log_factor + 1), где lfu_log_factor (по умолчанию 10) управляет «чувствительностью». Чем выше текущее значение счётчика, тем ниже вероятность инкремента — это даёт логарифмическую шкалу, способную различать частоты от единиц до миллионов обращений. Параметр lfu-decay-time (по умолчанию 1 минута) определяет, как часто счётчик уменьшается: каждые N минут простоя счётчик уменьшается на 1. Это позволяет «забывать» ранее популярные ключи, отражая актуальную частоту доступа, а не накопленную за всё время.
Выбор политики
Выбор политики зависит от того, как Redis используется — как чистый кеш, как хранилище или в смешанном режиме.
| Сценарий | Рекомендуемая политика |
|---|---|
| Redis только для кеша | allkeys-lru или allkeys-lfu |
| Кеш + важные данные без TTL | volatile-lru |
| Все ключи с TTL (сессии) | volatile-ttl |
| Неравномерный доступ (hot/cold) | allkeys-lfu |
При использовании Redis как внешнего кэша политика allkeys-lru позволяет серверу автоматически вытеснять устаревшие данные. С точки зрения приложения вытеснение выглядит как обычный cache miss: GET возвращает nil, приложение загружает данные из PostgreSQL и записывает в Redis заново. Для чистого кеша это прозрачно. Проблемы начинаются, если приложение хранит в Redis данные, которые не может восстановить (сессии, счётчики без бэкапа): вытеснение удаляет их без предупреждения.
При volatile-* политиках, если ни один ключ не имеет TTL, Redis ведёт себя как noeviction — возвращает ошибку OOM. Поэтому при использовании volatile-* важно, чтобы у кеширующих ключей всегда был TTL.
Помимо политик вытеснения, потребление памяти зависит от проектирования ключей — именования, безопасного удаления и мониторинга: проектирование ключей.
Sources
- Redis Documentation: Eviction. https://redis.io/docs/reference/eviction/
- Redis source:
src/evict.c