# Swoole і RoadRunner: PHP як довгоживучий процес

> Swoole і RoadRunner: чим довгоживучий PHP-процес відрізняється від FPM, як налаштувати сервер і на що дивитись при витоках пам'яті.

Класичний PHP-FPM запускає інтерпретатор заново на кожен запит: підняв застосунок, обробив один запит, вивантажив усе з пам'яті. Swoole і RoadRunner працюють інакше — застосунок завантажується один раз і тримається в пам'яті між запитами, як у Node.js або Go. Це пришвидшує обробку в рази, але вимагає іншого підходу до коду.

## Чим це відрізняється від звичного PHP-FPM

У моделі FPM кожен запит отримує чистий стан: жодних витоків між користувачами, але щоразу заново ініціалізується контейнер залежностей, з'єднання з базою і автозавантаження класів. У моделі Swoole або RoadRunner ініціалізація відбувається один раз при старті воркера, а далі на кожен запит виконується лише код обробника — це і дає основний приріст швидкості. Різницю зі звичайним CLI-запуском PHP розбирали у статті [про PHP CLI і FPM](https://www.zevshost.net/uk/kb/php-cli-vs-fpm-cron/).

Зворотний бік — глобальний стан. Статичні властивості класів, синглтони і просто забуті змінні в замиканнях живуть між запитами і можуть текти в пам'ять або протікати даними одного користувача до іншого, якщо код не написаний із розрахунком на довгий процес.

## Встановлення 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](https://www.zevshost.net/uk/kb/php-websockets-ratchet/).

## RoadRunner як альтернатива

RoadRunner влаштований інакше: це окремий бінарник на Go, який тримає пул PHP-воркерів і спілкується з ними за протоколом Goridge через stdin/stdout. PHP-код при цьому лишається звичайним PHP без спеціального розширення — потрібен лише пакет `spiral/roadrunner-http` через Composer, який описано у статті [про встановлення Composer](https://www.zevshost.net/uk/kb/composer-ustanovka-nastroika/).

| Параметр | Swoole | RoadRunner |
| --- | --- | --- |
| Тип | розширення PHP | окремий бінарник на Go |
| Корутини | так, вбудовані | ні, воркери однопотокові |
| Сумісність з Laravel/Symfony | через окремі пакети | через окремі пакети |
| Поріг входу | вищий | нижчий |

## На що дивитись при витоках пам'яті

Довгоживучий процес робить витоки помітними за хвилини, а не за тижні, як у звичайних десктопних застосунках. Перевірте ці місця:

- Статичні масиви і кеші рівня класу, які ростуть з кожним запитом і ніколи не очищаються.
- З'єднання з базою даних, яке переідкривається на кожен запит замість повторного використання вже відкритого з'єднання.
- Глобальний контейнер залежностей — якщо він створюється заново на кожен запит, то втрачається весь сенс довгоживучого процесу.
- Циклічні посилання між об'єктами, які збирач сміття PHP не завжди прибирає вчасно у довгому процесі.

## Моніторинг воркерів і перезапуск

Навіть правильно написаний застосунок варто перезапускати за розкладом, так само як і звичайні черги — підхід майже такий самий, як у статті [про Supervisor для черг](https://www.zevshost.net/uk/kb/php-ocheredi-supervisor/). У Swoole є вбудований параметр `max_request`, який перезапускає воркер після заданої кількості оброблених запитів:

```
$server->set([
    'worker_num' => 4,
    'max_request' => 5000,
    'task_worker_num' => 2,
]);
```

## Чек-лист переходу на довгоживучий процес

1. Приберіть із коду будь-які статичні властивості, які накопичують дані між запитами без явного очищення.
2. Налаштуйте плановий перезапуск воркерів через `max_request` у Swoole або аналогічний ліміт у RoadRunner.
3. Прожене навантажувальне тестування перед перемиканням проду — зростання затримок за час роботи воркера одразу покаже витік.

---

Джерело: https://www.zevshost.net/uk/kb/php-swoole-roadrunner/
