В Redis версия кода записана в имя ключа: frag:{версия}:{ключ}. После выката старые ключи никто не читает. Кажется, они истекут сами через неделю — и чистить их не нужно. Мы тоже так думали.
Это пятнадцатая часть цикла «Кэш фрагментов и Redis».
Почему мёртвые поколения опасны
Redis настроен с noeviction (четырнадцатая часть): когда память кончается, он не выкидывает старое, а отказывает в записи. При частых выкатах каждое поколение лежит неделю. Два-три выката подряд — и памяти не хватает. Новые ячейки не записываются, кэш превращается в его отсутствие: каждый показ — MISS.
«Истекут сами» — неправда при noeviction. Мёртвые поколения надо выносить самим, сразу после того, как новое поколение прогрелось.
Как выносить
function frag_delete_dead_generations(string $current): int {
$redis = frag_redis();
$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
$removed = 0;
$it = null;
try {
while ($keys = $redis->scan($it, 'frag:*', 1000)) {
$dead = array_filter($keys, fn($k) => strpos($k, "frag:{$current}:") !== 0);
if ($dead) {
$removed += $redis->unlink($dead);
}
}
} catch (RedisException $e) {
error_log('frag gc: ' . $e->getMessage());
}
frag_count('gc_removed', $removed);
return $removed;
}
SCAN, а неKEYS— не блокирует Redis на всё время обхода;UNLINK, а неDEL— память освобождается в фоне;- перехват ошибок и счётчик — видно, сколько вынесли.
Результат: 45 611 ключей за 0,32 с, занятая память 252 → 14,65 МБ.
Запускать — внутри периодического воркера, по смене версии кода, а не разовым событием (почему — тридцать шестая часть цикла «Кэш страниц без MISS»).
Сколько памяти давать
При выкате в памяти одновременно два поколения: старое ещё не вынесено, новое уже греется. Отсюда формула:
maxmemory ≥ 2 × (число ячеек × средний размер ячейки) × запас
Средняя ячейка после сжатия у нас — 5,7 КБ. 32 269 ячеек — 181 МБ на поколение. Лимит в 512 МБ оказался впритык; подняли до 1 ГБ.
# на работающем экземпляре — сразу
redis-cli config set maxmemory 1gb
# и в docker-compose — чтобы пережило перезапуск
# (CONFIG REWRITE без redis.conf ничего не сохранит)
Занятость памяти выводится в мониторинг; от 75 % строка становится красной (шестнадцатая часть).
Отложенная идея
Можно не выносить прошлое поколение сразу, а какое-то время отдавать его как STALE — тогда после выката посетители получали бы старую разметку мгновенно вместо MISS. Мы этого не сделали: старая разметка может не работать с новым скриптом, ради этого и сделан жёсткий MISS при смене кода.
Redis переполняется после выкатов?
Пришлите адрес — посчитаю поколения и подберу лимит памяти.
// загружаю комментарии…