Volver al inicio

Concurrencia en Laravel con TrueAsync — guía técnica

El artículo describe la integración de Laravel con TrueAsync para lograr una verdadera concurrencia. Se discuten en detalle los mecanismos de aislamiento contextual, la adaptación de servicios con estado y el trabajo con PDO Pool. La solución permite manejar cientos de solicitudes en un solo hilo sin Swoole.

TrueAsync en lugar de Swoole: arquitectura de concurrencia en Laravel
Advertisement 728x90

TrueAsync en Laravel: Arquitectura para concurrencia real sin Swoole

Las soluciones tradicionales para la asincronía en PHP, como Swoole o RoadRunner, requieren modificaciones profundas en el código e introducen funciones coloreadas. TrueAsync ofrece un enfoque fundamentalmente diferente: modificar el núcleo de Zend para asincronía transparente. Desglosaremos cómo adaptar Laravel a este modelo, superando problemas con servicios con estado y permitiendo el manejo concurrente de operaciones limitadas por I/O.

Fundamentos de TrueAsync: Cómo funciona la asincronía transparente

TrueAsync es un núcleo alternativo de PHP que modifica el Zend Engine, las bibliotecas de I/O y las operaciones de base de datos. Su principal ventaja es eliminar los problemas de coloreado de funciones. A diferencia de Node.js o Python asyncio, las funciones estándar de PHP (PDO::query(), file_get_contents()) ceden automáticamente el control al planificador durante las esperas de I/O. Las corrutinas se suspenden a nivel de función C, permitiendo manejar cientos de solicitudes en un solo hilo.

Importante entender: esto no es multihilo. El modelo se basa en multitarea cooperativa: cambio de contexto mientras se esperan recursos externos. Es ineficiente para tareas limitadas por CPU (cálculos pesados), pero ideal para escenarios web típicos que involucran bases de datos y llamadas a API. Aquí un ejemplo básico de uso:

Google AdInline article slot
use function Async\spawn;
use function Async\await;
use function Async\delay;

$a = spawn(function() {
    delay(100);
    return 'result A';
});

$b = spawn(function() {
    delay(100);
    return 'result B';
});

// Obschee time vypolneniya ~100ms
 echo await($a); 
echo await($b);

El problema de los servicios con estado en Laravel

Laravel fue diseñado originalmente para el modelo FPM, donde cada solicitud se maneja en un proceso aislado. Los servicios con estado (AuthManager, Session, Router) almacenan el estado en propiedades de objetos, que se destruyen después del procesamiento de la solicitud. En modo asíncrono, el proceso vive indefinidamente, lo que provoca fugas de estado:

// Coroutine A: request from user_1
Auth::loginUsingId(1);
delay(200);

// Coroutine B: request from user_2  
Auth::loginUsingId(2);

// Return in korutinu A
echo Auth::id(); // Vozvraschaet 2 vmesto 1

Las propiedades estáticas (Model::$dispatcher, Facade::$resolvedInstance) y los servicios en el contenedor IoC se comparten entre todas las solicitudes. Octane aborda esto clonando el contenedor, pero introduce sobrecarga y no cubre las propiedades estáticas.

Aislamiento contextual: La arquitectura de la solución

TrueAsync proporciona current_context(): un almacén de datos ligado al ciclo de vida de la solicitud. Cuando una corrutina termina, el contexto se borra automáticamente. La arquitectura divide Laravel en dos zonas:

Google AdInline article slot
  • Núcleo estático: router, config, contenedor IoC. Cargado una vez, compartido entre todas las solicitudes.
  • Servicios por solicitud: Auth, Session, Cookie. Creados en el contexto de cada corrutina.

El mecanismo clave es interceptar la resolución de servicios mediante AsyncApplication::resolve():

protected function resolve($abstract, ...)
{
    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, ...);
}

Claves contextuales mediante Enum y la solución de Facade

Para evitar colisiones y garantizar acceso seguro, se usan claves enum:

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

Esto proporciona:

Google AdInline article slot
  • Aislamiento de acceso (solo el propietario del enum puede leer/escribir datos)
  • Garantías sin colisiones (incluso con valores de cadena idénticos)
  • Soporte para análisis estático (PHPStan/IDE rastrea el uso)

Las Facades requerían una solución separada debido al caché de resolvedInstance en un array estático. Entra ScopedServiceProxy:

class ScopedServiceProxy
{
    public function __call($method, $args)
    {
        return ($this->resolver)()->$method(...$args);
    }
}

El proxy reenvía las llamadas al contexto de la corrutina actual mientras mantiene el caché a nivel de proxy.

Adaptaciones críticas y Pool de PDO

Después del aislamiento básico, se identificaron servicios que almacenan estado por solicitud internamente, requiriendo versiones asíncronas especializadas:

  • AsyncRouter (aislamiento de ruta actual)
  • AsyncDispatcher (eventos diferidos por corrutina)
  • AsyncTranslator (locale de traducción)
  • AsyncViewFactory (View::share())
  • AsyncConfig (cambios de config en tiempo de ejecución)
  • AsyncDatabaseSessionHandler (escrituras de sesión atómicas)
  • Async*Connection (contadores de transacciones)

Se prestó especial atención al manejo de bases de datos. Sin un pool de conexiones, las consultas concurrentes de corrutinas rompen el protocolo de PostgreSQL. TrueAsync lo resuelve con un Pool de PDO integrado:

$pdo = new PDO($dsn, $user, $password, [
    PDO::ATTR_POOL_ENABLED              => true,
    PDO::ATTR_POOL_MIN                  => 0,
    PDO::ATTR_POOL_MAX                  => 10,
    PDO::ATTR_POOL_HEALTHCHECK_INTERVAL => 30,
]);

El pool asigna dinámicamente conexiones: después de un resultado SELECT, la conexión regresa al pool, sirviendo más corrutinas que el tamaño máximo del pool. Para transacciones, la conexión se bloquea hasta commit/rollback.

Puntos clave

  • TrueAsync elimina el coloreado de funciones mediante modificaciones en el núcleo de PHP
  • Los servicios con estado de Laravel requieren aislamiento contextual mediante current_context()
  • El Pool de PDO es esencial para la operación correcta de bases de datos en entornos concurrentes
  • Las claves enum aseguran seguridad y trazabilidad de los datos contextuales
  • La solución no reemplaza el multihilo para tareas limitadas por CPU

— Editorial Team

Advertisement 728x90

Leer después