Cache-warming через Cron: дёшево, скучно, работает
Каждую ночь в 02:00 у нас сбрасывается кеш.
В 02:01 на сайт приходят боты Google. В 02:03 начинают заходить пользователи из Азии — время там утреннее.
Раньше первые 20 минут TTFB составлял 4 секунды. Сейчас — 0.7. Разница: один bash-скрипт и запись в crontab. Без Redis Cluster, без Varnish.
Почему холодный кеш — это потеря денег
Тут не про «немного медленнее». Тут про конверсию.
Реальная картина: после ночного сброса кеша или деплоя PHP генерирует каждую страницу с нуля. Запросы в базу, компиляция шаблонов, выборки инфоблоков — всё по новой. На проекте с 28k SKU и 200–300 одновременных пользователей в пике это означает TTFB 3–5 секунд на первых запросах к популярным страницам.
По нашим измерениям на одном из ecom-проектов: в первые 15 минут после сброса кеша отказ (bounce rate) рос на 40% по сравнению с нормальным ночным трафиком. Часть пользователей просто уходила, не дождавшись загрузки.
Это заказы, которых нет в отчёте за ноябрь.
Решение казалось слишком простым. Прогреть кеш до того, как придут пользователи.
Как работает прогрев кеша
Скрипт обходит список URL до того, как придут реальные пользователи. Делает HTTP-запросы, PHP генерирует страницы и кладёт результат в кеш. Когда приходят люди — кеш уже тёплый, TTFB нормальный.
Три варианта реализации. На большинстве проектов хватает первого.
Вариант 1 — wget + sitemap.xml
Пятнадцать строк bash, работает на любом PHP-проекте.
#!/bin/bash
SITEMAP_URL="https://example.com/sitemap.xml"
DELAY=0.5 # секунд между запросами
URLS=$(wget -qO- "$SITEMAP_URL" | grep -oP '(?<=<loc>)[^<]+')
COUNT=0
for URL in $URLS; do
wget -q --spider "$URL" &>/dev/null
COUNT=$((COUNT + 1))
sleep $DELAY
done
echo "$(date): warmed $COUNT URLs" >> /var/log/cache-warm.log
Запускаем через cron за час до пика трафика или сразу после деплоя:
0 1 * * * /usr/local/bin/warm-cache.sh
Что здесь важно — --spider означает, что wget делает запрос, но не скачивает контент. Страница генерируется, кеш заполняется, лишнего трафика нет. Параметр DELAY=0.5 — принципиальный: без throttling можно самому себе устроить DoS.
На проекте с 2000 URL и задержкой 0.5 секунды прогрев занимает около 17 минут. Это нормально, если запускать за час до пика.
Вариант 2 — PHP/Bitrix CLI
Когда нужно больше контроля: прогревать только категории с высоким приоритетом, пропускать служебные страницы, логировать статусы ответа.
Bitrix поддерживает запуск PHP из командной строки через агентов. Создаём скрипт warm_cache.php:
<?php
define('STOP_STATISTICS', true);
define('NO_KEEP_STATISTIC', true);
define('NO_AGENT_CHECK', true);
define('NOT_CHECK_PERMISSIONS', true);
$_SERVER['DOCUMENT_ROOT'] = '/var/www/html';
require('/var/www/html/bitrix/modules/main/include/prolog_before.php');
$urls = [
'https://example.com/',
'https://example.com/catalog/',
// добавляем приоритетные страницы вручную или из БД
];
foreach ($urls as $url) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
file_put_contents('/var/log/cache-warm.log',
date('Y-m-d H:i:s') . " $url HTTP/$code\n",
FILE_APPEND
);
usleep(500000); // 0.5 секунды
}
Запускаем через cron:
0 1 * * * php /var/www/html/local/scripts/warm_cache.php
Плюс этого подхода: можно приоритизировать. Сначала главная, каталог, топ-10 категорий — за 5 минут. Потом остальное.
Вариант 3 — headless прогрев через Next.js ISR
Если фронт на Next.js, а Bitrix — источник данных, то прогрев кеша имеет два слоя: PHP-кеш на бэкенде и ISR-кеш на фронте. После деплоя Next.js оба могут быть холодными.
Next.js поддерживает revalidation endpoint — можно дёргать его принудительно:
# Принудительная ревалидация конкретного пути
curl -X POST "https://example.com/api/revalidate?path=/catalog&secret=YOUR_SECRET"
В реальном проекте мы запускаем это сразу после деплоя через CI/CD-хук. Bitrix-прогрев через wget идёт параллельно. К моменту первых пользователей оба кеша заполнены.
Детальнее механику ISR и связку двух кешей разберём в отдельной статье.
Как не убить сервер прогревом
Первая ошибка при внедрении — убрать sleep. Кажется логичным: хочется прогреть 2000 URL за 10 минут, а не за 17. Результат: сервер под нагрузкой, медленные ответы, в худшем случае падение PHP-FPM.
Во-первых, throttling обязателен. Минимум 300мс между запросами на нагруженных проектах, на слабых серверах — секунда. Конкретное значение подбираем по top во время тестового прогона в staging.
Во-вторых, одновременно должен работать только один прогрев. Используем lock-файл:
LOCKFILE="/tmp/cache-warm.lock"
if [ -f "$LOCKFILE" ]; then
echo "Already running" >> /var/log/cache-warm.log
exit 0
fi
touch "$LOCKFILE"
trap "rm -f $LOCKFILE" EXIT
В-третьих, логировать обязательно. >> /var/log/cache-warm.log — обязательная строка. Когда через месяц возникнет вопрос «а работал ли прогрев в ту ночь», ответ будет в файле, а не в воспоминаниях.
Похожую философию «скучная автоматизация надёжнее умной» мы применяли при построении cron-инфраструктуры из 13 задач — подробнее в этой статье.
Когда cache-warming не поможет
Прогрев кеша решает одну конкретную проблему: страницы медленно генерируются после сброса кеша. Если проблема другая — прогрев не поможет.
Симптомы других проблем, которые часто путают с cold cache:
- TTFB стабильно высокий в течение дня, а не только после сброса — это N+1 запросы или отсутствие индексов, а не cold cache. Подробнее — в статье «Bitrix не тормозит. Тормозит то, как с ним работают».
- TTFB нормальный, но страница долго рендерится в браузере — это LCP, JS-бандл, CSS blocking. Не серверная проблема.
- Прогрев работает, но кеш сбрасывается слишком часто — проблема в TTL или в слишком агрессивных инвалидациях. Тут нужно смотреть на стратегию кеширования, а не на прогрев.
Итог
Cache-warming через cron — одно из редких решений, где соотношение «сложность внедрения / эффект» максимальное.
Два часа работы, 15 строк bash и одна запись в crontab. TTFB после сброса кеша упал с 4 секунд до 0.7. Bounce rate в ночные часы вернулся в норму.
Это не масштабируется до уровня крупного маркетплейса с миллионом SKU. Там нужны другие инструменты. Но на типичном ecom-проекте на Bitrix с 5k–50k SKU — работает, и больше ничего не нужно.
Скучно. Дёшево. Работает.