Итак, в логах nginx:
[error] 6#6: *57200 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 10.233.82.123, server: , request: "GET /my/page HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "site.ru", referrer: "https://site.ru/"В логах php-fpm:
NOTICE: [pool www] child 255 started (иногда эта строка не появляется в логах, почему неизвестно)
WARNING: [pool www] child 255 exited on signal 9 (SIGKILL) after 158.109586 seconds from startПри этом, если посмотреть логи базы данных, то для такого запроса можно увидеть:
postgres_1 | LOG: could not send data to client: Connection reset by peer
postgres_1 | FATAL: connection to client lost
При такой ситуации у приложения могут быть один, несколько или все следующие проблемы:
Стоит заметить, что nginx:
Важно: когда в настройках php-fpm не указан таймаут, то php-fpm процессы продолжают жить до тех пор, пока не выполнятся, независимо от того, какой из php-fpm pm-режимов выбран.
А теперь, давайте рассмотрим каждый из выше указанных трех случаев.
Эта ситуация возникает, когда cpu/memory сервису хватает, но одинаковые запросы в параллель выполняются медленно, в то время как сам по себе запрос выполняется достаточно быстро. Данную проблему можно побороть только исправлениями в самом приложении и обычно проблема заключается в:
Чтобы проверить эту гипотезу и повторить проблему локально, установите в docker-compose.yml:
php-fpm:
image: registry.site.ru/my-php-fpm:c3db38f46f7
environment:
- APP_ENV=prod
deploy:
resources:
limits:
cpus: '0.5'
memory: 128Mи запустив:
docker-compose -f docker-compose.test.yml --compatibility up
можно наблюдать статистику по ресурсам:
docker stats
Если в результате нагрузочных тестов вы видите, что MEMORY USAGE значение достигает или почти достигает значения в столбце LIMIT, то вам просто нужно увеличить кол-во памяти выданных контейнеру (хорошо, когда есть небольшой запас).
Наблюдаю ситуацию, когда при трех конкурентных (одновременных) запросах, 1 из 3 падает с ошибкой:
WARNING: [pool www] child 13 exited on signal 9 (SIGKILL) after 133.010222 seconds from start
Первым делом смотрим в /usr/local/etc/php-fpm.d/www.conf и видим настройки:
pm = dynamic - кол-во процессов PHP-FPM меняется динамически, в зависимости от количества запросов
pm.max_children = 5 - максимальное кол-во дочерних процессов, которое будет создано для пула
pm.start_servers = 2 - кол-во дочерних процессов, которые будут созданы при старте пула
pm.min_spare_servers = 1 - минимальное кол-во дочерних процессов в статусе ожидания (idle)
pm.max_spare_servers = 3 - максимальное кол-во дочерних процессов в статусе ожидания (idle)Нужно изменить эти настройки, ведь кроме SIGKILL я вижу совет от самой php-fpm:
WARNING: [pool www] server reached pm.max_children setting (5), consider raising it
Поэтому указываю:
pm = dynamic
pm.max_children = 25
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 5
но даже при таких настройках я каждый раз ловлю в логах php-fpm:
WARNING: [pool www] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning 8 children, there are 0 idle, and 8 total children
WARNING: [pool www] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning 16 children, there are 0 idle, and 9 total children
В общем, нужно подбирать параметры или переходить на режим pm = ondemand, где таких ворнингов не будет.
Года три тому назад, я тестировал разные режимы работы php-fpm для одного высоконагруженного php-приложения и для тех мощностей, которыми он обладал, мне удалось инструментом ab выяснить, что лучшие результаты (максимальное возможное кол-во одновременных процессов равное 406) можно получить такими настройками:
[www]
user = www-data
group = www-data
listen = 127.0.0.1:9000
; ondemand - FPM не будет создавать дочерних процессов, пока не появится реальный запрос для обработки, а запустит только мастер-процесс самого php-fpm
pm = ondemand
; максимальное возможное кол-во одновременно работающих процессов (вычисляется опытным путем, например с помощью ab):
pm.max_children = 406
; время, через которое Process Manager уничтожит дочерний процесс, который не обслуживает запросы:
pm.process_idle_timeout = 60Однако, в этом проекте не были установлены таймауты в nginx и php-fpm и выше указанные настройки помогли.
Если вы не используете таймауты в nginx и php-fpm, то вы идете неправильной дорожкой, потому что любой сервис (в том числе на пхп-фпм) не должен класть в долгий ящик все запросы, а клиенты не должны вечно ждать ответа, это неправильная тактика, сервис должен отвечать достаточно быстро (в согласованное время), а если не может уложиться по времени (согласно договоренности), то прекращать обрабатывать запрос (по возможности сразу, как только обговоренное время вышло), тем самым отдавая честный ответ клиенту - не успел/не смог (извини).
А если не указывать лимиты, то ваш сервис столкнется с ситуацией, когда ему не будет хватать, то памяти (потому что очень много запросов в обработке) то процессора, и клиенты будут недовольны медленной работой сервиса.
Отсюда мне видится, что подходить к настройке каждого приложения нужно индивидуально (серебренной пули не выйдет), но, уйти от стандартно настроенного пхп-фпм это здравая мысль хотя бы по двум причинам:
Таким образом, для пхп-фпм можно для любого из pm вариантов, просто задрать показатели повыше (но из опыта не советую pm = static). А если хотим, чтобы приложение немного экономило память + при изменении мощностей контейнера не требовало частой корректировки php-fpm конфига, то предлагаю использовать pm = ondemand
Вопросы на будущее (домашнее задание):
p.s. выше обозначенные проблемы иногда удобно тестировать так:
<?php
// тестируем расход memory:
$a = str_repeat("ПриветМир", 3000000);
echo memory_get_peak_usage(true);
// тестируем время выполнения:
// sleep(3);
// тестируем аутпут
// $fo = fopen('php://stdout', 'w');
// fwrite($fo, str_repeat("Hi-", 90));
// тестируем нагрузку на CPU:
// for ($i=0; $i< 300000; $i++) {
// // квадратный корень timestamp-а
// sqrt(time()-$i);
// }
exit;маленький итог по тестам:
p.s. если вы видите следующую проблему, то скорее всего дело в срабатывании php-fpm настройки pm.max_requests
NOTICE: [pool www] child 10 exited with code 0 after 13.274903 seconds from startА еще такие сообщения появляются когда изменено дефолтное значение pm.max_requests - если указать больше 0, то php-fpm-master будет убивать php-fpm-kid процесс после того, как php-fpm-kid обслужил указанное тут кол-во http-запросов. Это полезно для избежания утечек памяти при использовании сторонних библиотек, однако т.к. я использую request_terminate_timeout=2, то мне выгоднее дефолтное значение pm.max_requests=0 тем, что не засоряет логи выше указанными нотисами.
p.s. 2 проверить изменение конфига php-fpm можно командой:
php-fpm -ttКак это делают в настоящий момент:
1. https://github.com/hipages/php-fpm_exporter каждую секунду собирает с php-fpm данные https://www.php.net/manual/en/fpm.status.php
2. https://github.com/prometheus/prometheus каждые 15 секунд собирает с php-fpm_exporter-а данные, которые он накопил
Что интересно, а главное важно: php-fpm_exporter при сборе всегда инкрементирует собранные значения, и сбрасывает их после того, как Prometheus забрал их (поэтому не переживайте за лаг в 15 секунд).
Q: Как знания выше, помогут нам мониторить кол-во дочерних php-fpm процессов, которые килнул мастер php-fpm процесс
A: php-fpm делая "terminating" делает это когда скрипт потребил слишком большое кол-во памяти (ситуация не частая, за всю жизнь встречал ее на проде 2-3 раза) ИЛИ дочерний php-fpm процесс работал слишком долго (см. php-fpm настройку request_terminate_timeout). Как раз последнее может происходить достаточно часто, и мониторить можно не количество "terminating", а количество phpfpm_slow_requests.
По умолчанию phpfpm_slow_requests всегда равно нулю, т.к. в php-fpm образе не указан request_slowlog_timeout - https://www.php.net/manual/en/install.fpm.configuration.php#request-slowlog-timeout поэтому в образе php-fpm нужно (через ENV-переменную) указывать настройку request_slowlog_timeout + при этом slowlog направив в /dev/null, иначе образ может пухнуть в следствии появления слишком большого количества медленных запросов