Руководства по тюнингу рассказывают о пропускной способности. Никто не присылает вам алерты о пропускной способности. Вам присылают алерты о симптомах, и полезный навык заключается в том, чтобы сопоставить симптом с причиной, прежде чем тратить деньги на железо.
Три типа сбоев составляют большую часть того, что я нахожу на доставшихся по наследству серверах. Каждый из них имеет четкий почерк.
Ошибка 502, которую никто не может воспроизвести
На сервере 8 ГБ памяти. PHP-FPM настроен на 100 воркеров. Каждый воркер под нагрузкой использует 60 МБ. Итого 6 ГБ для PHP плюс MariaDB, плюс Nginx, плюс ОС.
При обычному трафике вы никогда не приближаетесь к 100 воркерам, поэтому месяцами всё выглядит нормально. Затем уходит маркетинговая рассылка, количество одновременных запросов резко возрастает, и в ядре заканчивается память. OOM killer выбирает процесс и завершает его — как правило, самый крупный, которым оказывается воркер PHP-FPM, обрабатывающий текущий запрос.
Пользователь получает 502. В логе приложения пусто, потому что процесс упал до того, как успел хоть что-то записать. Nginx пишет в логи recv() failed (104: Connection reset by peer) . Через десять минут всё снова выглядит нормально.
sudo dmesg -T | grep -i "killed process"
sudo journalctl -k | grep -i oom
Появление записей здесь означает, что перед вами не загадка. У вас есть значение pm.max_children , которое никто не соотнес с реальным объемом памяти.
Сайт, который деградирует весь день и сбрасывается за ночь
В 8 утра TTFB составляет 180 мс. К 16:00 он вырастает до 900 мс. Никаких деплоев не было. За ночь сайт снова начинает работать быстро, потому что что-то перезапустило PHP-FPM.
Всё дело в том, что в OPcache заканчивается свободное место. Когда кэш заполняется, он перестает кэшировать новые скрипты либо очищает память и перестраивается заново, и каждый промах кэша снова заставляет полностью парсить и компилировать код. Деградация происходит постепенно, поэтому её месяцами могут не замечать.
Счетчики для отслеживания — это oom_restarts и hash_restarts из функции opcache_get_status() .
Вот момент, на котором многие спотыкаются. Состояние OPcache привязано к конкретному SAPI. Если запустить эту функцию из CLI, вы будете читать кэш CLI, который пуст, существует отдельно и ничего не говорит о вашем сайте. Запрос нужно отправлять через PHP-FPM.
<?php
// drop in webroot, lock to your IP, delete when done
$allowed = ['203.0.113.42'];
if (!in_array($_SERVER['REMOTE_ADDR'], $allowed, true)) {
http_response_code(404);
exit;
}
$s = opcache_get_status(false);
$hits = $s['opcache_statistics']['hits'];
$miss = $s['opcache_statistics']['misses'];
echo 'hit rate: ', round($hits / max(1, $hits + $miss) * 100, 2), "%\n";
echo 'cached files: ', $s['opcache_statistics']['num_cached_scripts'],
' / ', $s['opcache_statistics']['max_cached_keys'], "\n";
echo 'oom restarts: ', $s['opcache_statistics']['oom_restarts'], "\n";
echo 'hash restarts: ', $s['opcache_statistics']['hash_restarts'], "\n";
echo 'cache full: ', var_export($s['cache_full'], true), "\n";
Если вы находитесь за Cloudflare или балансировщиком нагрузки, сначала проверьте, что именно содержит REMOTE_ADDR , иначе ваша защита превратится в проходной двор. Если вы не хотите трогать корневую директорию веб-сервера (webroot), утилита cachetool общается напрямую с сокетом FPM.
Значение по умолчанию для opcache.max_accelerated_files составляет 10 000. У приложения на CI4 с небольшим количеством зависимостей это около 1 500 – 3 000 файлов, и это нормально. Laravel со стандартным деревом зависимостей насчитывает от 10 000 до 15 000 файлов. Собственная проверка здоровья (health check) в Nextcloud рекомендует администраторам устанавливать это значение выше 80 000. Посчитайте свои файлы с помощью find . -name "*.php" | wc -l , прежде чем делать предположения.
Одно приложение, которое утягивает за собой пять других
Установка по умолчанию создает единственный пул www.conf , который разделяют все сайты на сервере. Поэтому, когда ваше наименее важное приложение начинает ждать медленный сторонний API, его запросы занимают воркеров из общего пула.
Эти воркеры становятся недоступны для всего остального. Ваш клиентский портал выстраивается в очередь за формой захвата лидов, которая обращается к зависшему CRM-эндпоинту.
По одному пулу на каждое приложение:
; /etc/php/8.5/fpm/pool.d/crm.conf
[crm]
user = www-data
group = www-data
listen = /run/php/php8.5-fpm-crm.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 8
pm.max_requests = 500
pm.status_path = /fpm-status
slowlog = /var/log/php8.5-fpm-crm-slow.log
request_slowlog_timeout = 5s
php_admin_value[memory_limit] = 256M
php_admin_value[error_log] = /var/log/php8.5-fpm-crm-error.log
Ограничение памяти на каждое приложение, раздельные логи, независимые перезапуски. Параметр pm.max_requests перезапускает воркеров, поэтому медленные утечки в старом коде локализуются, вместо того чтобы накапливаться до тех пор, пока не вмешается OOM killer.
Измеряйте память одного воркера, а не угадывайте её
Формула для pm.max_children , которую все цитируют, требует числа, которое большинство людей берет с потолка. Измерьте его на основе прогретых воркеров в продакшене:
$ ps -ylC php-fpm8.5 --sort:rss | awk 'NR>1 {print $8/1024" MB"}' | tail -5
58.4 MB
61.2 MB
63.9 MB
71.5 MB
88.1 MB
Сначала проверьте имя процесса с помощью ps -e | grep fpm , так как на Debian и Ubuntu это php-fpm8.5 , а в других дистрибутивах — просто php-fpm .
Рассчитывайте размер под пиковые нагрузки, а не под средние, и оставляйте запас по памяти. Если расчеты показывают 50, установите 40. Излишняя осторожность будет стоить вам чуть более длинной очереди. Излишняя самоуверенность будет стоить вам работы OOM killer.
Час работы в режиме «только чтение»
| Проверка | Как выполнять | Действовать, если |
|---|---|---|
| OOM-киллы | `dmesg -T \ | grep -i "killed process"` |
| Загрузка CPU (steal time) |
vmstat 1 5 , колонка st |
Устойчиво держится выше 5% |
| OPcache | скрипт на локалхосте через FPM, а не CLI | Hit rate ниже 95%, перезапуски больше нуля |
| Количество файлов | `find . -name "*.php" \ | wc -l` |
| Память воркера | ps -ylC php-fpm8.5 --sort:rss |
Превышает значения, заложенные в max_children |
| Изоляция пулов | ls /etc/php/8.5/fpm/pool.d/ |
Только www.conf при наличии нескольких сайтов |
Ни одно из этих действий ничего не меняет и ничего не перезапускает. Вы завершите этот час с пониманием того, с чем именно вы столкнулись — с проблемой конфигурации или с нехваткой ресурсов. Именно это различие определяет, делаете ли вы правку конфига или навсегда увеличиваете ежемесячный счет за сервер.
Более длинная версия материала, включающая настройку страницы статуса, stub_status в Nginx, кэширование FastCGI и правило обхода кэша, на котором обжигаются многие: Nginx and PHP-FPM Tuning for Mid-Market Infrastructure
Комментарии (0)
Пока нет комментариев — будьте первым.