- Laravel ofrece una API unificada de colas con múltiples drivers (database, Redis, SQS…) y separación entre conexiones y colas lógicas.
- Los Jobs permiten mover tareas pesadas a segundo plano, con soporte para serializar modelos, middleware, trabajos únicos, cifrado, lotes y cadenas.
- Los workers se gestionan con queue:work, Supervisor y comandos como queue:pause/queue:continue, controlando reintentos, timeouts y prioridades.
- Existe un ecosistema completo para fallos, monitorización y pruebas (failed_jobs, queue:retry, queue:monitor, Queue::fake) que hace las colas fiables en producción.
Trabajar con colas en Laravel es casi obligatorio en cualquier proyecto medio serio: envío de correos, procesado de CSV, generación de PDFs, redimensionado de imágenes, llamadas a APIs externas… Si todo eso se hace en la petición HTTP principal, la web se vuelve lenta, aparecen errores 500 y el usuario acaba desesperado mirando una pantalla en blanco.
La buena noticia es que Laravel trae una infraestructura de colas muy potente y flexible que permite delegar esos trabajos pesados a procesos en segundo plano, gestionarlos por prioridad, agruparlos en lotes, monitorizarlos, reintentarlos cuando fallan e incluso pausar temporalmente los workers cuando toca mantenimiento. Vamos a ver con calma (y sin dejar nada en el tintero) cómo sacarle todo el jugo, incluyendo cómo pausar, reanudar y controlar su comportamiento en producción.
Conceptos básicos: conexiones, colas y drivers
En Laravel, antes de meterse a pausar nada, hay que entender bien la diferencia entre conexiones de cola y colas en sí. En config/queue.php tienes un array connections donde defines cada backend: database, redis, beanstalkd, sqs, sync, null, failover, rabbitmq (si lo añades), etc.
Cada conexión puede tener varias colas lógicas (nombres como default, emails, high, low, imports). El atributo queue de cada conexión indica cuál es la cola por defecto donde irán los trabajos si no se especifica otra mediante onQueue(). Así puedes hacer cosas como:
- Una cola rápida para emails u operaciones cortas.
- Otra de baja prioridad para tareas largas como procesar vídeos o informes grandes.
- Colas segmentadas por tipo de tarea:
imports,notificaciones,reportes, etc.
Cuando lanzas el worker con php artisan queue:work, por defecto escucha la cola indicada como predeterminada en la conexión seleccionada. Si quieres priorizar, puedes pasar varias colas separadas por comas:
php artisan queue:work redis --queue=high,default,low
Con eso el worker procesa primero high, luego default y solo después low, algo crucial cuando el sistema va cargado y no quieres que un informe de 30 minutos bloquee los correos de alta prioridad.
Drivers de colas y requisitos previos
Laravel ofrece una API unificada para varios drivers de colas, pero cada uno tiene sus particularidades y dependencias. Elegir bien el driver es clave antes de hablar de pausar workers.
Driver database
El driver database guarda los trabajos en una tabla SQL (normalmente jobs). Es fácil de montar y perfecto para proyectos pequeños o entornos de desarrollo donde no quieres liarte con Redis o SQS.
Pasos básicos:
- Generar la migración:
php artisan queue:table(o en versiones nuevas, viene comocreate_jobs_tablepor defecto). - Ejecutar migraciones:
php artisan migrate. - Configurar QUEUE_CONNECTION=database en el
.envy confirmar enconfig/queue.phpque'default' => env('QUEUE_CONNECTION','database').
Es cómodo y suficientemente robusto, pero con muchas colas y mucho tráfico puede volverse un cuello de botella, ya que todo pasa por la misma base de datos relacional.
Driver redis
El driver redis es el caballo de batalla habitual en producción. Es rápido, permite características avanzadas (bloqueo, rate limiting, locks atómicos, Horizon…) y escala muy bien.
Puntos importantes:
- Debes configurar la conexión Redis en
config/database.php. - Las opciones
serializerycompressionde Redis no son compatibles con el driver de colas. - Si usas Redis Cluster, los nombres de las colas deben llevar una etiqueta de hash, por ejemplo
{default}, para que todas las claves de esa cola caigan en el mismo slot. - La opción
block_forpermite que el worker se bloquee esperando trabajos unos segundos, en lugar de hacer polling continuo: ahorra CPU y reduce consultas al servidor Redis.
Por ejemplo:
'redis' => ,
Si pones block_for = 0, el worker se queda bloqueado indefinidamente hasta que haya un trabajo disponible, algo a tener en cuenta si quieres que responda rápido a señales como SIGTERM o a comandos de pausa.
Otros drivers: SQS, Beanstalkd, null, failover…
Además de database y redis, Laravel soporta otros sistemas:
- Amazon SQS: colas gestionadas en AWS, con soporte para FIFO, grupos de mensajes, deduplicación y configuración avanzada de visibilidad. Requiere el SDK
aws/aws-sdk-php. - Beanstalkd: muy rápido y sencillo, usando el paquete
pda/pheanstalk. - sync: ejecuta los trabajos inmediatamente en el mismo proceso. Útil en desarrollo o testing, pero en producción prácticamente es como no usar colas.
- null: descarta los trabajos en cuanto se “despachan”. Se usa para desactivar completamente el sistema de colas.
- failover: permite configurar varias conexiones y, si la primaria falla, Laravel intenta empujar el trabajo en la siguiente de la lista. Perfecto para entornos donde la alta disponibilidad es crítica.
A nivel de pausar y reanudar workers, la lógica es prácticamente la misma independientemente del driver, porque se gestiona desde los comandos de Artisan y la clase Worker, no desde el backend de cola.
Creación de trabajos (Jobs) y serialización de modelos
Un Job en Laravel es simplemente una clase que encapsula una tarea que se va a ejecutar en cola. Normalmente se genera con Artisan y se guarda en app/Jobs.
Para crear uno:
php artisan make:job ProcessPodcast
La clase generada implementa ShouldQueue y suele usar los traits Dispatchable, InteractsWithQueue, Queueable y SerializesModels. El corazón está en el método handle(), que es lo que ejecuta el worker cuando le llega el trabajo.
Un detalle muy potente es la serialización automática de modelos Eloquent. Si en el constructor del Job aceptas un modelo, Laravel no mete el objeto entero en la cola, solo su identificador. Cuando el trabajo se procesa, reconstruye el modelo desde la base de datos con sus relaciones cargadas.
Ejemplo típico (en pseudocódigo, para no repetir tal cual):
- Constructor recibe Podcast $podcast.
- El trait
SerializesModelsse encarga de guardar el ID. - En
handle(AudioProcessor $processor)haces el trabajo duro (procesar audio, por ejemplo).
Si no quieres que las relaciones de un modelo se serialicen (para que el payload no sea enorme), puedes usar $model->withoutRelations() o en PHP 8 el atributo # en la propiedad promovida del constructor.
Inyección de dependencias en el método handle
Otra ventaja es que puedes inyectar servicios directamente en el método handle (como si fuera un controlador), porque Laravel usa el contenedor de servicios también en los Jobs. Por ejemplo, puedes pedir un AudioProcessor, clientes HTTP, repositorios, etc., y el framework se encarga de resolverlos.
Si necesitas un control ultra fino de cómo se llama al handle, puedes usar Container::bindMethod() en un service provider y definir tú mismo la lógica de inyección, pero en la mayoría de casos no hace falta complicarse tanto.
Middleware de trabajos
Igual que las rutas tienen middleware, los Jobs también pueden tener middleware específicos que se ejecutan antes y después del trabajo. Esto reduce mucho el código repetitivo en los métodos handle() y es especialmente útil para:
- Limitación de tasa (rate limiting) con Redis.
- Evitar solapamientos de trabajos que tocan el mismo recurso.
- Regular excepciones para APIs inestables.
- Saltar trabajos en ciertas condiciones (
Skipmiddleware).
Los middleware se suelen colocar en app/Jobs/Middleware y se devuelven desde el método middleware() del Job. Ejemplo conceptual:
public function middleware(): array { return ;
Tipos especiales de trabajos: únicos, cifrados y con bloqueo
Laravel permite marcar ciertos trabajos como únicos para que nunca haya dos instancias iguales en cola al mismo tiempo. Esto se hace implementando la interfaz ShouldBeUnique o ShouldBeUniqueUntilProcessing, y soporta definir una clave uniqueId() y un tiempo uniqueFor.
Esto es ideal para cosas como:
- Recalcular un índice de búsqueda de un producto: solo una vez por producto hasta que termine.
- Sincronizaciones con servicios externos que no admiten peticiones concurrentes para el mismo recurso.
Por debajo, Laravel usa un lock atómico de cache (Redis, memcached, database, etc.). También puedes elegir el driver de cache para ese lock con uniqueVia().
Además, existe la interfaz ShouldBeEncrypted, con la que indicas que el Job completo se cifre antes de entrar en la cola. Muy útil cuando pasas datos sensibles y no quieres que viajen en claro por Redis, SQS o la base de datos.
Despachar, retrasar y ejecutar trabajos en diferentes modos
Cuando tu Job está definido, lo lanzas con Job::dispatch() desde controladores, listeners de eventos, comandos Artisan o incluso closures. Los parámetros que pasas a dispatch van directos al constructor del Job.
Variantes interesantes:
dispatchIf()ydispatchUnless()para condicionar el envío en una sola línea.delay()para que el trabajo no se pueda procesar hasta una fecha/hora concreta.dispatchAfterResponse()oafterResponse()endispatch()de closures, que difieren la ejecución hasta que se ha enviado la respuesta HTTP, pero dentro del mismo proceso PHP.dispatchSync()para ejecutar el Job de forma síncrona (sin pasar por la cola) pero reutilizando la misma clase.- Conexiones especiales como
deferredobackgroundpara ejecutar después de la respuesta, en el mismo proceso o en un proceso PHP nuevo.
Por último, puedes indicar conexión y cola con métodos encadenados onConnection() y onQueue(), ya sea al despachar o dentro del constructor del Job.
Control fino: reintentos, backoff, timeouts y manejo de errores
Una buena parte del trabajo con colas en Laravel es decidir cuántas veces reintentar un trabajo, cuánto tiempo esperar entre intentos y en qué momento considerarlo fallido.
Opciones disponibles:
--triesenqueue:workpara fijar el máximo de intentos para todos los trabajos de ese worker (salvo que el Job defina su propio$triesotries()).$trieso un métodotries()en la clase Job para un control por-Job.retryUntil()para decir: “reintenta lo que quieras hasta esta hora, a partir de ahí, fallo”. Si coincide contries, mandaretryUntil.$maxExceptionspara cortar cuando el problema es por excepciones no controladas repetidas.$timeouto--timeoutpara indicar cuántos segundos se puede ejecutar un Job antes de que el worker mate el proceso hijo.$failOnTimeoutpara marcar el trabajo como fallido en caso de timeout.$backoffo métodobackoff()para definir cuánto esperar entre reintentos, incluso con valores exponenciales.
Si un trabajo lanza una excepción sin capturar, Laravel lo libera de nuevo en la cola hasta agotar los intentos configurados. Puedes controlar esto manualmente con release($segundos) o fail($exception) cuando quieras marcar adrede un trabajo como fallido.
Trabajos fallidos, tabla failed_jobs y poda
Cuando un Job agota sus intentos, pasa a considerarse trabajo fallido y Laravel lo inserta en la tabla failed_jobs (o en DynamoDB si así lo configuras). Esa tabla guarda UUID, conexión, cola, payload del Job y la excepción que reventó.
Comandos útiles:
php artisan queue:failedpara listar los fallidos.php artisan queue:retry <id|all>para reintentarlos.php artisan queue:forget <id>para borrar uno.php artisan queue:flushpara borrar todos (con opción de horas en versiones nuevas).php artisan queue:prune-failed --hours=48para podar registros antiguos automáticamente.
En los Jobs puedes definir un método failed(Throwable $exception) para hacer limpieza extra: notificar al usuario, revertir acciones parciales, escribir logs especializados, etc. Ten en cuenta que se crea una instancia nueva del Job para llamar a failed(), así que no confíes en cambios de propiedades hechos en handle().
Job batching: lotes de trabajos y combinación con cadenas
Cuando tienes que lanzar cientos o miles de Jobs relacionados (por ejemplo, importar un CSV gigante por trozos, procesar ficheros por lotes, limpiar datos, etc.), conviene usar la funcionalidad de batches de Laravel.
Flujo básico:
- Crear la tabla
job_batchesconphp artisan queue:batches-table && php artisan migrate. - Marcar los Jobs que formarán parte de lotes con el trait
Batchable. - Usar
Bus::batch()para enviar un conjunto de Jobs, con callbacksthen,catchyfinally. - Acceder al lote desde cada Job con
$this->batch()y comprobar si está cancelado. - Posibilidad de añadir más Jobs a un lote en marcha con
$this->batch()->add().
Los batches se integran de maravilla con cadenas (chains): puedes tener cadenas dentro de un lote o lotes como elementos de una cadena mayor. Es especialmente útil cuando quieres que varias cadenas se ejecuten “en paralelo” y disparar algo cuando todas se han completado.
Ejecución de workers, prioridades y despliegue
Toda la magia de las colas ocurre porque tienes uno o varios workers ejecutando el comando queue:work (o queue:listen, aunque este último es menos eficiente). Estos procesos son demonios de larga duración que se quedan escuchando la cola correspondiente.
Opciones clave de queue:work:
- Conexión:
php artisan queue:work redis,queue:work database, etc. - Colas:
--queue=high,default,lowpara priorizar. - –once: procesa un solo trabajo y sale.
- –max-jobs: procesa X trabajos y termina (para reiniciar workers periódicamente y liberar memoria).
- –max-time: procesa durante N segundos y luego sale.
- –sleep: segundos de espera cuando no hay trabajos disponibles.
- –stop-when-empty: vacía la cola y se cierra de forma limpia (ideal en entornos Docker o jobs puntuales).
- –timeout: segundos de timeout por trabajo, como comentábamos antes.
- –force: para procesar trabajos incluso si la app está en modo mantenimiento.
Como estos procesos son de larga duración, en producción casi siempre se usa un monitor de procesos como Supervisor (en Linux) para levantar X workers, reiniciarlos si caen y gestionar logs. Un típico bloque de configuración de Supervisor para Laravel define:
- command con el
php artisan queue:work ...concreto. - numprocs para indicar cuántos workers lanzar en paralelo.
- Rutas de logs, usuario, reinicios automáticos, etc.
Pausar y reanudar colas en Laravel
Vamos a lo que muchas veces interesa en mantenimiento: cómo pausar workers de colas sin pararlos a machete y sin dejar jobs a medias. Laravel ofrece varios mecanismos, algunos más nuevos, otros clásicos, para controlar esto.
Pausar: queue:pause y señales de interrupción
En versiones recientes, Laravel añade comandos específicos para pausar y continuar la ejecución de workers:
php artisan queue:pause: indica al sistema que los workers dejen de coger nuevos trabajos, pero dejen terminar los que ya están en curso.php artisan queue:continue: reanuda el procesamiento de nuevos trabajos desde donde se dejó.
Estos comandos trabajan enviando señales a los workers a través del mecanismo de cache que Laravel usa internamente. Por eso es importante tener un driver de cache bien configurado (memcached, redis, database, file…) incluso si tu cola como tal es otra cosa.
Importante: pausar una cola o un worker no cancela ni elimina los trabajos ya encolados, simplemente evita que se sigan retirando más trabajos de la cola mientras dure la pausa.
Polling de interrupciones e impacto en el rendimiento
Cada vez que un worker termina un trabajo, Laravel consulta en la cache si hay señales de restart, pause o continue. Eso tiene un coste minúsculo, pero existe. Si vas al milímetro con el rendimiento y sabes que no vas a necesitar pausar o reiniciar workers desde comandos, puedes desactivar este polling llamando a:
Queue::withoutInterruptionPolling();
O incluso ajustar propiedades estáticas de Illuminate\Queue\Worker para desactivar toques concretos ($restartable, $pausable). Eso sí, si haces esto y luego intentas usar queue:pause o queue:restart, los workers no se enterarán. Es una decisión de diseño que conviene pensar bien.
Pausar “a mano” con Supervisor o Docker
Además de los comandos propios de Laravel, en entornos productivos muchas veces se “pausa” de forma indirecta:
- Parando el programa en Supervisor (
supervisorctl stop laravel-worker:*), que mata o detiene los workers. - Usando –stop-when-empty en despliegues para dejar que los workers acaben lo que haya en cola y luego se apague.
- En Docker, dejando que el contenedor procese con
--stop-when-emptyy luego se apague.
La diferencia con queue:pause es que, al parar por completo los procesos, dejarás de procesar trabajos hasta que se levanten de nuevo los workers. Con pausa interna puedes mantener los procesos en memoria, atentos al comando continue, algo útil en despliegues roll-back rápidos o mantenimientos cortos.
Transacciones de base de datos y after_commit
Un clásico: despachar Jobs dentro de transacciones de base de datos. Si no tienes cuidado, el worker puede ejecutar el Job antes de que se haga el commit y el modelo o los datos que esperas aún no estén en la base de datos.
Para evitar este baile, tienes dos formas:
- Activar
'after_commit' => trueen la configuración de la conexión de cola. Así, cualquier Job despachado dentro de una transacción se pospone internamente hasta que se confirme. - Usar métodos por-job
afterCommit()obeforeCommit()al despachar, para forzar el comportamiento solo en Jobs concretos.
Si la transacción se revierte, los trabajos despachados durante la misma se desechan sin llegar a entrar en la cola, que es justo lo que suele interesar para mantener la consistencia.
Monitoreo, limpieza de colas y pruebas
Cuando ya tienes colas funcionando en serio, no basta con tirar Jobs y olvidarse; conviene monitorizar y probar cómo se comporta todo.
Herramientas y comandos útiles:
queue:clearpara vaciar una cola específica (SQS, Redis, database).queue:monitorpara vigilar el número de trabajos en ciertas colas y disparar el eventoQueueBusysi se pasan de un umbral.- Eventos de Job como
Queue::before,Queue::after,Queue::loopingyQueue::failingpara enganchar lógica custom (logs, métricas, rollbacks, etc.). - Fakes en pruebas usando
Queue::fake()yBus::fake()para comprobar que se han despachado Jobs, cadenas o lotes sin ejecutarlos de verdad.
En tests puedes afirmar cosas como “se ha encolado este Job X veces”, “se ha despachado una cadena con estos Jobs en este orden” o “no se ha encolado ningún Job determinado”, lo que da mucha seguridad en flujos complejos.
Con todo este ecosistema —drivers como Redis o SQS, Jobs bien diseñados, middleware para control fino, lotes, cadenas, configuración de reintentos, workers gestionados por Supervisor y comandos como queue:pause y queue:continue— Laravel te permite dominar el procesamiento en segundo plano casi al milímetro, desde el típico envío de correos hasta arquitecturas de colas distribuidas con failover, garantizando que tu aplicación siga respondiendo rápido en la capa HTTP mientras debajo se cuecen trabajos pesados sin bloquear al usuario.
Tabla de Contenidos
- Conceptos básicos: conexiones, colas y drivers
- Drivers de colas y requisitos previos
- Creación de trabajos (Jobs) y serialización de modelos
- Tipos especiales de trabajos: únicos, cifrados y con bloqueo
- Despachar, retrasar y ejecutar trabajos en diferentes modos
- Control fino: reintentos, backoff, timeouts y manejo de errores
- Trabajos fallidos, tabla failed_jobs y poda
- Job batching: lotes de trabajos y combinación con cadenas
- Ejecución de workers, prioridades y despliegue
- Pausar y reanudar colas en Laravel
- Transacciones de base de datos y after_commit
- Monitoreo, limpieza de colas y pruebas