Laravel Octane: чем платит приложение за долгоживущий процесс
Octane держит приложение в памяти между запросами и убирает бутстрап фреймворка из горячего пути. Разбираем, что при этом ломается: утечки в синглтонах, статические свойства и стейт, переживший запрос.

Обычный Laravel на PHP-FPM собирает контейнер, читает конфиги, регистрирует сервис-провайдеры и строит приложение заново на каждый HTTP-запрос. При 300–400 запросах в секунду это заметная часть времени ответа. Octane загружает приложение один раз, держит его в памяти воркера и на каждый следующий запрос отдаёт уже готовый объект. Time-to-first-byte на «пустом» роуте падает с десятков миллисекунд до единиц. Плата за это — состояние, которое теперь живёт между запросами и о котором раньше можно было не думать.
Речь про Laravel Octane 2.x на фреймворке Laravel 10/11 с рантаймами Swoole, Open Swoole или RoadRunner. Разбираем один угол: что именно переживает границу запроса и как это отследить, а не как поднять Octane с нуля.
Что меняет долгоживущий процесс
В модели FPM каждый запрос — это отдельный процесс с чистой памятью. Всё, что вы наинициализировали, умирает в конце запроса. Небрежность прощается: даже утечка в статическом свойстве живёт ровно один запрос.
Octane убирает эту гарантию. Один воркер обслуживает тысячи запросов подряд, не перезапускаясь. Всё, что попало в память и не сбросилось, остаётся видимым следующему запросу. Меняются три вещи одновременно:
- Контейнер приложения переиспользуется. Синглтоны, зарегистрированные в провайдерах, создаются один раз на воркер, а не на запрос.
- Статические свойства классов сохраняются. Массив, который вы накапливаете в
static-поле, растёт от запроса к запросу, пока воркер не перезапустят. - Суперглобалы и запрос инкапсулированы. Прямое обращение к
$_SERVERилиrequest(), закешированное в синглтоне, отдаёт данные того запроса, при котором синглтон создался.
Octane сбрасывает часть фреймворкового стейта между запросами сам: пересоздаёт Request, чистит некоторые сервисы, вызывает конфигурируемые «flush». Но пользовательский код за пределами этого списка — ваша ответственность.
Термин: воркер
Воркер (worker) — отдельный процесс рантайма (Swoole или RoadRunner), который держит загруженное приложение в памяти и последовательно обрабатывает запросы. Их обычно запускают по числу ядер. Один воркер = одна копия приложения в памяти со своим набором синглтонов.
Три классические утечки состояния
Синглтон, закешировавший запрос
Сервис зарегистрирован как синглтон и в конструкторе тянет текущего пользователя или заголовок. Под FPM это работает: синглтон живёт один запрос. Под Octane синглтон создаётся при первом запросе воркера и отдаёт данные того запроса всем следующим — включая чужого авторизованного пользователя.
// Провайдер, register()
$this->app->singleton(ReportBuilder::class, function ($app) {
// ОШИБКА под Octane: user() зафиксируется на первом запросе воркера
return new ReportBuilder($app['auth']->user());
});
Правильно — не тащить request-зависимый стейт в конструктор синглтона. Либо получать пользователя в момент вызова метода, либо регистрировать сервис как bind (создаётся заново на каждое разрешение), либо резолвить зависимость через контейнер внутри метода.
// Пользователь берётся в момент вызова, а не при создании сервиса
class ReportBuilder
{
public function build(): Report
{
$user = auth()->user(); // актуален для текущего запроса
// ...
}
}
Статическое свойство как накопитель
Любой static-массив, в который вы что-то дописываете за запрос и не очищаете, под Octane превращается в бесконечно растущий буфер. Через несколько часов воркер занимает гигабайты, и его убивает ограничение памяти.
class MetricsCollector
{
// Под FPM обнуляется на каждый запрос, под Octane — растёт вечно
private static array $events = [];
public static function record(string $event): void
{
self::$events[] = $event;
}
}
Лечится либо явным сбросом в слушателе события RequestTerminated, либо отказом от статики в пользу инстанс-сервиса, который Octane пересоздаёт по конфигу.
Бутстрап, выполненный один раз
Иногда, наоборот, ждут поведения FPM там, где его нет. Код в register() или boot() провайдера выполняется один раз на старте воркера, а не на каждый запрос. Если вы читали там текущую локаль из заголовка и выставляли её приложению — под Octane все запросы воркера получат локаль первого.
Под Octane граница запроса перестаёт быть границей жизни объектов. Каждый синглтон нужно спрашивать: что произойдёт, если он проживёт десять тысяч запросов подряд.
Что Octane сбрасывает сам, а что нет
Octane пересоздаёт ключевые сервисы фреймворка между запросами и позволяет добавить свои в конфиг config/octane.php, секции warm и flush. Точный состав дефолтного списка зависит от версии — сверяйтесь с текущей документацией и с самим файлом конфига после публикации.
| Что | Кто отвечает за сброс | Риск, если забыть |
|---|---|---|
Объект Request, сессия, аутентификация | Octane, автоматически | — |
| Ваши синглтоны с request-стейтом | Разработчик | Утечка данных между пользователями |
| Статические свойства классов | Разработчик | Рост памяти воркера |
| Глобальные фасады, забинженные вручную | Зависит от регистрации | Устаревший стейт |
| Подключения к БД | Octane переиспользует пул | «Зависшие» транзакции при исключениях |
Как поймать утечку до прода
Проблема Octane в том, что она не проявляется на первом запросе. Локально вы кликаете по приложению, всё работает — потому что воркер обслужил один-два запроса. Разваливается это под нагрузкой и во времени.
- Прогоните нагрузку на один воркер. Запустите Octane с
--workers=1и отправьте тысячи запросов от разных пользователей через нагрузочный инструмент. Если данные утекают между запросами, при одном воркере это видно сразу. - Следите за памятью воркера во времени. Наблюдайте RSS процессов Octane под постоянной нагрузкой. Ровная пила (рост — перезапуск — рост) допустима. Монотонный рост без потолка — утечка в статике или в накапливающем синглтоне.
- Прогоните тесты в режиме Octane. Feature-тесты, гоняющие несколько запросов подряд в одном процессе, ловят часть утечек стейта. Тест, который проходит по одному, но падает при повторе, — сигнал.
- Ограничьте число запросов на воркер. Флаг
--max-requestsперезапускает воркер после N запросов. Это не лечение, а страховка: маскирует медленные утечки и снижает их ущерб на проде, пока вы ищете причину.
Где ломается сильнее всего
- Пакеты третьих сторон. Библиотека, написанная в расчёте на FPM, может держать стейт в статике. Свой код вы поправите, чужой — нет. Перед включением Octane проверьте, что ключевые пакеты заявляют совместимость.
- Долгие обработчики и блокировка воркера. В FPM медленный запрос занимал один процесс из пула. Под Swoole долгая синхронная операция блокирует воркер целиком, и он не берёт новые запросы. Тяжёлое выносите в очередь.
- Незакрытые транзакции. Соединение с БД переиспользуется. Если запрос упал с исключением внутри открытой транзакции и вы её не откатили, следующий запрос на том же воркере получит соединение в непредсказуемом состоянии.
Что делать дальше
Перед включением Octane на проде пройдите по своим сервис-провайдерам и выпишите все singleton-регистрации: для каждой ответьте, безопасно ли ей прожить весь воркер. Затем прогоните нагрузку на одном воркере и понаблюдайте RSS — это два действия, которые ловят большинство проблем.
Дальше настройте flush и warm в config/octane.php под свои сервисы и заведите на проде метрику памяти воркера с алертом на монотонный рост. Инвалидация пулов соединений и тонкая настройка числа воркеров под конкретную нагрузку — отдельная тема; здесь мы разобрали только границу стейта между запросами.
Prompt-инженер: Идеальные запросы для Midjourney, ChatGPT и других моделей.
Спросить за 15 ₽Источники: Laravel Octane — официальная документация, Laravel Octane на GitHub
Частые вопросы
У меня Swoole, а в статье примеры — они подойдут для RoadRunner?
Приложение работало под FPM без ошибок — почему под Octane что-то сломается?
Как понять, что память воркера растёт из-за утечки, а не просто так?
Я включил Octane и данные одного пользователя показались другому — это точно из-за него?
Флаг --max-requests решает проблему утечек?
Обязательно ли переписывать все синглтоны перед переходом на Octane?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.