> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Concurrencia

> Explicamos cómo mejorar el rendimiento de la aplicación ejecutando varios procesos en paralelo con la fachada Concurrency de Laravel 13.

## ¿Qué es la concurrencia?

Cuando ejecutas **en serie** varios procesos que no dependen entre sí, como solicitudes a varias APIs externas o agregaciones en la base de datos, el tiempo total es la suma de los tiempos de cada proceso.
Si los ejecutas **de forma concurrente**, el tiempo total se reduce al tiempo del proceso más lento.

La fachada `Concurrency` de Laravel ofrece una API sencilla para lograr esa ejecución concurrente.

<Info>
  La fachada `Concurrency` se introdujo en Laravel 11 y sigue disponible en Laravel 13.
  Su driver por defecto usa procesos PHP hijos, por lo que funciona sin necesidad de paquetes adicionales.
</Info>

## Cómo funciona

La fachada `Concurrency` serializa los closures recibidos, los envía a un comando Artisan oculto y los ejecuta en procesos PHP separados.
Cuando cada proceso termina, se serializa el valor devuelto y se retorna al proceso padre.

Hay tres drivers disponibles.

| Driver    | Descripción                                                                                         |
| --------- | --------------------------------------------------------------------------------------------------- |
| `process` | Por defecto. Lanza procesos PHP hijos. Funciona también en solicitudes web                          |
| `fork`    | Requiere el paquete `spatie/fork`. Como hace fork del proceso, solo funciona en CLI, pero es rápido |
| `sync`    | No ejecuta en paralelo, sino en secuencia. Adecuado para pruebas                                    |

## Uso básico

### Concurrency::run()

Al pasar un array de closures a `run()`, se ejecutan concurrentemente.
El valor devuelto es un array con los valores retornados por cada closure.

```php theme={null}
use Illuminate\Support\Facades\Concurrency;
use Illuminate\Support\Facades\DB;

[$userCount, $orderCount] = Concurrency::run([
    fn () => DB::table('users')->count(),
    fn () => DB::table('orders')->count(),
]);
```

Con la desestructuración de arrays puedes recibir cada resultado en una variable.
El orden de los closures coincide con el orden de los valores devueltos.

### Especificar el driver

Si quieres usar un driver específico, indícalo con el método `driver()`.

```php theme={null}
$results = Concurrency::driver('fork')->run([
    fn () => fetchFromApiA(),
    fn () => fetchFromApiB(),
]);
```

Para cambiar el driver por defecto, publica el archivo de configuración y actualiza la opción `default`.

```shell theme={null}
php artisan config:publish concurrency
```

## Uso del driver fork

El driver `fork` es más rápido que `process`, pero solo puede usarse en un entorno CLI de PHP (comandos Artisan o workers de cola).
No puede usarse dentro de una solicitud web.

Antes de utilizarlo, instala el paquete `spatie/fork`.

```shell theme={null}
composer require spatie/fork
```

<Warning>
  El driver `fork` no funciona durante una solicitud web. Úsalo cuando quieras concurrencia dentro de comandos Artisan o workers de cola.
</Warning>

## Sin recibir resultados: Concurrency::defer()

Si no te interesa el resultado del proceso y quieres ejecutarlo en segundo plano después de haber devuelto la respuesta HTTP, usa el método `defer()`.

```php theme={null}
use App\Services\Metrics;
use Illuminate\Support\Facades\Concurrency;

Concurrency::defer([
    fn () => Metrics::report('users'),
    fn () => Metrics::report('orders'),
]);
```

En el momento de llamar a `defer()` los closures no se ejecutan.
Se ejecutan concurrentemente después de que la respuesta HTTP se haya enviado al usuario.

<Tip>
  `defer()` es ideal para procesos que no deben hacer esperar al usuario, como registrar datos analíticos o precalentar cachés.
</Tip>

## Ejemplo práctico: llamar a varias APIs externas a la vez

Piensa en un dashboard de un ecommerce que consume tres APIs: gestión de inventario, ventas y estado de envíos.

### Ejecución en serie (antes)

```php theme={null}
use Illuminate\Support\Facades\Http;

public function dashboard(): array
{
    // Espera a que cada solicitud termine, una tras otra (total ~3 s)
    $inventory = Http::get('https://api.example.com/inventory')->json();
    $sales     = Http::get('https://api.example.com/sales')->json();
    $shipping  = Http::get('https://api.example.com/shipping')->json();

    return compact('inventory', 'sales', 'shipping');
}
```

### Ejecución concurrente (después)

```php theme={null}
use Illuminate\Support\Facades\Concurrency;
use Illuminate\Support\Facades\Http;

public function dashboard(): array
{
    // Ejecuta las tres solicitudes en paralelo (termina en el tiempo de la API más lenta)
    [$inventory, $sales, $shipping] = Concurrency::run([
        fn () => Http::get('https://api.example.com/inventory')->json(),
        fn () => Http::get('https://api.example.com/sales')->json(),
        fn () => Http::get('https://api.example.com/shipping')->json(),
    ]);

    return compact('inventory', 'sales', 'shipping');
}
```

Si cada API tarda 1 segundo, la ejecución en serie tarda 3 segundos, mientras que la concurrente termina en aproximadamente 1 segundo.

### Ejecutar varias agregaciones de base de datos a la vez

```php theme={null}
use Illuminate\Support\Facades\Concurrency;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;

public function statistics(): array
{
    [$totalUsers, $activeUsers, $totalOrders, $revenue] = Concurrency::run([
        fn () => DB::table('users')->count(),
        fn () => DB::table('users')->where('active', true)->count(),
        fn () => DB::table('orders')->count(),
        fn () => DB::table('orders')->sum('total_amount'),
    ]);

    return [
        'total_users'  => $totalUsers,
        'active_users' => $activeUsers,
        'total_orders' => $totalOrders,
        'revenue'      => $revenue,
    ];
}
```

<Tip>
  Al usar el driver `process` para agregaciones de base de datos, cada proceso hijo establece una nueva conexión.
  Con muchos procesos en paralelo, presta atención al número máximo de conexiones de la base de datos.
</Tip>

## Configuración para pruebas

En el entorno de pruebas conviene usar el driver `sync`, que ejecuta los closures uno tras otro.
Evitas el coste de arrancar procesos concurrentes y las pruebas se hacen más rápidas.

```php theme={null}
// tests/Feature/DashboardTest.php
use Illuminate\Support\Facades\Concurrency;

public function test_dashboard_returns_correct_data(): void
{
    Concurrency::fake(); // Cambia al driver sync

    $response = $this->get('/dashboard');

    $response->assertStatus(200);
}
```

Otra opción es cambiar el driver por defecto en `.env.testing`.

```ini theme={null}
CONCURRENCY_DRIVER=sync
```

## Advertencias

<AccordionGroup>
  <Accordion title="Limitaciones de los closures">
    Los closures se serializan y se pasan al proceso hijo.
    No puedes capturar del exterior objetos no serializables (conexiones a base de datos, handles de archivo, recursos, etc.).
    Crea dentro del closure los objetos que necesites.

    ```php theme={null}
    // Mal: captura un modelo Eloquent externo
    $user = User::find(1);
    Concurrency::run([
        fn () => $user->orders()->count(), // Podría no ser serializable
    ]);

    // Bien: obtener los datos dentro del closure
    $userId = 1;
    Concurrency::run([
        fn () => Order::where('user_id', $userId)->count(),
    ]);
    ```
  </Accordion>

  <Accordion title="Sobrecarga del driver process">
    El driver `process` tiene el coste de arrancar procesos hijos, así que para operaciones muy cortas (unos pocos milisegundos) puede no ser más rápido.
    Aporta beneficios cuando se paralelizan operaciones que tardan más de 100 ms, como solicitudes HTTP o agregaciones de base de datos.
  </Accordion>

  <Accordion title="Limitaciones del driver fork">
    Como `fork` hace fork del proceso PHP, no puede usarse durante una solicitud web (FPM o Apache).
    Úsalo solo dentro de comandos Artisan o workers de cola.
  </Accordion>

  <Accordion title="Manejo de excepciones">
    Si durante la ejecución concurrente se lanza una excepción, `run()` la vuelve a lanzar.
    Si quieres que el resto de operaciones continúen aunque una lance excepción, usa `try/catch` dentro del closure.

    ```php theme={null}
    Concurrency::run([
        function () {
            try {
                return Http::get('https://api.example.com/data')->json();
            } catch (\Exception $e) {
                return null; // Devuelve null en caso de fallo
            }
        },
    ]);
    ```
  </Accordion>
</AccordionGroup>

## Próximos pasos

<Card title="Colas y jobs" icon="layer-group" href="/es/queues">
  Aprende a ejecutar procesos en segundo plano de forma asíncrona con colas y jobs
</Card>


## Related topics

- [Caché](/es/cache.md)
