Как crawler перегружал VPS и разгонял CPU до 60%
У проекта ( 0ffer.ru ) на 1C-Битрикс появилась неприятная и на первый взгляд неочевидная проблема: VPS-сервер стабильно держал утилизацию CPU на уровне около 60%, а в отдельные периоды уходил и выше и падал в ребут. При этом всплески не были привязаны к рекламной активности, обновлениям каталога товаров или росту реального пользовательского трафика.
Дополнительно рос объем системного кеша, а каталог товаров вел себя как источник постоянной фоновой нагрузки. Уже на этом этапе стало понятно, что проблема не только в производительности VPS, PHP или базы данных, а в характере нагрузки со стороны входящих "запросов".
Симптомы
Проблема проявлялась сразу в нескольких направлениях:
- утилизация CPU на VPS на уровне 60% даже вне пиков продаж ( в ночное время ).
- На сайте не наблюдалось соразмерного объема реальных пользовательских переходов.
- Кеш каталога рос значительно быстрее, чем это можно было объяснить обычной работой интернет-магазина.
- В логах куча однотипных обращений к URL-каталога с большим количеством вариаций ( через GET-параметры ).
Фактически сервер тратил ресурсы не на обслуживание покупателей, а на обработку большого числа однотипных технических запросов.
С чего начали анализ
Первым шагом стал анализ логов nginx. Базовая статистика по User-Agent быстро показала, что значительный объем запросов генерирует не браузерный трафик, а crawler от Facebook:
Facebook - Организация, деятельность которой запрещена на территории РФ
awk -F\" '{print $6}' access.log | sort | uniq -c | sort -nr | head -50
В топе оказался:
115604 meta-externalagent/1.1 (+https://developers.facebook.com/docs/sharing/webmasters/crawler)
После этого уже стало понятно, что значительная часть нагрузки связана не с пользователями, а с роботом, который активно обходит страницы сайта для построения preview и других служебных задач.
Что именно делал crawler
Проблема была не просто в большом количестве обращений. По логам оказалось, что crawler активно перебирает URL каталога:
- страницы разделов,
- страницы со smart.filter,
- страницы с пагинацией,
- URL с сортировками,
- URL с разными вариантами отображения списка.
Именно это делало ситуацию опасной. Каждый такой запрос запускал тяжелую серверную обработку каталога, а в ряде случаев еще и порождал новые варианты кеша.
Важно отметить, что подробнее про неконтролируемый рост catalog.section кеша и нашу схему его ограничения мы отдельно рассказали во второй статье. Здесь фокус именно на внешнем источнике нагрузки и его блокировке.
Почему блокировка на уровне приложения была плохой идеей
Теоретически можно было бы отсекать такие запросы в PHP или даже в логике сайта. Но в этом случае:
- запрос уже дошел бы до nginx,
- затем до apache или php-fpm,
- затем до Битрикса,
- и только после этого сервер начал бы его отбрасывать.
То есть нагрузка уже возникла бы. Для подобных ситуаций защита должна стоять как можно раньше по цепочке обработки запроса.
Почему выбрали именно nginx
Оптимальной точкой блокировки стал nginx, который стоит перед приложением.
Такой подход дает сразу несколько преимуществ:
- запрос отбрасывается до запуска PHP,
- Apache и Битрикс не участвуют в обработке,
- не создается дополнительная нагрузка на CPU,
- можно очень точно ограничить только опасные сценарии.
Мы не стали блокировать crawler целиком по всему сайту, потому что это могло бы повлиять на корректность генерации превью ссылок в соцсети. Вместо этого была выбрана точечная схема: ограничивать именно тяжелые URL каталога.
Как настроили блокировку
В nginx были добавлены правила, которые:
- определяют запросы от meta-externalagent,
- отдельно определяют тяжелые URL каталога,
- возвращают отказ только в случае пересечения этих двух условий.
Пример конфигурации:
map $http_user_agent $is_meta_externalagent {
default 0;
~*meta-externalagent 1;
}
map $request_uri $is_heavy_catalog_url {
default 0;
~^/catalog/.*/filter/ 1;
~^/catalog/.*(-from-|-to-) 1;
~^/catalog/.*[?&]PAGEN_[0-9]+= 1;
~^/catalog/.*[?&]sort= 1;
~^/catalog/.*[?&]display= 1;
~^/catalog/.*[?&]linerow= 1;
}
map "$is_meta_externalagent:$is_heavy_catalog_url" $block_meta_heavy_catalog {
default 0;
"1:1" 1;
}
А затем в server block:
if ($block_meta_heavy_catalog) {
return 403;
}
Такой вариант позволил сохранить доступ crawler к обычным страницам, но отсечь наиболее дорогие в обработке URL каталога.
Результат
После включения блокировки ситуация изменилась довольно быстро:
- постоянная фонова нагрузка ушла,
- утилизация CPU на VPS стабилизировалась,
- средняя утилизация снизилась примерно до 10-15%,
- сервер перестал тратить ресурсы на бесконечный обход тяжелых страниц каталога.
Это был ключевой переломный момент: стало ясно, что значительная часть инфраструктурной проблемы находилась вне самого кода Битрикса и была вызвана агрессивным внешним обходом.
Вывод
Если у вас на Битриксе или любом другом CMS-проекте неожиданно растет CPU без видимого роста продаж и посещаемости, первым делом нужно смотреть не только в профилировщик PHP, но и в access-логи фронтового веб-сервера.
В нашем случае именно разбор логов nginx показал реальный источник постоянной нагрузки. Блокировка на уровне nginx позволила быстро и дешево с точки зрения ресурсов снять проблему, не дожидаясь глубокой переработки приложения.
А уже следующим этапом мы занялись второй частью проблемы: почему запросы к каталогу порождали неконтролируемый рост кеша catalog.section, и как этот рост удалось ограничить - Рост кеша catalog.section в 1С-Битрикс и как перестать кешировать токсичные URL каталога