Тестирование производилось с помощью 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

Комментарии (40)


  1. kricha
    14.01.2019 22:48

    del.


  1. mmasiukevich
    14.01.2019 23:08
    +1

    небольшой оффтоп:

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


    1. greabock
      14.01.2019 23:32

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


      1. mmasiukevich
        14.01.2019 23:40
        +1

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

        Но, да, в случае с php-pm не будет одного из ключевых минусов reactphp с заблокированным лупом. Главным образом за счёт воркеров


  1. Arbane
    15.01.2019 00:07

    Выводов можно было сделать больше. Вроде: прощай php-fpm.


    1. mikevmk
      15.01.2019 02:01

      Ну прощаться, конечно, рановато еще. Но начало заката — да, всё отчетливее


      1. mmasiukevich
        15.01.2019 02:09

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


    1. Gemorroj
      15.01.2019 11:58

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


    1. OnYourLips
      15.01.2019 22:00
      +1

      Вы перед тем, как выводы делать, посмотрели бы лучше на конфиг php-fpm.
      Он кривой наглухо, результат с таким конфигом не удивителен.


  1. SerafimArts
    15.01.2019 03:37

    На моей практике Swoole работает в 3-4 раза быстрее React PHP (увы, только их и сравнивал), кушая примерно на 1/4 меньше оперативки. Очень жаль, что его нет в бенчмарках, но подборка действительно крутая, спасибо.


    1. shandy
      15.01.2019 09:37

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


      1. SerafimArts
        15.01.2019 15:43

        Вот только мой бенчмарк FPM vs Swoole (zend-expressive hello world) показал скорость выше всего на 20% (1000RPS). Возможно это было связано с мощным i7 процом (4/8).


        Блин, я очепятался, простите. Конечно же я хотел написать «не треть/на четверть», а по привычке написал «в 3-4 раза». Посыпаю голову пеплом.


  1. iproger
    15.01.2019 04:24

    В статье странно указан диск

    HDD: 50 GB SSD


    В общем, плохие новости про php последнее время. Но в случае fpm я не вижу проблемы. Да 80% сайтов до сих пор работают на медленном апаче. Единицы сайтов хорошо оптимизированы.
    Для них это все не грозит.


    1. mrsuh
      15.01.2019 09:26

      Спасибо, поправил опечатку.


  1. e45456
    15.01.2019 09:26
    +1

    За счет чего unit такой быстрый? Чем он принципиально отличается от php-fpm?


    1. mrsuh
      15.01.2019 09:28
      -1

      Для каждого языка, в том числе и PHP, собирается модуль для работы с nginx unit. Наверняка в таком модуле есть оптимизации и кеширование.
      github.com/nginx/unit/blob/db631917190c44b3b55a15e4e5e88aa92e6b5334/src/nxt_php_sapi.c


    1. VBart
      15.01.2019 17:18

      У него другая архитектура. Я рассказывал об этом в докладе на БИФ2018: yadi.sk/i/yczMbKccJ7Ujkg (где-то с 16 минуты).


      1. e45456
        15.01.2019 17:40

        Подскажите, у юнита есть url для получения статистики? Количество процессов, запросов, длинна очереди?


        1. VBart
          15.01.2019 17:45

          Сейчас нет. API со статистикой в планах.


  1. quantum
    15.01.2019 11:01

    А какой хостинг/план? Хочу погонять кое что точно на таком же железе


  1. igormwd
    15.01.2019 11:03

    Скажите, а в настройках PHP-FPM (www.conf) была проведена оптимизация этих параметров:

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

    А то чувствуется, что они остались по-умолчанию установленными…


    1. mrsuh
      15.01.2019 11:05
      +1

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


      1. quantum
        15.01.2019 11:13

        max_children=2…


        1. mrsuh Автор
          15.01.2019 11:20

          все верно, как написано в статье:

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


          1. quantum
            15.01.2019 11:27

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


            1. mrsuh Автор
              15.01.2019 11:29

              Хотелось сделать условия для всех более менее одинаковые, поэтому такие настройки. В других сервисах тоже по 2 воркера.


              1. quantum
                15.01.2019 11:31

                Условия — утилизировать железо, fpm не утилизировал. Разные подходы — разные настройки


                1. pmurzakov
                  15.01.2019 14:15

                  Условия — утилизировать железо, fpm не утилизировал. Разные подходы — разные настройки

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


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


                  1. mrsuh Автор
                    15.01.2019 15:45

                    Добавил сервис php-fpm-80 c pm.max_children = 80


                    1. fisher
                      15.01.2019 16:41

                      ну а графики все построены со старыми настройками?


                      1. mrsuh Автор
                        15.01.2019 16:47

                        Заново построены все графики, где в легенде присутствует сервис php-fpm-80
                        Графики: 3.1, 3.3, 3.4, 4.1, 4.2, 4.3, 4.4


                        1. fisher
                          15.01.2019 17:01

                          Да, где-то есть 80 где-то нет — понял, спасибо.
                          Идея теста и исполнение интересные, но вообще, похоже, вы померили менеджеры процессов со слишком простым кодом приложения, и скорее всего бОльшую часть процессорного времени в вашем тесте занимает работа менеджера, а не кода приложения. В то время как практическую ценность имел бы тест: как менеджер процессов ускоряет приложение, которое само по себе в режиме php-fpm ожирает хотя бы 0.05 процессорных секунд (не за счет моделей обработки соединений, а за счёт экономиях на инициализациях, например). Но здесь наверное основная проблема в том, что «правильный» тест нельзя сделать одним и тем же кодом.


                          1. quantum
                            15.01.2019 17:45

                            >вы померили менеджеры процессов со слишком простым кодом приложения

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


                    1. igormwd
                      16.01.2019 09:20

                      Мало будет один параметр поправить, нужно все зависимые тоже править:

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


                      В этой статье есть пример как это сделать: hcbogdan.com/php/2016/09/16/php-fpm-dynamic


          1. igormwd
            15.01.2019 13:26

            Это совсем не правильно! В вашем случае должно быть 80!

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


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


  1. Lachezis
    15.01.2019 11:15

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


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


  1. pupsegadm
    15.01.2019 19:11

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


    1. sirmax123
      16.01.2019 12:00

      мой опыт говорит что при пиковых нагрузках никаких динамических пре-форков, хоть динамик хоть он-деманд

      нафоркать столько воркеров сколько тянет машина и с ними жить — иначе при резком скачке нагрузки все помрет


  1. Berkof
    16.01.2019 07:39
    -2

    Чтобы тестировать производительность на единичных запусках на единичных виртуальных серваках надо быть очень большим оптимистом.


  1. Nickmob
    16.01.2019 09:46

    Если интересно, недавно запилил собственный бенчмарк с PHP-FPM, Apache mod_php и Nginx Unit. Спойлер: результаты отличаются от этой статьи.
    Видео бенчмарка