Урок 05

Laravel Octane: Swoole и RoadRunner

Что такое Octane

Octane — сервер приложений, который держит Laravel в памяти между запросами.

Что даёт:

  • Bootstrap один раз.
  • Отклик в разы быстрее.
  • RPS в 5–10× выше.
  • Память — константна.

Что стоит:

  • Сложнее дебажить.
  • Утечки заметны.
  • Не всё работает.

Правило: Octane — следующий шаг после оптимизаций.

Как работает

Обычный PHP-FPM:

  • Каждый запрос — bootstrap.
  • Composer, config, providers.
  • 50–100 мс на пустой запрос.

Octane:

  • Bootstrap один раз.
  • Keep-alive воркеры.
  • 1–3 мс на пустой запрос.

Правило: Octane экономит bootstrap.

Два сервера

  • Swoole — расширение PHP.
  • RoadRunner — Go-сервер.

Правило:

  • Swoole — максимальная скорость.
  • RoadRunner — проще ставить.

Требования

Swoole:

  • PHP 8.1+.
  • Расширение swoole.
  • Linux / macOS.

RoadRunner:

  • PHP 8.1+.
  • Бинарник rr.
  • Linux / macOS / Windows.

Установка

composer require laravel/octane
php artisan octane:install

Что спрашивает:

  • Swoole или RoadRunner.
  • Создать конфиг config/octane.php.

Swoole: установка расширения

pecl install swoole

php.ini:

extension=swoole.so

Проверка:

php -m | grep swoole

Правило: Swoole — не через composer, а через pecl.

RoadRunner: установка

./vendor/bin/rr get-binary

Что даёт: бинарник rr в проекте.

Запуск

Dev:

php artisan octane:start --watch

Прод (Swoole):

php artisan octane:start --server=swoole --host=0.0.0.0 --port=8000 --workers=4

Прод (RoadRunner):

php artisan octane:start --server=roadrunner --host=0.0.0.0 --port=8000 --workers=4

Правило: --workers = количество CPU-ядер.

Reload

php artisan octane:reload
php artisan octane:stop

Что даёт: перезагрузка воркеров без простоя.

Правило: после деплоя — всегда octane:reload.

Что важно

Проблемы:

  • Состояние между запросами.
  • Статичные свойства не сбрасываются.
  • Singleton живёт долго.
  • Утечки памяти.
  • Соединения к БД не закрываются.

Правило: писать stateless код.

Stateless код

Плохо:

class UserService
{
    private ?User $currentUser = null;

    public function setUser(User $user): void { /* ... */ }
}

Хорошо:

class UserService
{
    public function get(User $user): array { /* ... */ }
}

Правило: не хранить состояние в сервисах.

Config cache

Обязательно:

php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache

Правило: Octane требует кэша.

Не сбрасывается автоматически

Что остаётся:

  • Статичные свойства.
  • Container singletons.
  • App state.
  • Статические счётчики.

Правило: за этим следить вручную.

Listeners

config/octane.php:

'listeners' => [
    RequestReceived::class => [
        ...Octane::prepareApplicationForNextOperation(),
        ...Octane::prepareApplicationForNextRequest(),
    ],
    RequestTerminated::class => [
        ...Octane::prepareApplicationForNextOperation(),
    ],
],

Что делают: сбрасывают состояние.

Правило: использовать штатные listeners.

Утечки памяти

Как найти:

use Laravel\Octane\Events\RequestTerminated;

Event::listen(RequestTerminated::class, function () {
    if (memory_get_usage() > 100 * 1024 * 1024) {
        Log::warning('Memory leak', [
            'usage' => memory_get_usage(true),
            'url' => request()->fullUrl(),
        ]);
    }
});

Правило: логировать при > 100 MB.

Restart по памяти

config/octane.php:

'max_requests' => 500,   // после — воркер перезапускается

Что даёт: утечки не накапливаются.

Правило: max_requests — обязательно.

Swoole Table

Для shared state:

use Swoole\Table;

$table = new Table(1024);
$table->column('count', Table::TYPE_INT);
$table->create();

$table->incr('key', 'count');

Что даёт: быстрый shared counter.

Правило: shared state — через Swoole Table.

Swoole Cache driver

.env:

CACHE_STORE=octane

Что даёт: кэш в памяти воркеров.

⚠️ Не шарится между воркерами.

Правило: octane-кэш — только для одного воркера.

Concurrent tasks

Swoole:

use Laravel\Octane\Facades\Concurrently;

[$users, $posts] = Concurrently::run([
    fn () => User::all(),
    fn () => Post::all(),
]);

Что даёт: параллельные запросы.

Правило: независимые операции — параллельно.

RoadRunner vs Swoole

SwooleRoadRunner
Установкаpeclбинарник
СкоростьБыстрееБыстро
PSR-7Не полностьюПолностью
WindowsНетДа
СтабильностьВысокаяВысокая

Правило:

  • Linux → Swoole.
  • Windows → RoadRunner.
  • Простота → RoadRunner.

Octane vs PHP-FPM

Когда Octane:

  • RPS > 1000.
  • Bootstrap > 30 мс.
  • Много запросов.
  • Команда готова к нюансам.

Когда НЕ Octane:

  • Маленький проект.
  • Не готовы следить за утечками.
  • Legacy с shared state.

Правило: Octane — не серебряная пуля.

Nginx + Octane

Upstream:

upstream octane {
    server 127.0.0.1:8000;
    keepalive 32;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://octane;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Connection "";
    }
}

Правило: keepalive — обязателен для скорости.

Supervisor

[program:octane]
command=php /var/www/skills/api/artisan octane:start --server=swoole --workers=4
user=www-data
numprocs=1
autorestart=true
stopwaitsecs=3600

Правило: Octane — один процесс на сервер.

Deploy

php artisan down
git pull origin main
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan optimize:clear
php artisan optimize
php artisan octane:reload
php artisan up

Правило: reload — обязательно после деплоя.

Мониторинг

Что смотреть:

  • Memory per worker.
  • Restart count.
  • RPS.
  • p95 latency.
  • Error rate.

Через Laravel Pulse + Sentry.

Правило: без мониторинга — утечка словит прод.

Практика

1. Установите Octane + Swoole.

2. Запустите octane:start --watch.

3. Проверьте X-Cache / X-Octane.

4. Замерьте latency до/после.

5. Включите max_requests = 500.

6. Логируйте при > 100 MB.

7. Проверьте на утечки.

8. Настройте Nginx + keepalive.

9. Supervisor + autorestart.

10. octane:reload в деплое.

Частые ошибки

1. Shared state:

Чужие данные

Решение: stateless.

2. Singleton с состоянием:

Утечка

Решение: reset в listener.

3. Нет max_requests:

OOM

Решение: 500.

4. Забыли reload:

Старый код

Решение: в деплой.

5. Без keepalive:

Медленно

Решение: Nginx upstream.

6. Octane на Windows:

Не работает

Решение: RoadRunner или Linux.

7. Не то ядро/расширение:

Ошибки

Решение: Swoole 5+, PHP 8.1+.

8. Без мониторинга:

Тихий OOM

Решение: Pulse + Sentry.

Итог

  • Octane — долгоживущие воркеры.
  • 5–10× быстрее.
  • Swoole — быстрее.
  • RoadRunner — проще.
  • Stateless — обязательно.
  • max_requests — защита.
  • octane:reload — после деплоя.
  • Nginx + keepalive.
  • Мониторить память.
  • Не серебряная пуля.

Дальше — оптимизация middleware и сервис-провайдеров.