Программа курса

Модуль 02 / Урок 7 из 27

TTL и eviction-политики

Теория и практика

Два механизма, которые управляют жизнью данных в Redis: истечение по времени (TTL) и вытеснение при нехватке памяти (eviction). Непонимание первого даёт «вечный мусор», второго — необъяснимые пропажи ключей.

TTL: как ключи умирают

Redis не обходит все ключи по таймеру — это было бы O(N) каждую секунду. Истечение работает в два механизма:

  • Пассивное: при обращении к ключу Redis проверяет его время истечения; истёкший ключ удаляется, клиент получает «не существует»;
  • Активное: 10 раз в секунду Redis семплирует случайные ключи из словаря expires и удаляет истёкшие; если истёкло > 25% выборки — сразу повторяет цикл.

Итого: ключ без обращений тоже умрёт в разумное время, но точного момента гарантии нет — TTL в Redis это не «будильник», а вероятностный сборщик мусора.

SET session:abc "..." EX 3600     # секунды; PX — миллисекунды
TTL session:abc                    3599
EXPIRE session:abc 60              # перезадать
EXPIRETIME session:abc             # unix-время истечения
PERSIST session:abc                # снять TTL

# Redis 7+: условные EXPIRE
EXPIRE key 60 NX   # только если TTL ещё нет
EXPIRE key 60 XX   # только если TTL уже есть
EXPIRE key 60 GT   # только продлевая (новый TTL больше)
EXPIRE key 60 LT   # только сокращая

Подводные камни TTL

  • EXPIRE привязан к ключу, а не к значению: HSET на ключ с TTL не продлевает TTL (и не снимает его) — запомните это; продление только явным EXPIRE;
  • SET снимает TTL: перезапись значения через SET/GETSET стирает время истечения (проверьте: SET k v EX 10, SET k v2, TTL k → −1);
  • переименование сохраняет TTL (RENAME переносит expiry), но TTL сессии обычно продлевают именно на каждом обращении;
  • часы сервера: TTL сравнивается с системным временем мастера; расхождение часов master/replica даёт разное «время смерти» одного ключа.
💡 Паттерн «скользящая сессия»: при каждом обращении EXPIRE session:{id} 1800. А для «абсолютного» TTL дедлайна — сравнивайте EXPIRETIME в приложении.

Eviction: что удалять, когда память кончилась

maxmemory задаёт лимит данных (не всего процесса!). При его достижении включается политика maxmemory-policy:

ПолитикаЧто делаетДля кого
noevictionошибка OOM на записьRedis как основное хранилище (по умолчанию)
allkeys-lruвыкидывает редко используемый (по времени) ключклассический кеш
allkeys-lfuвыкидывает наименее частотный ключкеш с горячими ключами и сканами
volatile-lru / volatile-lfuLRU/LFU только среди ключей с TTLсмешанное БД+кеш
volatile-ttlвыкидывает ключи с ближайшим истечениемредко
allkeys-random / volatile-randomслучайнопочти никогда

LRU и LFU в Redis — приближённые

Настоящий LRU-список на миллиардах ключей дорог, поэтому Redis держит 24-битную метку последнего доступа у каждого объекта и выбирает жертву из случайной выборки (maxmemory-samples 5, по умолчанию). Точность растёт с выборкой, но идеального LRU нет — и это нормально.

LFU (Redis 4+) хранит логарифмический счётчик частоты с затуханием по времени (lfu-log-factor 10, lfu-decay-time 1): недавно горячий, но остывший ключ «остывает» и может быть вытеснен. Для кешей это устойчивее LRU: редкий полный обход всех данных (бэкап-скрипт, отчёт) не выметает горячие ключи.

💡 Дефолт для кеша — allkeys-lfu. Меняйте, только если понимаете, почему. И никогда не полагайтесь на eviction для «важных» ключей: всё, что под политикой вытеснения, может исчезнуть в любой момент.

Практика

  1. Продемонстрируйте, что SET снимает TTL, а HSET — нет.
  2. CONFIG SET maxmemory 50mb, maxmemory-policy allkeys-lru; залейте 200 МБ benchmark'ом и посмотрите INFO stats | grep evicted. Повторите с lfu и сравните evicted_keys.
  3. Сымитируйте «скан-кэш-трэшинг»: горячий ключ + фоновый обход 100 000 холодных — сравните выживание горячего ключа под LRU и LFU.
Мини-квиз

1. Ключ с TTL никто не читает. Когда он умрёт? — При очередном проходе активного истечения, когда попадёт в выборку; гарантированного момента нет.

2. Какая политика нужна «Redis = кеш + немного важных данных»? — volatile-*: вытесняются только ключи с TTL.

3. Почему LFU лучше переносит массовые обходы? — LRU метит «недавно тронутые» обходом ключи горячими; LFU смотрит на частоту и не даёт однократному скану вытеснить по-настоящему частые ключи.