Самая неприятная разновидность проблем с производительностью — та, где метрики выглядят плохо, а причина находится вне вашего кода. Профилировщик показывает, что каталог работает нормально. Запросы к базе укладываются в норму. Продажи не выросли. А CPU держится на шестидесяти процентах и периодически уводит сервер в ребут.
Разбор ниже — про то, как такая нагрузка ищется (в access-логах, а не в профилировщике) и почему блокировать её нужно на входе, а не в приложении.
Симптомы проблемы
На проекте 1С-Битрикс появилась проблема: VPS-сервер держал утилизацию CPU на уровне около 60%, а в отдельные периоды уходил выше и падал в ребут.
Всплески не были привязаны к:
- Рекламной активности
- Обновлениям каталога
- Росту реального пользовательского трафика
Дополнительно рос объём системного кэша, а каталог товаров вёл себя как источник постоянной фоновой нагрузки.
Анализ логов
Первым шагом стал анализ логов nginx:
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)Значительная часть нагрузки связана не с пользователями, а с роботом Facebook, который активно обходит страницы сайта.
Что именно делал crawler
Проблема была не просто в количестве обращений. По логам crawler активно перебирал URL каталога:
- Страницы разделов
- Страницы со smart.filter
- Страницы с пагинацией
- URL с сортировками
- URL с разными вариантами отображения
Каждый такой запрос запускал тяжёлую серверную обработку и порождал новые варианты кэша.
Почему блокировка в PHP — плохая идея
При блокировке на уровне приложения:
- Запрос уже дошёл до nginx
- Затем до Apache или php-fpm
- Затем до Битрикса
- И только после этого сервер начал бы его отбрасывать
Нагрузка уже возникла. Защита должна стоять как можно раньше.
Стоимость запроса, отброшенного nginx, — микросекунды и ноль обращений к базе. Стоимость того же запроса, отброшенного в init.php, — полный старт ядра Битрикса: подключение модулей, соединение с MySQL, чтение настроек. Разница между этими двумя вариантами на потоке в сто тысяч запросов и есть те самые шестьдесят процентов CPU.
Решение: блокировка на уровне nginx
Оптимальная точка — nginx, который стоит перед приложением.
Преимущества:
- Запрос отбрасывается до запуска PHP
- Apache и Битрикс не участвуют в обработке
- Не создаётся дополнительная нагрузка на CPU
- Можно точно ограничить только опасные сценарии
Конфигурация nginx
Определение условий
# Определяем meta-externalagent
map $http_user_agent $is_meta_externalagent {
default 0;
~*meta-externalagent 1;
}
# Определяем тяжёлые URL каталога
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 {
listen 80;
server_name example.com;
# Блокируем crawler только на тяжёлых URL
if ($block_meta_heavy_catalog) {
return 403;
}
# ... остальная конфигурация
}Почему не блокируем crawler целиком
Полная блокировка могла бы повлиять на корректность генерации превью ссылок в соцсети. Вместо этого выбрана точечная схема: ограничиваем именно тяжёлые URL каталога.
| URL | Crawler | Результат |
|---|---|---|
/ | meta-externalagent | ✅ Доступ |
/catalog/category/ | meta-externalagent | ✅ Доступ |
/catalog/category/?PAGEN_1=5 | meta-externalagent | ❌ 403 |
/catalog/category/filter/price-from-1000/ | meta-externalagent | ❌ 403 |
/catalog/category/?sort=price | meta-externalagent | ❌ 403 |
Результаты
После включения блокировки:
| Метрика | До | После |
|---|---|---|
| Утилизация CPU | ~60% | 10-15% |
| Фоновая нагрузка | Постоянная | Минимальная |
| Стабильность | Ребуты | Стабильно |
| Обход основных страниц | Работает | Работает |
Дополнительно: блокировка других ботов
Аналогично можно настроить блокировку для других агрессивных ботов:
map $http_user_agent $is_aggressive_bot {
default 0;
~*meta-externalagent 1;
~*AhrefsBot 1;
~*SemrushBot 1;
~*MJ12bot 1;
~*DotBot 1;
}
map "$is_aggressive_bot:$is_heavy_catalog_url" $block_aggressive_bot_heavy {
default 0;
"1:1" 1;
}Мониторинг
Логирование заблокированных запросов стоит настроить сразу: без него вы не узнаете, отсекается ли лишнее и не попал ли под раздачу кто-то нужный.
log_format blocked '$remote_addr - $http_user_agent - $request_uri';
server {
# ...
# Отдельный лог только для заблокированных запросов
access_log /var/log/nginx/blocked_crawlers.log blocked if=$block_meta_heavy_catalog;
if ($block_meta_heavy_catalog) {
return 403;
}
}access_log нельзя поместить внутрь if на уровне server. Директива допустима в http, server, location и if in location — при попытке объявить её в блоке if уровня сервера nginx не стартует с ошибкой «directive is not allowed here». Правильный способ условного логирования — параметр if= у самой директивы: значение переменной, отличное от 0 и пустой строки, включает запись.
Анализ логов:
# Топ заблокированных User-Agent
awk '{print $3}' /var/log/nginx/blocked_crawlers.log | sort | uniq -c | sort -nr | head -10
# Топ заблокированных URL
awk '{print $4}' /var/log/nginx/blocked_crawlers.log | sort | uniq -c | sort -nr | head -20Ограничения этого подхода
Метод рабочий, но у него есть чёткая граница применимости, и о ней лучше знать заранее.
Блокировка по User-Agent работает только с теми, кто честно представляется. Facebook, Ahrefs, Semrush указывают себя в заголовке, потому что заинтересованы в том, чтобы их пускали. Парсер конкурента или скрапер для перепродажи данных подставит Mozilla/5.0 и пройдёт мимо всех правил выше.
Против таких нужен другой инструмент — ограничение частоты, которое работает вне зависимости от того, кем представляется клиент:
# Отдельная зона для тяжёлых URL каталога
limit_req_zone $binary_remote_addr zone=catalog_heavy:10m rate=2r/s;
location /catalog/ {
limit_req zone=catalog_heavy burst=10 nodelay;
# ...
}return 444 дешевле, чем return 403. Специальный код nginx закрывает соединение без ответа: не тратится время на формирование страницы ошибки и не отдаётся трафик. Для явно нежелательных запросов это разумнее, но применять его к крупным поисковым роботам не стоит — лучше отдать им понятный статус.
403 — сигнал «доступ запрещён навсегда». Если бот вернётся к этим URL, он будет получать тот же ответ и в конце концов уберёт их из своей очереди — что нам и нужно. Но если вы ограничиваете временную нагрузку и хотите, чтобы бот вернулся позже, честнее отдавать 429 с заголовком Retry-After.
Вывод
Если на Битриксе неожиданно растёт CPU без видимого роста продаж:
- Смотрите в access-логи nginx, а не только в профилировщик PHP. Профилировщик отвечает на вопрос «что тормозит», логи — на вопрос «кто это заказал», а во внешней нагрузке важен именно второй.
- Блокируйте на входе, а не в приложении: цена отброшенного запроса отличается на порядки.
- Блокируйте точечно. Полный запрет для крупного краулера — это потеря превью ссылок в соцсетях или пропажа сайта из чьего-то индекса. Отсекать нужно конкретные тяжёлые сценарии, а не агента целиком.
- Настройте лимит частоты как второй рубеж — он работает и против тех, кто не представляется.
Следующий шаг: После устранения внешней нагрузки займитесь внутренней проблемой — почему запросы к каталогу порождали неконтролируемый рост кэша catalog.section.
Комментарии
Система комментариев скоро будет подключена. А пока вы можете написать мне в Telegram или на email.