Ejecutar procesos secuenciales en colas en Laravel

En alguna ocasión me he visto en la necesidad de ejecutar diferentes jobs en Laravel de forma secuencial y ordenada, de forma que un job no pueda ser ejecutado sin que los anteriores hayan terminado.

Ejecutar procesos secuenciales en colas en Laravel
Photo by Levi Jones / Unsplash

En alguna ocasión me he visto en la necesidad de ejecutar diferentes jobs en Laravel de forma secuencial y ordenada, de forma que un job no pueda ser ejecutado sin que los anteriores hayan terminado.

Por defecto, cuando ejecutamos la siguiente línea en Laravel:

ProcessVideo::dispatch($video);

Se creará un registro en la cola y será procesado desde que uno de los workers esté disponible. Ahora, imaginemos el siguiente pipeline:

  1. El usuario sube un vídeo al panel de administración
  2. El vídeo tiene que ser codificado a MP4 y comprimido para ahorrar ancho de banda
  3. Tras comprimirlo y codificarlo, hay que extraer una screenshot del vídeo y almacenarla en S3
  4. Esta screenshot hay que procesarla con Blurhash para generar una imagen de baja resolución que la aplicación pueda usar mientras se descarga la original

Al tratarse de jobs que deben ser ejecutados de forma secuencial no podemos usar el método ::dispatch() en cada uno de ellos como haríamos normalmente, ya que si tenemos varios workers corriendo esto haría que se ejecutaran de forma paralela.

Opción 1: Llamar a jobs desde otro job

En alguna ocasión, cuando no conocía la forma laraveliana, lo que hacía era llamar al dispatch del siguiente job cuando acababa de procesar uno de ellos. Algo así:

<?php

namespace App\Jobs\VideoProcessing;

use App\Models\Video;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class CompressVideo implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(
        public Video $video,
    )
    {
        //
    }

    public function handle()
    {
        // compress and convert video
        // ...
        // finish conversion and compression
        
        TakeVideoScreenshot::dispatch($this->video);
    }
}

De esta forma, me aseguraba de que cada job solo pueda ejecutarse cuando el anterior esté terminado. Este proceso se llama job chaining.

Usar el Bus de Laravel para chainear  jobs

Con el tiempo, me doy cuenta de que esto que estoy haciendo ya me lo gestiona Laravel de una forma mucho más expresiva: el Bus.

El Bus de Laravel nos permite crear una chain de jobs que deben ejecutarse de forma secuencial, de forma que si uno de ellos falla el resto no puede ejecutarse. Además, nos permite ejecutar acciones en caso de que alguno de los jobs falle. El código de arriba se nos quedaría de la siguiente forma:

use Illuminate\Support\Facades\Bus;

Bus::chain([
	new CompressVideo($video),
    new TakeVideoScreenshot($video),
    new GenerateVideoBlurhash($video),
])->dispatch();

Para leer más info sobre job chaining puedes hacer click aquí.