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
Кеш + важные данные без TTLvolatile-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


Внутренние кодировки | Проектирование ключей