Классический PHP-FPM запускает интерпретатор заново на каждый запрос: поднял приложение, обработал один запрос, выгрузил всё из памяти. Swoole и RoadRunner работают иначе — приложение загружается один раз и держится в памяти между запросами, как в Node.js или Go. Это ускоряет обработку в разы, но требует другого подхода к коду.
Чем это отличается от привычного PHP-FPM
В модели FPM каждый запрос получает чистое состояние: никаких утечек между пользователями, но каждый раз заново инициализируется контейнер зависимостей, подключение к базе и автозагрузка классов. В модели Swoole или RoadRunner инициализация происходит один раз при старте воркера, а дальше на каждый запрос выполняется только код обработчика — это и даёт основной прирост скорости. Разницу с обычным CLI-запуском PHP разбирали в статье про PHP CLI и FPM.
Обратная сторона — глобальное состояние. Статические свойства классов, синглтоны и просто забытые переменные в замыканиях живут между запросами и могут утекать в память или протекать данными одного пользователя к другому, если код не написан с расчётом на долгий процесс.
Установка Swoole как PHP-расширения
sudo apt install php8.3-dev php-pear
sudo pecl install swoole
echo "extension=swoole.so" | sudo tee /etc/php/8.3/mods-available/swoole.ini
sudo phpenmod swoole
php -m | grep swoole
Swoole встраивается прямо в PHP как расширение и добавляет классы для асинхронного HTTP-сервера, корутин и вебсокетов. Про отдельный протокол вебсокетов на чистом PHP — в статье про WebSockets с Ratchet.
RoadRunner как альтернатива
RoadRunner устроен иначе: это отдельный бинарник на Go, который держит
пул PHP-воркеров и общается с ними по протоколу Goridge через stdin/stdout.
PHP-код при этом остаётся обычным PHP без специального расширения — нужен
только пакет spiral/roadrunner-http через Composer, который
описан в статье про
установку Composer.
| Параметр | Swoole | RoadRunner |
|---|---|---|
| Тип | расширение PHP | отдельный бинарник на Go |
| Корутины | да, встроены | нет, воркеры однопоточные |
| Совместимость с Laravel/Symfony | через отдельные пакеты | через отдельные пакеты |
| Порог входа | выше | ниже |
На что смотреть при утечках памяти
Долгоживущий процесс делает утечки заметными за минуты, а не за недели, как в обычных десктопных приложениях. Проверьте эти места:
- Статические массивы и кэши уровня класса, которые растут с каждым запросом и никогда не очищаются.
- Подключение к базе данных, которое переоткрывается на каждый запрос вместо переиспользования уже открытого соединения.
- Глобальный контейнер зависимостей — если он создаётся заново на каждый запрос, то теряется весь смысл долгоживущего процесса.
- Циклические ссылки между объектами, которые сборщик мусора PHP не всегда убирает вовремя в длинном процессе.
Мониторинг воркеров и перезапуск
Даже правильно написанное приложение стоит перезапускать по расписанию,
как и обычные очереди — подход почти такой же, как в статье
про Supervisor для очередей.
У Swoole есть встроенный параметр max_request, который
перезапускает воркер после заданного числа обработанных запросов:
$server->set([
'worker_num' => 4,
'max_request' => 5000,
'task_worker_num' => 2,
]);
Чек-лист перехода на долгоживущий процесс
- Уберите из кода любые статические свойства, которые накапливают данные между запросами без явной очистки.
- Настройте плановый перезапуск воркеров через
max_requestв Swoole или аналогичный лимит в RoadRunner. - Прогоните нагрузочное тестирование перед переключением прода — рост задержек по времени работы воркера сразу покажет утечку.