Сравниваем PHP FPM, PHP PPM, Nginx Unit, React PHP и RoadRunner



    Тестирование производилось с помощью Yandex Tank.
    В качестве приложения использовались Symfony 4 и PHP 7.2.
    Целью являлось сравнение характеристик сервисов при разных нагрузках и нахождение оптимального варианта.
    Для удобства все собрано в docker-контейнеры и поднимается с помощью docker-compose.
    Под катом много таблиц и графиков.

    Исходный код лежит тут.
    Все примеры команд, описанные в статье, должны выполняться из директории проекта.


    Приложение


    Приложение работает на Symfony 4 и PHP 7.2.


    Отвечает только на один роут и возвращает:


    • случайное число;
    • окружение;
    • pid процесса;
    • имя сервиса, с помощью которого работает;
    • переменные php.ini.

    Пример ответа:


    curl 'http://127.0.0.1:8000/' | python -m json.tool
    {
        "env": "prod",
        "type": "php-fpm",
        "pid": 8,
        "random_num": 37264,
        "php": {
            "version": "7.2.12",
            "date.timezone": "Europe/Paris",
            "display_errors": "",
            "error_log": "/proc/self/fd/2",
            "error_reporting": "32767",
            "log_errors": "1",
            "memory_limit": "256M",
            "opcache.enable": "1",
            "opcache.max_accelerated_files": "20000",
            "opcache.memory_consumption": "256",
            "opcache.validate_timestamps": "0",
            "realpath_cache_size": "4096K",
            "realpath_cache_ttl": "600",
            "short_open_tag": ""
        }
    }

    В каждом контейнере настроен PHP:



    Логи пишутся в stderr:
    /config/packages/prod/monolog.yaml


    monolog:
        handlers:
            main:
                type: stream
                path: "php://stderr"
                level: error
            console:
                type: console

    Кеш пишется в /dev/shm:
    /src/Kernel.php


    ...
    class Kernel extends BaseKernel
    {
        public function getCacheDir()
        {
            if ($this->environment === 'prod') {
                return '/dev/shm/symfony-app/cache/' . $this->environment;
            } else {
                return $this->getProjectDir() . '/var/cache/' . $this->environment;
            }
        }
    }
    ...

    В каждом docker-compose запускаются три основных контейнера:


    • Nginx — реверсивный прокси-сервер;
    • App — подготовленный код приложения со всеми зависимостями;
    • PHP FPM\Nginx Unit\Road Runner\React PHP — сервер приложения.

    Обработка запросов ограничивается двумя инстансами приложения (по числу ядер процессора).


    Сервисы


    PHP FPM


    Менеджер PHP процессов. Написан на C.


    Плюсы:


    • не нужно следить за памятью;
    • не нужно ничего менять в приложении.

    Минусы:


    • на каждый запрос PHP должен инициализировать переменные.

    Команда для запуска приложения с docker-compose:


    cd docker/php-fpm && docker-compose up -d

    PHP PPM


    Менеджер PHP процессов. Написан на PHP.


    Плюсы:


    • инициализирует переменные один раз и затем использует их;
    • не нужно ничего менять в приложении (есть готовые модули для Symfony/Laravel, Zend, CakePHP).

    Минусы:


    • нужно следить за памятью.

    Команда для запуска приложения с docker-compose:


    cd docker/php-ppm && docker-compose up -d

    Nginx Unit


    Сервер приложений от команды Nginx. Написан на С.


    Плюсы:


    • можно менять конфигурацию по HTTP API;
    • можно запускать одновременно несколько инстансов одного приложения с разными конфигурациями и версиями языков;
    • не нужно следить за памятью;
    • не нужно ничего менять в приложении.

    Минусы:


    • на каждый запрос PHP должен инициализировать переменные.

    Чтобы передать переменные окружения из файла конфигурации nginx-unit, необходимо поправить php.ini:


    ; Nginx Unit
    variables_order=E

    Команда для запуска приложения с docker-compose:


    cd docker/nginx-unit && docker-compose up -d

    React PHP


    Библиотека для событийного программирования. Написана на PHP.


    Плюсы:


    • c помощью библиотеки можно написать сервер, который будет инициализировать переменные только один раз и дальше работать с ними.

    Минусы:


    • необходимо написать код для сервера;
    • необходимо следить за памятью.

    Если использовать для воркера флаг --reboot-kernel-after-request, то Symfony Kernel будет инициализироваться заново на каждый запрос. При таком подходе не нужно следить за памятью.


    Код воркера
    #!/usr/bin/env php
    
    <?php
    
    use App\Kernel;
    use Symfony\Component\Debug\Debug;
    use Symfony\Component\HttpFoundation\Request;
    
    require __DIR__ . '/../config/bootstrap.php';
    
    $env   = $_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? 'dev';
    $debug = (bool)($_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? ('prod' !== $env));
    
    if ($debug) {
        umask(0000);
    
        Debug::enable();
    }
    
    if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? $_ENV['TRUSTED_PROXIES'] ?? false) {
        Request::setTrustedProxies(explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST);
    }
    
    if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? $_ENV['TRUSTED_HOSTS'] ?? false) {
        Request::setTrustedHosts(explode(',', $trustedHosts));
    }
    
    $loop   = React\EventLoop\Factory::create();
    $kernel = new Kernel($env, $debug);
    $kernel->boot();
    $rebootKernelAfterRequest = in_array('--reboot-kernel-after-request', $argv);
    
    /** @var \Psr\Log\LoggerInterface $logger */
    $logger = $kernel->getContainer()->get('logger');
    $server = new React\Http\Server(function (Psr\Http\Message\ServerRequestInterface $request) use ($kernel, $logger, $rebootKernelAfterRequest) {
    
        $method  = $request->getMethod();
        $headers = $request->getHeaders();
        $content = $request->getBody();
        $post    = [];
        if (in_array(strtoupper($method), ['POST', 'PUT', 'DELETE', 'PATCH']) &&
            isset($headers['Content-Type']) && (0 === strpos($headers['Content-Type'], 'application/x-www-form-urlencoded'))
        ) {
            parse_str($content, $post);
        }
        $sfRequest = new Symfony\Component\HttpFoundation\Request(
            $request->getQueryParams(),
            $post,
            [],
            $request->getCookieParams(),
            $request->getUploadedFiles(),
            [],
            $content
        );
        $sfRequest->setMethod($method);
        $sfRequest->headers->replace($headers);
        $sfRequest->server->set('REQUEST_URI', $request->getUri());
    
        if (isset($headers['Host'])) {
            $sfRequest->server->set('SERVER_NAME', current($headers['Host']));
        }
    
        try {
            $sfResponse = $kernel->handle($sfRequest);
        } catch (\Exception $e) {
            $logger->error('Internal server error', ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
            $sfResponse = new \Symfony\Component\HttpFoundation\Response('Internal server error', 500);
        } catch (\Throwable $e) {
            $logger->error('Internal server error', ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
            $sfResponse = new \Symfony\Component\HttpFoundation\Response('Internal server error', 500);
        }
    
        $kernel->terminate($sfRequest, $sfResponse);
        if ($rebootKernelAfterRequest) {
            $kernel->reboot(null);
        }
    
        return new React\Http\Response(
            $sfResponse->getStatusCode(),
            $sfResponse->headers->all(),
            $sfResponse->getContent()
        );
    });
    
    $server->on('error', function (\Exception $e) use ($logger) {
        $logger->error('Internal server error', ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
    });
    
    $socket = new React\Socket\Server('tcp://0.0.0.0:9000', $loop);
    $server->listen($socket);
    
    $logger->info('Server running', ['addr' => 'tcp://0.0.0.0:9000']);
    
    $loop->run();

    Команда для запуска приложения с docker-compose:


    cd docker/react-php && docker-compose up -d --scale php=2

    Road Runner


    Web-сервер и менеджер PHP-процессов. Написан на Golang.


    Плюсы:


    • можно написать воркер, который будет инициализировать переменные только один раз и дальше работать с ними.

    Минусы:


    • необходимо написать код для воркера;
    • необходимо следить за памятью.

    Если использовать для воркера флаг --reboot-kernel-after-request, то Symfony Kernel будет инициализироваться заново на каждый запрос. При таком подходе не нужно следить за памятью.


    Код воркера
    #!/usr/bin/env php
    
    <?php
    
    use App\Kernel;
    use Spiral\Goridge\SocketRelay;
    use Spiral\RoadRunner\PSR7Client;
    use Spiral\RoadRunner\Worker;
    use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
    use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
    use Symfony\Component\Debug\Debug;
    use Symfony\Component\HttpFoundation\Request;
    
    require __DIR__ . '/../config/bootstrap.php';
    
    $env   = $_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? 'dev';
    $debug = (bool)($_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? ('prod' !== $env));
    
    if ($debug) {
        umask(0000);
    
        Debug::enable();
    }
    
    if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? $_ENV['TRUSTED_PROXIES'] ?? false) {
        Request::setTrustedProxies(explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST);
    }
    
    if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? $_ENV['TRUSTED_HOSTS'] ?? false) {
        Request::setTrustedHosts(explode(',', $trustedHosts));
    }
    
    $kernel = new Kernel($env, $debug);
    $kernel->boot();
    $rebootKernelAfterRequest = in_array('--reboot-kernel-after-request', $argv);
    $relay                    = new SocketRelay('/tmp/road-runner.sock', null, SocketRelay::SOCK_UNIX);
    $psr7                     = new PSR7Client(new Worker($relay));
    $httpFoundationFactory    = new HttpFoundationFactory();
    $diactorosFactory         = new DiactorosFactory();
    
    while ($req = $psr7->acceptRequest()) {
        try {
            $request  = $httpFoundationFactory->createRequest($req);
            $response = $kernel->handle($request);
            $psr7->respond($diactorosFactory->createResponse($response));
            $kernel->terminate($request, $response);
            if($rebootKernelAfterRequest) {
                $kernel->reboot(null);
            }
        } catch (\Throwable $e) {
            $psr7->getWorker()->error((string)$e);
        }
    }

    Команда для запуска приложения с docker-compose:


    cd docker/road-runner && docker-compose up -d

    Тестирование


    Тестирование производилось с помощью Yandex Tank.
    Приложение и Yandex Tank были на разных виртуальных серверах.


    Характеристики виртуального сервера с приложением:
    Virtualization: KVM
    CPU: 2 cores
    RAM: 4096 МБ
    SSD: 50 GB
    Connection: 100MBit
    OS: CentOS 7 (64x)


    Тестируемые сервисы:


    • php-fpm
    • php-ppm
    • nginx-unit
    • road-runner
    • road-runner-reboot (c флагом --reboot-kernel-after-request)
    • react-php
    • react-php-reboot (c флагом --reboot-kernel-after-request)

    Для тестов 1000/1000 rps добавлен сервис php-fpm-80
    Для него использовалась конфигурация php-fpm:


    pm = dynamic
    pm.max_children = 80

    Yandex Tank заранее определяет, сколько раз ему нужно выстрелить в цель, и не останавливается, пока не кончатся патроны. В зависимости от скорости ответа сервиса время теста может быть больше, чем задано в конфигурации тестов. Из-за этого графики разных сервисов могут иметь разную длину. Чем медленнее отвечает сервис, тем длиннее будет его график.


    Для каждого сервиса и конфигурации Yandex Tank проводился всего один тест. Из-за этого цифры могут быть неточными. Важно было оценить характеристики сервисов относительно друг друга.


    100 rps


    Конфигурация phantom Yandex Tank


    phantom:
        load_profile:
            load_type: rps
            schedule: line(1, 100, 60s) const(100, 540s)

    Ссылки с детальным отчетом



    Перцентили времени ответа


    95%(ms) 90%(ms) 80%(ms) 50%(ms) HTTP OK(%) HTTP OK(count)
    php-fpm 9.9 6.3 4.35 3.59 100 57030
    php-ppm 9.4 6 3.88 3.16 100 57030
    nginx-unit 11 6.6 4.43 3.69 100 57030
    road-runner 8.1 5.1 3.53 2.92 100 57030
    road-runner-reboot 12 8.6 5.3 3.85 100 57030
    react-php 8.5 4.91 3.29 2.74 100 57030
    react-php-reboot 13 8.5 5.5 3.95 100 57030

    Мониторинг


    cpu median(%) cpu max(%) memory median(MB) memory max(MB)
    php-fpm 9.15 12.58 880.32 907.97
    php-ppm 7.08 13.68 901.72 913.80
    nginx-unit 9.56 12.54 923.02 943.90
    road-runner 5.57 8.61 992.71 1,001.46
    road-runner-reboot 9.18 12.67 848.43 870.26
    react-php 4.53 6.58 1,004.68 1,009.91
    react-php-reboot 9.61 12.67 885.92 892.52

    Графики



    График 1.1 Среднее время ответа в секунду



    График 1.2 Средняя нагрузка процессора в секунду



    График 1.3 Среднее потребление памяти в секунду


    500 rps


    Конфигурация phantom Yandex Tank


    phantom:
        load_profile:
            load_type: rps
            schedule: line(1, 500, 60s) const(500, 540s)

    Ссылки с детальным отчетом



    Перцентили времени ответа


    95%(ms) 90%(ms) 80%(ms) 50%(ms) HTTP OK(%) HTTP OK(count)
    php-fpm 13 8.4 5.3 3.69 100 285030
    php-ppm 15 9 4.72 3.24 100 285030
    nginx-unit 12 8 5.5 3.93 100 285030
    road-runner 9.6 6 3.71 2.83 100 285030
    road-runner-reboot 14 11 7.1 4.45 100 285030
    react-php 9.3 5.8 3.57 2.68 100 285030
    react-php-reboot 15 12 7.2 4.21 100 285030

    Мониторинг


    cpu median(%) cpu max(%) memory median(MB) memory max(MB)
    php-fpm 41.68 48.33 1,006.06 1,015.09
    php-ppm 33.90 48.90 1,046.32 1,055.00
    nginx-unit 42.13 47.92 1,006.67 1,015.73
    road-runner 24.08 28.06 1,035.86 1,044.58
    road-runner-reboot 46.23 52.04 939.63 948.08
    react-php 19.57 23.42 1,049.83 1,060.26
    react-php-reboot 41.30 47.89 957.01 958.56

    Графики



    График 2.1 Среднее время ответа в секунду



    График 2.2 Средняя нагрузка процессора в секунду



    График 2.3 Среднее потребление памяти в секунду


    1000 rps


    Конфигурация phantom Yandex Tank


    phantom:
        load_profile:
            load_type: rps
            schedule: line(1, 1000, 60s) const(1000, 60s)

    Ссылки с детальным отчетом



    Перцентили времени ответа


    95%(ms) 90%(ms) 80%(ms) 50%(ms) HTTP OK(%) HTTP OK(count)
    php-fpm 11050 11050 9040 195 80.67 72627
    php-fpm-80 3150 1375 1165 152 99.85 89895
    php-ppm 2785 2740 2685 2545 100 90030
    nginx-unit 98 80 60 21 100 90030
    road-runner 27 15 7.1 3.21 100 90030
    road-runner-reboot 1110 1100 1085 1060 100 90030
    react-php 23 13 5.6 2.86 100 90030
    react-php-reboot 28 24 19 11 100 90030

    Мониторинг


    cpu median(%) cpu max(%) memory median(MB) memory max(MB)
    php-fpm 12.66 78.25 990.16 1,006.56
    php-fpm-80 83.78 91.28 746.01 937.24
    php-ppm 66.16 91.20 1,088.74 1,102.92
    nginx-unit 78.11 88.77 1,010.15 1,062.01
    road-runner 42.93 54.23 1,010.89 1,068.48
    road-runner-reboot 77.64 85.66 976.44 1,044.05
    react-php 36.39 46.31 1,018.03 1,088.23
    react-php-reboot 72.11 81.81 911.28 961.62

    Графики



    График 3.1 Среднее время ответа в секунду



    График 3.2 Среднее время ответа в секунду (без php-fpm, php-ppm, road-runner-reboot)



    График 3.3 Средняя нагрузка процессора в секунду



    График 3.4 Среднее потребление памяти в секунду


    10000 rps


    Конфигурация phantom Yandex Tank


    phantom:
        load_profile:
            load_type: rps
            schedule: line(1, 10000, 30s) const(10000, 30s)

    Ссылки с детальным отчетом



    Перцентили времени ответа


    95%(ms) 90%(ms) 80%(ms) 50%(ms) HTTP OK(%) HTTP OK(count)
    php-fpm 11050 11050 11050 1880 70.466 317107
    php-fpm-80 3260 3140 1360 1145 99.619 448301
    php-ppm 2755 2730 2695 2605 100 450015
    nginx-unit 1020 1010 1000 980 100 450015
    road-runner 640 630 615 580 100 450015
    road-runner-reboot 1130 1120 1110 1085 100 450015
    react-php 1890 1090 1045 58 99.996 449996
    react-php-reboot 3480 3070 1255 91 99.72 448753

    Мониторинг


    cpu median(%) cpu max(%) memory median(MB) memory max(MB)
    php-fpm 5.57 79.35 984.47 998.78
    php-fpm-80 85.05 92.19 936.64 943.93
    php-ppm 66.86 82.41 1,089.31 1,097.41
    nginx-unit 86.14 93.94 1,067.71 1,069.52
    road-runner 73.41 82.72 1,129.48 1,134.00
    road-runner-reboot 80.32 86.29 982.69 984.80
    react-php 73.76 82.18 1,101.71 1,105.06
    react-php-reboot 85.77 91.92 975.85 978.42


    График 4.1 Среднее время ответа в секунду



    График 4.2 Среднее время ответа в секунду (без php-fpm, php-ppm)



    График 4.3 Средняя нагрузка процессора в секунду



    График 4.4 Среднее потребление памяти в секунду


    Итоги


    Здесь собраны графики, отображающие изменение характеристик сервисов в зависимости от нагрузки. При просмотре графиков стоит учитывать, что не все сервисы ответили на 100% запросов.



    График 5.1 95% перцентиль времени ответа



    График 5.2 95% перцентиль времени ответа (без php-fpm)



    График 5.3 Максимальная нагрузка процессора



    График 5.4 Максимальное потребление памяти


    Оптимальным решением (без изменения кода), на мой взгляд, является менеджер процессов Nginx Unit. Он показывает хорошие результаты в скорости ответа и имеет поддержку компании.


    В любом случае подход к разработке и инструменты нужно выбирать индивидуально в зависимости от ваших нагрузок, ресурсов сервера и возможностей разработчиков.


    UPD
    Для тестов 1000/1000 rps добавлен сервис php-fpm-80
    Для него использовалась конфигурация php-fpm:


    pm = dynamic
    pm.max_children = 80
    Поделиться публикацией

    Комментарии 45

      0
      del.
        +2
        небольшой оффтоп:

        Тот же reactphp используется дай бог на треть возможностей. Мало просто всё запихнуть в event loop, надо ещё гарантировать, что этот самый loop ничего не блокирует.
        Именно поэтому он абсолютно не годится для всяких фреймворков. Воткнуть можно, но вреда будет больше, чем пользы (ну только в hello world'ах всё красиво)
          0

          Смотря как этот react готовить. Тот же php-pm отлично работает.

            +2
            Отлично — понятие довольно расплывчатое.
            У того же php-pm под капотом будут ровно те же проблемы с блокирующим io. Ну т.е. мы не получаем эффекта от кооперативной многозадачности. Только минус к бутстрапингу.

            Но, да, в случае с php-pm не будет одного из ключевых минусов reactphp с заблокированным лупом. Главным образом за счёт воркеров
          +1
          Выводов можно было сделать больше. Вроде: прощай php-fpm.
            0
            Ну прощаться, конечно, рановато еще. Но начало заката — да, всё отчетливее
              0
              PHP так или иначе будет развиваться в сторону демонов. Вопрос лишь в том, какой ценой.
              Если взять в рассчёт не простое echo, а +\- вменяемое приложение, то там есть ещё запас (php-pm и road runner не выгребают всё). На сколько разумно его использовать, глядя на «конкурентов» — хз, ведь это потребует очень большого разворота в головах php программистов.
              Наверное, проще всё же взять какой-нибудь kotlin и не страдать.
              +3

              до выхода php 8 с предполагаемой искоробочной асинхронностью основной менеджер процессов таки php-fpm. остальное — частные случаи для небольших приложений, где таки реально уследить за памятью и блокировками.

                +1
                Вы перед тем, как выводы делать, посмотрели бы лучше на конфиг php-fpm.
                Он кривой наглухо, результат с таким конфигом не удивителен.
                +3
                На моей практике Swoole работает в 3-4 раза быстрее React PHP (увы, только их и сравнивал), кушая примерно на 1/4 меньше оперативки. Очень жаль, что его нет в бенчмарках, но подборка действительно крутая, спасибо.
                  +2
                  Да Swoole крутая штука, с корутинами и асинхронным io, было бы интересно впихнуть сравнение в этот бенчмарк.
                  Вот только мой бенчмарк FPM vs Swoole (zend-expressive hello world) показал скорость выше всего на 20% (1000RPS). Возможно это было связано с мощным i7 процом (4/8).
                    +2
                    Вот только мой бенчмарк FPM vs Swoole (zend-expressive hello world) показал скорость выше всего на 20% (1000RPS). Возможно это было связано с мощным i7 процом (4/8).


                    Блин, я очепятался, простите. Конечно же я хотел написать «не треть/на четверть», а по привычке написал «в 3-4 раза». Посыпаю голову пеплом.
                  +2
                  В статье странно указан диск
                  HDD: 50 GB SSD


                  В общем, плохие новости про php последнее время. Но в случае fpm я не вижу проблемы. Да 80% сайтов до сих пор работают на медленном апаче. Единицы сайтов хорошо оптимизированы.
                  Для них это все не грозит.
                    0
                    Спасибо, поправил опечатку.
                    +2
                    За счет чего unit такой быстрый? Чем он принципиально отличается от php-fpm?
                      –1
                      Для каждого языка, в том числе и PHP, собирается модуль для работы с nginx unit. Наверняка в таком модуле есть оптимизации и кеширование.
                      github.com/nginx/unit/blob/db631917190c44b3b55a15e4e5e88aa92e6b5334/src/nxt_php_sapi.c
                        0
                        У него другая архитектура. Я рассказывал об этом в докладе на БИФ2018: yadi.sk/i/yczMbKccJ7Ujkg (где-то с 16 минуты).
                          0
                          Подскажите, у юнита есть url для получения статистики? Количество процессов, запросов, длинна очереди?
                            0
                            Сейчас нет. API со статистикой в планах.
                            0
                            По просьбам зрителей ссылка на слайды: pp.nginx.com/vbart/slides/BIF2018.pdf
                          0
                          А какой хостинг/план? Хочу погонять кое что точно на таком же железе
                            0
                            Скажите, а в настройках PHP-FPM (www.conf) была проведена оптимизация этих параметров:

                            pm.max_children
                            pm.start_servers
                            pm.min_spare_servers
                            pm.max_spare_servers

                            А то чувствуется, что они остались по-умолчанию установленными…
                              +1
                              Все конфиги лежат в репо, так что вы можете сами их посмотреть для каждого сервиса.
                              Конкретно для php-fpm конфигурация тут:
                              github.com/mrsuh/php-load-test/blob/master/docker/php-fpm/php-fpm/php-fpm.conf
                                +3
                                max_children=2…
                                  0
                                  все верно, как написано в статье:

                                  Обработка запросов ограничивается двумя инстансами приложения (по числу ядер процессора).

                                    +5
                                    Вот только max_children никогда не ставят равным количеству ядер. Кроме самой обработки запроса есть еще io операции, во время которых процессор простаивает. В это время могли обрабатываться другие воркеры. Первая ссылка из гугла по настройке hcbogdan.com/php/2016/09/16/php-fpm-dynamic
                                      0
                                      Хотелось сделать условия для всех более менее одинаковые, поэтому такие настройки. В других сервисах тоже по 2 воркера.
                                        +3
                                        Условия — утилизировать железо, fpm не утилизировал. Разные подходы — разные настройки
                                          +2
                                          Условия — утилизировать железо, fpm не утилизировал. Разные подходы — разные настройки

                                          Поддерживаю. Бенчмарк, имеющий условие, что какая-то настройка (у всех значащая разное) равна двум, не имеет смысла.


                                          mrsuh может быть добавите правильно настроенный PHP-FPM к результатам?

                                            +1
                                            Добавил сервис php-fpm-80 c pm.max_children = 80
                                              0
                                              ну а графики все построены со старыми настройками?
                                                0
                                                Заново построены все графики, где в легенде присутствует сервис php-fpm-80
                                                Графики: 3.1, 3.3, 3.4, 4.1, 4.2, 4.3, 4.4
                                                  0
                                                  Да, где-то есть 80 где-то нет — понял, спасибо.
                                                  Идея теста и исполнение интересные, но вообще, похоже, вы померили менеджеры процессов со слишком простым кодом приложения, и скорее всего бОльшую часть процессорного времени в вашем тесте занимает работа менеджера, а не кода приложения. В то время как практическую ценность имел бы тест: как менеджер процессов ускоряет приложение, которое само по себе в режиме php-fpm ожирает хотя бы 0.05 процессорных секунд (не за счет моделей обработки соединений, а за счёт экономиях на инициализациях, например). Но здесь наверное основная проблема в том, что «правильный» тест нельзя сделать одним и тем же кодом.
                                                    0
                                                    >вы померили менеджеры процессов со слишком простым кодом приложения

                                                    Так и есть. Я как раз на днях тоже заценил, что простое приложение с парой запросов к бд и рендерингом шаблона не выдает те тысячи rps, которые декларируют авторы фреймворков
                                                +1
                                                Мало будет один параметр поправить, нужно все зависимые тоже править:

                                                pm.max_children — максимальное количество дочерних процессов
                                                pm.start_servers — количество процессов при старте
                                                pm.min_spare_servers — минимальное количество процессов, ожидающих соединения (запросов для обработки)
                                                pm.max_spare_servers — максимальное количество процессов, ожидающих соединения (запросов для обработки)


                                                В этой статье есть пример как это сделать: hcbogdan.com/php/2016/09/16/php-fpm-dynamic
                                        +1
                                        Это совсем не правильно! В вашем случае должно быть 80!

                                        pm.max_children — максимальное количество дочерних процессов
                                        pm.start_servers — количество процессов при старте
                                        pm.min_spare_servers — минимальное количество процессов, ожидающих соединения (запросов для обработки)
                                        pm.max_spare_servers — максимальное количество процессов, ожидающих соединения (запросов для обработки)


                                        Это же видно как на 1000 rps начал захлебываться — 72627
                                  –1

                                  Спасибо, очень грамотный бенчмарк.


                                  Будем изучать аномалию с rr-reboot выше 1000rps, в теории поведение должно быть такое-же как и reactphp-reboot.

                                    +1
                                    Коллеги, не слова об ondemand, а только про dynamic. Почему?
                                    FPM пока держит рынок, но закат близко.
                                      +2
                                      мой опыт говорит что при пиковых нагрузках никаких динамических пре-форков, хоть динамик хоть он-деманд

                                      нафоркать столько воркеров сколько тянет машина и с ними жить — иначе при резком скачке нагрузки все помрет
                                        0
                                        Абсолютно верный опыт.

                                        Таким образом можно и метрики производительности иметь на единицу деплоя.
                                        И знать сколько ресурсов потребуется.
                                        И настроить алертинг на количество запросов/занятых воркеров, чтобы получить сигналы о том, что нужно подкинуть дровишек (железа, виртуалок, подов).
                                      –2
                                      Чтобы тестировать производительность на единичных запусках на единичных виртуальных серваках надо быть очень большим оптимистом.
                                        –1
                                        Если интересно, недавно запилил собственный бенчмарк с PHP-FPM, Apache mod_php и Nginx Unit. Спойлер: результаты отличаются от этой статьи.
                                        Видео бенчмарка
                                        0
                                        net.core.somaxconn какое значение имеет, стандартное 128? для php-fpm это важно в общем-то, да и нестандартный ulimit я только у nginx увидел.
                                          0
                                          sysctl.conf и limits.conf для тестов были поправлены
                                          в частности:
                                          net.core.somaxconn = 2048

                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                        Самое читаемое