В 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 переполняется после выкатов?

Пришлите адрес — посчитаю поколения и подберу лимит памяти.

· полезно?
АЧ
Алексей Чикун веб-разработчик, команда TRIOHEAD

Делаю сайты и сервисы целиком: от вёрстки до сервера. Пишу о том, что проверил на своих проектах.

Комментарии

// загружаю комментарии…

Комментарии публикуются после проверки. Ссылки и HTML не принимаются.