Как стать автором
Обновить

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

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

есть некоторые сомнения насчет "не сильно", как и во всех хеллоуворлд-бенчмарках

Спасибо, жаль, что также не добавили "голый" php-fpm для сравнения. Вот ссылка на подобное исследование для symfony, если кому интересно.

PHP-FPM медленнее работает, не стал его втягивать в эту передрягу. Но если интересно, в октябре прошлого года в другой среде тестировал и не на чистом проект. Результат был таким:

Октан был на RoadRunner.

Интересно как раз насколько медленнее, спасибо. cpu/memory usage не сравнивали?

Через htop не смотрел, не видел особого смысла, а у хостинга единый пик в статистике отображён:

странные показатели, на живом проекте fpm давал на однотипные запросы (мускуль, редис, ничего сложного) порядка 200мс на запрос, тогда как rr/franken те же запросы выполняли за 50мс

Странные показатели. Пустой эндпоинт на octane+swoole отрабатывает за 2мс. C RR не работаю, один раз тестировал - цифра была похожая.

И да, пустой эндпоинт может работать на скорости 1к рпс.

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

Стоит ещё проверить параметры хостинга, а точнее точно ли вам выделено 100% CPU.

Чтобы сравнение было честным, стоит наверное явно задать количество потоков сервера.
А чтобы оно было не абстрактными попугаями, а близким к реальности - включать opcache для cli.

Ну и в идеале, конечно, результаты тестирования лучше смотреть в динамике. Итоговая сводка в 80мс в теории может складываться как среднее из 10мс и 900мс. Тут как минимум нужно указывать min, max, avg, а лучше графики.

И нет. При наличии логики поведение будет отличаться очень сильно. Типичная для php сайтов io-bound нагрузка легко может увести разницу между обсуждаемыми технологиями в разряд погрешности, ато и вовсе "сравнять" их по применимости с php-fpm.

Уже год использую RR. Встал вопрос о переходе с FPM, и сначала я выбрал FrankenPHP. Октан выполняет большую часть работы по адаптации PHP-сервера к приложению, но не обошлось без проблем. Все основные проблемы возникали с статичными классами, значения которых передавались от сессии к сессии. Например, таймзона клиента хранилась в статичных свойствах, и если один клиент менял таймзону в рамках одного воркера, она изменялась и для другого клиента. Я откатил обратно на FPM и начал более подробно изучать тему.

После детального изучения, что именно нужно доработать, я решил остановиться на RR. Основные причины:

  1. Он всё ещё невероятно быстрый по сравнению с FPM.

  2. RR — это не только PHP-сервер, но и, например, сервер очередей, кешей и других полезных вещей.

  3. Интеграция с Temporal можно отнести ко второму пункту, но я бы выделил её отдельно. Для тех, кто работает с глобальными workflow или с Temporal в частности, RR из коробки поддерживает поднятие Temporal-сервера на PHP.

Ну и не стоит забывать, что RR делают те же ребята, которые разрабатывают Spiral и другие проекты. Активно поддерживающие PHP-сообщество ну и говорят по-русски. Это даёт им несколько дополнительных очков форы )

При использовании Swoole, вы прогревали endpoint? Swoole при старте загружает приложение в себя и "висит" некоторые время, что негативно сказывается на бенчмарках. Для сферического теста в вакууме этим можно пренебречь, в реальности я бы этот фактор учел.
У вас на графиках отражена загрузка приложения тем, что перфомансы в начальный момент времени хуже у Swoole, а со временем улучшаются


P.S одна из причина по которой лучше использовать OpenSwoole

Прогревом октан занимается при первом запросе к нему. Я проверял с точки зрения простейшего использования по принципу "установил зависимость - запустил фрейм - протестировал". Никаких улучшений и тонких настроек. Только дефолт. Только хардкор.

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

С хардкорами и дефолтами нет ничего общего.

В бенчах RR-Swoole-Franken обычно все допускают одни и те же ошибки в настройках RR:
- не выключают логи и тестирую пропускную способность stdout.
- не устанавливают protobuf и теряют очень много на (де)сериализации протобафов. При тестировании пустых эндпоинтов это существенно.
- не тестируют разные важные опции типа кол-ва воркеров (относится ко всем серверам)

Несмотря на то, что Swoole в HTTP будет всегда объективно быстрее других (если только RR или Франкен не перепишут с go на что-то без интеропа), всё-таки хотелось бы видеть какую-то объективность в цифрах.

Короче, рекомендую читать документацию к серверам, чтобы устанавливать и настраивать их правильно, а не полагаться на дефолты Octane.

Я бы вам "лайк" влепил, да у меня кармы нет ^_^
Полностью согласен.

Интересно бы посмотреть тест с прогретыми соединениями к БД, и какой-нить мессадж брокер. **От ФПМ отрыв будет однозначно больше.
Автору спасибо за статью :pray:

Один из моих проектов через FPM отдавал данные примерно за 300-350 мс. С Октаном на RoadRunner возвращает примерно за 90-100мс.

Выглядит так, будто бы включен opcache.revalidate_path, или же вообще отсутствует opcache.
Натравил вот ab -n1000 -c16 на свой прод, полноценный проц, в котором и БД, и кэши, и рендеринг, а не хэлловорлд-стаб. В связке nginx+nginx+php-fpm (8.3), и вот что-то как-то не верится в то, что php-fpm может давать вот так вот сходу +200ms латенси.

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        5    9   3.7      8      34
Processing:    40   63   8.5     63     119
Waiting:       30   40   5.6     41      64
Total:         46   72  10.7     71     152

А у прода какая конфигурация? Само железо?

Вдобавок, мои цифры указаны с расчётом клиентского запроса, то есть с компьютера через DNS и провайдеров в другую страну к серверу. Это честный запрос, т.к. мной рассматривается скорость отклика от сайта, а не производительность сервера.

Касательно производительности самого сервера, то при дефолтных настройках ab тест на RoadRunner показывал около 145 rps, а FrankenPHP около 250 rps. PHP-Fpm при этом не замерял.

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

Всё в виртуалках, поверх Silver 4216 (8CPU+8RAM), без лимитирования. Ко всему прочему, скрипты лежат на NFS-сторадже, а не в каком-нибудь локальном NVMe.

Вдобавок, мои цифры указаны с расчётом клиентского запроса
Так вы меряете скорость своего роутера, своего впна, коммутаторов провайдера, днс серверов и ещё черт знает каких железок. С такой экспертизой можно скатиться до бенчмарков в браузерах.

И выше верно замечено было, что надо тестировать не хэлловорлд, а хоть какую-то реальную логику, а всё потому что, если при 10ms и при 100ms на запрос картина может оказаться совершенно не такой, как могло бы показаться.

Ну и при бенчмаркингах принято увеличивать конкурентность, а не время теста. Время теста вообще ни о чем не говорит. Особенно если оно живёт при c=10, но сдыхает при c=20...

Ну вот. А у меня VPS c двумя ядрами и 4 RAM... И файлы в нём же на NVMe локальном.

какие-то дохлые цифры даже для 2 ядер. Я сам использую часто OpenSwoole и RoadRunner как с Symfony, так и с Laravel. Пустой контроллер типа health-check на Symfony под OpenSwoole и RoadRunner выдает 8k RPS на 8 ядрах (ограничение в Docker, физический процессор - Intel 13900). Без фреймворка чистый RoadRunner показывает примерно те же цифры.

Вполне возможно что и дохлые. у Вас 8к RPS в каких условиях выполнялись? С самого сервера на себя же? Или комп запроса с сервером в разных странах? И сервер на VPS с двумя защитами от дудоса - один от CloudFlare, а вторая от хостинга?

Спасибо за статью, сможете ли вы выложить код проектов в репозиторий? Хочется увидеть все настройки в том числе .rr файл.

Кода там нет никакого кроме роута, который я выложил. Конфиг роадраннера тоже дефолтный и составляется самим Laravel Octane в момент его запуска. Руками в файл нельзя лазить иначе его можно запросто сломать и он не будет запускаться.

Да и урл создавал чисто для проверки, чтобы не на главную ломиться где сессии работают.

По сути, всё что Вам нужно сделать для запуска нового проекта под roadrunner, это выполнить эти команды:

laravel new test && cd test
composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http
php vendor/bin/rr get-binary

И, либо в супервизор конфигурацию воткнуть, либо руками локально запустить:

php artisan octane:start

Содержимое файла .rr.yaml Laravel Octane формирует самостоятельно, но при запущенном инстансе он выглядит так (копипаст с прода):

Показать содержимое .rr.yaml
version: '3'
rpc:
    listen: 'tcp://127.0.0.1:6001'
server:
    command: 'php app.php'
    relay: pipes
http:
    address: '0.0.0.0:8080'
    middleware:
        - gzip
        - static
    static:
        dir: public
        forbid:
            - .php
            - .htaccess
    pool:
        num_workers: 1
        supervisor:
            max_worker_memory: 100
jobs:
    pool:
        num_workers: 2
        max_worker_memory: 100
    consume: {  }
kv:
    local:
        driver: memory
        config:
            interval: 60
metrics:
    address: '127.0.0.1:2112'

Также при старте создаётся ещё один файл - storage/logs/octane-server-state.json со следующим содержимым:

Показать содержимое octane-server-state.json
{
    "masterProcessId": 808,
    "state": {
        "appName": "Test App",
        "host": "127.0.0.1",
        "port": "9000",
        "rpcPort": "6001",
        "workers": 0,
        "maxRequests": "500",
        "octaneConfig": {
            "server": "roadrunner",
            "https": false,
            "listeners": {
                "Laravel\\Octane\\Events\\WorkerStarting": [
                    "Laravel\\Octane\\Listeners\\EnsureUploadedFilesAreValid",
                    "Laravel\\Octane\\Listeners\\EnsureUploadedFilesCanBeMoved"
                ],
                "Laravel\\Octane\\Events\\RequestReceived": [
                    "Laravel\\Octane\\Listeners\\CreateConfigurationSandbox",
                    "Laravel\\Octane\\Listeners\\CreateUrlGeneratorSandbox",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToAuthorizationGate",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToBroadcastManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToDatabaseManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToDatabaseSessionHandler",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToFilesystemManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToHttpKernel",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToLogManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToMailManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToNotificationChannelManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToPipelineHub",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToCacheManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToSessionManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToQueueManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToRouter",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToValidationFactory",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToViewFactory",
                    "Laravel\\Octane\\Listeners\\FlushDatabaseRecordModificationState",
                    "Laravel\\Octane\\Listeners\\FlushDatabaseQueryLog",
                    "Laravel\\Octane\\Listeners\\RefreshQueryDurationHandling",
                    "Laravel\\Octane\\Listeners\\FlushLogContext",
                    "Laravel\\Octane\\Listeners\\FlushArrayCache",
                    "Laravel\\Octane\\Listeners\\FlushMonologState",
                    "Laravel\\Octane\\Listeners\\FlushStrCache",
                    "Laravel\\Octane\\Listeners\\FlushTranslatorCache",
                    "Laravel\\Octane\\Listeners\\PrepareInertiaForNextOperation",
                    "Laravel\\Octane\\Listeners\\PrepareLivewireForNextOperation",
                    "Laravel\\Octane\\Listeners\\PrepareScoutForNextOperation",
                    "Laravel\\Octane\\Listeners\\PrepareSocialiteForNextOperation",
                    "Laravel\\Octane\\Listeners\\FlushLocaleState",
                    "Laravel\\Octane\\Listeners\\FlushQueuedCookies",
                    "Laravel\\Octane\\Listeners\\FlushSessionState",
                    "Laravel\\Octane\\Listeners\\FlushAuthenticationState",
                    "Laravel\\Octane\\Listeners\\EnforceRequestScheme",
                    "Laravel\\Octane\\Listeners\\EnsureRequestServerPortMatchesScheme",
                    "Laravel\\Octane\\Listeners\\GiveNewRequestInstanceToApplication",
                    "Laravel\\Octane\\Listeners\\GiveNewRequestInstanceToPaginator"
                ],
                "Laravel\\Octane\\Events\\RequestHandled": [],
                "Laravel\\Octane\\Events\\RequestTerminated": [],
                "Laravel\\Octane\\Events\\TaskReceived": [
                    "Laravel\\Octane\\Listeners\\CreateConfigurationSandbox",
                    "Laravel\\Octane\\Listeners\\CreateUrlGeneratorSandbox",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToAuthorizationGate",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToBroadcastManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToDatabaseManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToDatabaseSessionHandler",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToFilesystemManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToHttpKernel",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToLogManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToMailManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToNotificationChannelManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToPipelineHub",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToCacheManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToSessionManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToQueueManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToRouter",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToValidationFactory",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToViewFactory",
                    "Laravel\\Octane\\Listeners\\FlushDatabaseRecordModificationState",
                    "Laravel\\Octane\\Listeners\\FlushDatabaseQueryLog",
                    "Laravel\\Octane\\Listeners\\RefreshQueryDurationHandling",
                    "Laravel\\Octane\\Listeners\\FlushLogContext",
                    "Laravel\\Octane\\Listeners\\FlushArrayCache",
                    "Laravel\\Octane\\Listeners\\FlushMonologState",
                    "Laravel\\Octane\\Listeners\\FlushStrCache",
                    "Laravel\\Octane\\Listeners\\FlushTranslatorCache",
                    "Laravel\\Octane\\Listeners\\PrepareInertiaForNextOperation",
                    "Laravel\\Octane\\Listeners\\PrepareLivewireForNextOperation",
                    "Laravel\\Octane\\Listeners\\PrepareScoutForNextOperation",
                    "Laravel\\Octane\\Listeners\\PrepareSocialiteForNextOperation"
                ],
                "Laravel\\Octane\\Events\\TaskTerminated": [],
                "Laravel\\Octane\\Events\\TickReceived": [
                    "Laravel\\Octane\\Listeners\\CreateConfigurationSandbox",
                    "Laravel\\Octane\\Listeners\\CreateUrlGeneratorSandbox",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToAuthorizationGate",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToBroadcastManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToDatabaseManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToDatabaseSessionHandler",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToFilesystemManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToHttpKernel",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToLogManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToMailManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToNotificationChannelManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToPipelineHub",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToCacheManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToSessionManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToQueueManager",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToRouter",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToValidationFactory",
                    "Laravel\\Octane\\Listeners\\GiveNewApplicationInstanceToViewFactory",
                    "Laravel\\Octane\\Listeners\\FlushDatabaseRecordModificationState",
                    "Laravel\\Octane\\Listeners\\FlushDatabaseQueryLog",
                    "Laravel\\Octane\\Listeners\\RefreshQueryDurationHandling",
                    "Laravel\\Octane\\Listeners\\FlushLogContext",
                    "Laravel\\Octane\\Listeners\\FlushArrayCache",
                    "Laravel\\Octane\\Listeners\\FlushMonologState",
                    "Laravel\\Octane\\Listeners\\FlushStrCache",
                    "Laravel\\Octane\\Listeners\\FlushTranslatorCache",
                    "Laravel\\Octane\\Listeners\\PrepareInertiaForNextOperation",
                    "Laravel\\Octane\\Listeners\\PrepareLivewireForNextOperation",
                    "Laravel\\Octane\\Listeners\\PrepareScoutForNextOperation",
                    "Laravel\\Octane\\Listeners\\PrepareSocialiteForNextOperation"
                ],
                "Laravel\\Octane\\Events\\TickTerminated": [],
                "Laravel\\Octane\\Contracts\\OperationTerminated": [
                    "Laravel\\Octane\\Listeners\\FlushOnce",
                    "Laravel\\Octane\\Listeners\\FlushTemporaryContainerInstances"
                ],
                "Laravel\\Octane\\Events\\WorkerErrorOccurred": [
                    "Laravel\\Octane\\Listeners\\ReportException",
                    "Laravel\\Octane\\Listeners\\StopWorkerIfNecessary"
                ],
                "Laravel\\Octane\\Events\\WorkerStopping": [
                    "Laravel\\Octane\\Listeners\\CloseMonologHandlers"
                ]
            },
            "warm": [
                "auth",
                "cache",
                "cache.store",
                "config",
                "cookie",
                "db",
                "db.factory",
                "db.transactions",
                "encrypter",
                "files",
                "hash",
                "log",
                "router",
                "routes",
                "session",
                "session.store",
                "translator",
                "url",
                "view",
                "App\\Http\\Webhooks\\MainHandler",
                "App\\Queries\\AdministratorQuery",
                "App\\Queries\\ChatQuery",
                "App\\Queries\\CommandQuery",
                "App\\Queries\\MemberQuery",
                "App\\Queries\\MessageQuery",
                "App\\Queries\\ModuleQuery",
                "App\\Queries\\RoleQuery",
                "App\\Services\\AdministratorService",
                "App\\Services\\ChatService",
                "App\\Services\\MemberService",
                "App\\Services\\MessageService",
                "App\\Services\\ModuleService",
            ],
            "flush": [],
            "tables": {
                "example:1000": {
                    "name": "string:1000",
                    "votes": "int"
                }
            },
            "cache": {
                "rows": 1000,
                "bytes": 10000
            },
            "watch": [
                "app",
                "bootstrap",
                "config\/**\/*.php",
                "database\/**\/*.php",
                "public\/**\/*.php",
                "resources\/**\/*.php",
                "routes",
                "composer.lock",
                ".env"
            ],
            "garbage": 50,
            "max_execution_time": 30
        }
    }
}

При этом, в команде запуска октана можно указать лишь уровень логов параметром --log-level:

'-o', 'logs.mode=production',
'-o', 'logs.level='.($this->option('log-level') ?: (app()->environment('local') ? 'debug' : 'warn')),
'-o', 'logs.output=stdout',
'-o', 'logs.encoding=json',

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации