惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
WordPress大学
WordPress大学
宝玉的分享
宝玉的分享
人人都是产品经理
人人都是产品经理
博客园 - 聂微东
IT之家
IT之家
V
V2EX
Jina AI
Jina AI
V
Visual Studio Blog
有赞技术团队
有赞技术团队
博客园 - 司徒正美
博客园 - 叶小钗
The Cloudflare Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
小众软件
小众软件
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 三生石上(FineUI控件)
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Google DeepMind News
Google DeepMind News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
腾讯CDC
Google Online Security Blog
Google Online Security Blog
博客园 - 【当耐特】
Apple Machine Learning Research
Apple Machine Learning Research
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
N
News and Events Feed by Topic
N
News and Events Feed by Topic
The Last Watchdog
The Last Watchdog
W
WeLiveSecurity
月光博客
月光博客
Security Archives - TechRepublic
Security Archives - TechRepublic
Webroot Blog
Webroot Blog
SecWiki News
SecWiki News
博客园_首页
罗磊的独立博客
量子位
Latest news
Latest news
I
Intezer
V
Vulnerabilities – Threatpost
A
Arctic Wolf
Last Week in AI
Last Week in AI
Recent Commits to openclaw:main
Recent Commits to openclaw:main
S
SegmentFault 最新的问题
S
Security Affairs
阮一峰的网络日志
阮一峰的网络日志
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
酷 壳 – CoolShell
酷 壳 – CoolShell
P
Palo Alto Networks Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
N
News | PayPal Newsroom

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Laravel + TrueAsync: настоящая конкурентность без Swoole
YanGusik · 2026-04-17 · via Все публикации подряд на Хабре

Всё началось с отклонённого RFC.

В 2025 году в PHP промелькнул RFC про нативные корутины в PHP. Сообщество контрибьютеров-консерваторов посмотрело, покивало, и благополучно его завернуло — как это обычно и происходит со всем интересным в PHP. Я тогда подумал «жаль, а было бы интересно увидеть это в PHP…» и забыл.

Спустя полгода я случайно наткнулся на репозиторий — оказалось, автор не забросил идею и всё это время продолжал над ней работать.

TrueAsync — это альтернативное ядро PHP, которое реализует настоящую асинхронность путём модификации Zend-ядра, библиотек I/O, работы с БД и сокетами.

И когда вышла первая более-менее рабочая версия, у меня возникла очевидная мысль: а что если попробовать запустить на этом Laravel?

Спойлер: Laravel к такому не был готов...


Что такое TrueAsync и при чём тут корутины

Но прежде чем переходить к тому как я ломал Laravel — пара слов про то, что вообще такое корутины и зачем они нужны. (Если вы это знаете — листайте дальше)

Корутина — контекст, который может быть приостановлен буквально в любой точке.
В том числе внутри С-функции. Просто кусок кода который говорит планировщику «я жду ответа от IO, займись пока чем-нибудь другим». Планировщик переключается на другую корутину, та делает своё, потом возвращается к первой когда ответ пришёл.

Важная особенность TrueAsync: прозрачная асинхронность без цветных функций.

Если вы работали с Node.js или Python asyncio, вы знаете эту боль стоит одной функции стать async, и весь стек вызовов выше неё тоже обязан стать async.
Это называют проблемой цвета функций.

В TrueAsync этого нет: обычные функции PHP работают асинхронно без каких-либо изменений в их коде. PDO::query(), файловые операции, сокеты — всё прозрачно уступает управление планировщику в момент ожидания.

Вот как это выглядит:

use function Async\spawn;
use function Async\await;
use function Async\delay;

$a = spawn(function() {
    // имитируем запрос в БД 100мс +-
    delay(100);
    return 'результат A';
});

$b = spawn(function() {
    delay(100);
    return 'результат B';
});

// Важно: в отличие от Go тут нет паралельности. 
// Паралельность требует несколько потоков.
// А у нас код всегда в 1 потоке. 
// Поэтому код переключается, но за 1 раз работает только 1 корутина.

// общее время — ~100мс, не 200мс
echo await($a); // 'результат A'
echo await($b); // 'результат B'

Важный момент который часто путают это не многопоточность!

Один поток, один процесс. Корутины не работают буквально одновременно — они переключаются в моменты ожидания I/O.

Поэтому:
Хорошо работает там где есть ожидание: запросы в БД, HTTP-запросы к внешним API, чтение файлов — всё что связано с ожиданием ответа от внешней системы. Пока одна корутина ждёт — другая работает.

Не поможет с: вычисления на CPU, в простонародье (CPU-bound).
Если у тебя тяжёлый алгоритм или обработка больших файлов.
Потоки в TrueAsync в разработке 0.7.0, но пока их нет.

Для типичного веб-приложения — API с запросами в БД этого должно быть достаточно.


FrankenPHP и модель «один воркер — много запросов»

Общая схема

Общая схема

TrueAsync интегрируется с FrankenPHP в async-режиме. Воркер стартует, загружает приложение один раз, и дальше обрабатывает входящие запросы как корутины:

[воркер]
  ├── корутина: GET /api/orders    → запрос в БД → ждём...
  ├── корутина: GET /api/users     → запрос в БД → ждём...
  ├── корутина: POST /api/payments → запрос в БД → ждём...
  └── ...ещё 50 корутин в процессе

Пока все ждут ответа от PostgreSQL, планировщик гоняет их по кругу. PDO Pool прозрачно даёт каждой корутине своё физическое соединение с БД.

Звучит здорово. На практике оказалось, что сначала нужно объяснить это Laravel...


Проблема: Laravel не готов к такой жизни

Зря я туда полез....

Зря я туда полез....

Laravel проектировался под FPM-модель: один процесс = один запрос = умер. Stateful-сервисы живут на весь запрос и дальше выбрасываются вместе с процессом.

В async-модели процесс не умирает. Один воркер обрабатывает тысячи запросов подряд. И если Auth::user() где-то внутри хранит залогиненного пользователя в свойстве объекта — этот объект переживёт запрос и попадёт к следующему. Хорошо для синхронного кода, но плохо для нас.

Вот простой пример того как это ломается:

// Корутина A: запрос от пользователя user_1
Auth::loginUsingId(1);
delay(200); // ждём чего-нибудь

// Корутина B (запускается пока A ждёт): запрос от пользователя user_2  
Auth::loginUsingId(2);

// Возвращаемся в корутину A
echo Auth::id(); // 2. Привет, user_1. Ты теперь user_2.

AuthManager хранит guard в свойстве, guard хранит пользователя. Один объект на всех.

То же самое с сессиями, с View::share(), с локалью переводчика, с роутером который запоминает текущий маршрут, с конфигом если кто-то его перезаписывает в runtime.

Изначально я хотел взять за основу Octane, т.к он поддерживает swoole,roadrunner и т.д
Но Octane решает это клонированием контейнера на каждый запрос: аля clone $app.

Работает, но:

  1. Клонировать контейнер с сотнями байндингов на каждый запрос — это накладные расходы

  2. Статические свойства PHP-классов (Model::$dispatcher, Facade::$resolvedInstance) вообще не в контейнере — clone их не трогает, Octane вынужден вести ручной список того что сбрасывать (очень много listeners).

  3. PDO всё равно синхронный — настоящей конкурентности нет

Нам нужен другой подход.


Решение:

TrueAsync даёт current_context() — хранилище данных привязанное к текущему Scope (жизненному циклу запроса). Когда Scope запроса завершается — контекст автоматически уничтожается. Никакого ручного сброса.

По сути Laravel делится на две части:

  • Статическое ядро — роутер, конфиг, IoC-контейнер, сервис-провайдеры. Загружается один раз при старте воркера, живёт вечно, общее для всех запросов.

  • Per-request объекты — AuthManager, сессия, cookie. Создаются для каждого запроса в своём Scope, уничтожаются когда запрос завершён.

Идея: вместо того чтобы клонировать весь контейнер, перехватываем резолвинг конкретных сервисов и смотрим в контекст текущей корутины:

// Упростил Внутри AsyncApplication::resolve()
protected function resolve($abstract, ...)
{
    // 'auth', 'session', 'cookie', 'auth.driver' - scoped сервисы
    if ($this->asyncMode && $this->isScoped($abstract)) {
        $ctx = current_context();
        
        // есть в контексте этой корутины - возвращаем
        if ($instance = $ctx->find($abstract)) {
            return $instance;
        }
        
        // нет - создаём, кладём в контекст
        $instance = $this->buildFresh($abstract);
        $ctx->set($abstract, $instance);
        return $instance;
    }
    
    return parent::resolve($abstract, ...);
}

Каждая корутина получает свой экземпляр AuthManager, свою сессию, свой cookie-jar. Контейнер один, клонирования нет.

Ключи контекста — не строки а enum

enum ScopedService: string
  {
      case REQUEST     = 'request';
      case SESSION     = 'session';
      case AUTH        = 'auth';
      case AUTH_DRIVER = 'auth.driver';
      case COOKIE      = 'cookie';
  }

Это даёт три вещи сразу.

  1. Изоляция по доступу.
    Данные в контексте фактически приватны для того кода, который владеет enum-ом. Сторонний пакет не может случайно прочитать объект сессии или подменить request, у него нет ScopedService::SESSION.

  2. Никаких коллизий.
    Два разных enum-а с одинаковым строковым значением — разные ключи. ScopedService::REQUEST и SomeOtherEnum::REQUEST никогда не пересекутся, даже если оба backed-значения = 'request'.

  3. Статический анализ.
    IDE и PHPStan видит все точки использования каждого enum-case .
    Поиск ScopedService::AUTH мгновенно показывает все места, где читается или пишется auth-состояние. Со строками такой гарантии нет.

Проблема с Facades

Facades кешируют resolved instance в статическом массиве:

// Упрощённо, Illuminate\Support\Facades\Facade
protected static function resolveFacadeInstance($name)
{
    if (isset(static::$resolvedInstance[$name])) {
        return static::$resolvedInstance[$name]; // ← вот он, кэш
    }
    // ...
}

Даже если контейнер правильно изолирует, Facade закеширует первый instance и будет отдавать его всем корутинам навсегда.

Решение — ScopedServiceProxy. Facade кеширует прокси один раз, а прокси при каждом вызове идёт в контекст текущей корутины:

// Facade кеширует это
class ScopedServiceProxy
{
    public function __call($method, $args)
    {
        // каждый раз берём из контекста ТЕКУЩЕЙ корутины
        return ($this->resolver)()->$method(...$args);
    }
}

Auth::user() → Facade достаёт прокси из кеша → прокси идёт в current_context() → достаёт AuthManager этой корутины → возвращает правильного пользователя.


Что пришлось адаптировать

После того как базовая изоляция заработала, выяснилось что отдельные сервисы Laravel хранят per-request состояние у себя внутри. Пришлось делать async-safe версии (узнавали по ходу тестирования):

  • AsyncRouter$current и $currentRequest в свойствах объекта, течёт между корутинами

  • AsyncDispatcher — состояние отложенных событий (defer) per-coroutine

  • AsyncTranslator — локаль (setLocale) per-coroutine

  • AsyncViewFactoryView::share() per-coroutine

  • AsyncConfig — runtime-изменения конфига per-coroutine

  • AsyncDatabaseSessionHandler — upsert вместо INSERT + catch + UPDATE (атомарность при concurrent записи одной сессии)

  • Async*Connection — счётчик транзакций $transactions per-coroutine (иначе concurrent beginTransaction() создаёт SAVEPOINT вместо BEGIN)

Ещё адаптеры для Spatie Permissions, Inertia, Socialite, Telescope - но это уже детали.


PDO Pool - и почему без него никуда

Корутины разделяют один процесс — а значит и все объекты внутри него. Если дать двум корутинам одно и то же соединение с БД, они начнут одновременно писать и читать из одного сокета.
На сетевом уровне пакеты перемешиваются, протокол ломается.
PostgreSQL в таких случаях честно ругается: “запрос ещё выполняется, а вы уже шлёте следующий”.

Решение очевидное — каждой корутине своё соединение. Но создавать новое TCP-соединение на каждый запрос дорого и бессмысленно. Нужен пул.

Раньше это было проблемой: Swoole, AMPHP, ReactPHP — каждый требовал использовать свой async-драйвер для БД. Стандартный PDO не работал, нужно было переписывать код под специфичные клиенты.

Жизненный цикл

Жизненный цикл

В TrueAsync PDO Pool встроен в ядро. Включается одной строкой, весь остальной код не меняется:

$pdo = new PDO($dsn, $user, $password, [
    PDO::ATTR_POOL_ENABLED              => true,  // Включить пул
    PDO::ATTR_POOL_MIN                  => 0,     // Минимум соединений (по умолчанию 0)
    PDO::ATTR_POOL_MAX                  => 10,    // Максимум соединений (по умолчанию 10)
    PDO::ATTR_POOL_HEALTHCHECK_INTERVAL => 30,    // HealthCheck (сек, 0 = выключено)
]);

Пул работает умнее чем кажется.
Соединение возвращается не когда корутина завершилась, а как только оно больше не нужно — например, после SELECT и получения результата соединение сразу уходит обратно в пул.
Поэтому пул из 10 соединений может обслуживать больше чем 10 корутин одновременно.

Исключение — транзакции. Пока транзакция активна, соединение закреплено за корутиной до commit() или rollback()


Пара слов о производительности

asyncMode — это не просто флаг, а двухфазная инициализация.

Когда же Ларавел уже инициализирован и сервер стартует, активируется asyncMode, и тогда все данные уже не ложатся в общую память. Это касается например Ларавел конфига.

Таким образом мы снова экономим память где можно, а где нет - память честно делиться между запросами

ScopedServiceProxy добавляет лишний уровень indirection на каждый вызов через Facade.
Да, это чуть медленнее. Замеры показали что overhead незначительный, но он есть.

Зато такой подход позволяет быстро адаптировать большой фреймворк не переписывая его целиком. А проблемы с производительностью — решать точечно, если они вообще появятся на реальной нагрузке.


А что по цифрам?

После тысячи правок и исправлений багов, решил сравнить производительность с octane.

Нагрузка: 1200 req/s (константная, constant-arrival-rate), два эндпоинта с реальными запросами в PostgreSQL, 30 секунд, 12 воркеров у каждого, тестировал через k6.

PHP-FPM

Octane Swoole

TrueAsync

Реальный throughput

~200 req/s

~752 req/s

~1118 req/s

Пропущено итераций

~28 000

~5 000

20

Средняя latency

~4 000ms

~880ms

13ms

p95 latency

~5 000ms

2 320ms

21ms

p95 < 200ms

Пиковых DB-соединений

12

12

120

FPM упирается в количество процессов.
Swoole держит приложение в памяти, это помогает, но PDO блокирует воркер (12 соединений). TrueAsync при 1200 target реально обрабатывает 1118 и p95 = 21ms.

Важная оговорка: бенчмарки на WSL2, на bare metal числа будут выше. И это I/O-bound нагрузка — если у вас тяжёлые вычисления на CPU, прироста не будет.

Проверить тесты можно тут в ta_benchmark и проверить самому


Кстати, сам Swoole прогнал сравнение на чистом PHP без фреймворка и без I/O — на облачном сервере с 4 ядрами и 4GB RAM:

Метрика

TrueAsync

Swoole

Throughput

9 607 req/s

9 918 req/s

Avg latency

13.28ms

4.23ms

Median latency

8.4ms

1.11ms

P95 latency

43.06ms

19.01ms

Max latency

170.75ms

69.92ms

Max concurrent VUs

535

315

На CPU-bound нагрузке Swoole быстрее по latency — сказывается накладной расход на Go и PHP границе в FrankenPHP. Разница в throughput около 3%.

На I/O-bound нагрузке этот overhead незаметен — корутина всё равно ждёт ответа от БД, и эти миллисекунды растворяются в задержке сети.

https://github.com/php/frankenphp/issues/1754#issuecomment-4215141347


Что получилось

AsyncServiceProvider делает всё автоматически — ставишь пакет, он регистрирует адаптеры, дальше пишешь Laravel как обычно. Auth::user(), session(), DB::transaction() — работают корректно в concurrent-среде без изменений в коде приложения.

Так же, можно зарегистрировать чужие библиотеки как scoped.

    /*
    |--------------------------------------------------------------------------
    | Scoped Services
    |--------------------------------------------------------------------------
    |
    | Services listed here will be resolved per-coroutine instead of shared
    | as singletons. Use this for third-party packages that hold request state.
    |
    | Example:
    |   \SomePackage\Manager::class,
    |
    */

    'scoped_services' => [],

Репозиторий: yangusik/laravel-spawn

Если используете Spatie Permissions, Inertia, Socialite, Debugbar или Telescope — адаптеры для них тоже есть из коробки.

Потоки в PHP TrueAsync пока в разработке — когда появятся, CPU-bound задачи тоже получат прирост. Пока это история про I/O.

Вы же, в свою очередь, можете помочь с тестированием и фидбеком, это очень сильно помогает выйти true-async в стабильный релиз.

Ссылки:

Laravel Spawn
Документация
Репозиторий TrueAsync