Cargando…

Esto está tardando más de lo esperado.

Volver al centro de ayuda

Colas y trabajo programado

Qué corre en segundo plano, cómo mantener vivo el worker y qué corre cron cada día.

Esta guía cubre las dos cosas que pasan fuera de una petición web: los jobs que esperan en una cola a un worker, y las tareas que el programador corre con un reloj. La necesitas cuando un job no corre, cuando un despliegue no surtió efecto en segundo plano, o cuando agregas trabajo propio.

La conexión de la cola

config/queue.php lee QUEUE_CONNECTION, por omisión database: los jobs son filas de la tabla jobs, que las migraciones del kit crean, y los jobs fallidos van a failed_jobs (driver database-uuids). No hace falta ningún servicio extra. Las conexiones y sus variables:

Conexión Variables Notas
database DB_QUEUE_CONNECTION (por omisión: la base de datos principal), DB_QUEUE_TABLE (jobs), DB_QUEUE (default), DB_QUEUE_RETRY_AFTER (10) La opción por omisión. retry_after es de diez segundos: un job que corre más que eso sin terminar se entrega a otro worker como si hubiera fallado. Súbelo por encima de tu job más largo, y mantenlo por encima del --timeout del worker
redis REDIS_QUEUE_CONNECTION (default), REDIS_QUEUE (default), REDIS_QUEUE_RETRY_AFTER (90) Más rápido y más ligero para la base de datos. Necesita la conexión Redis de config/database.php
sqs SQS_PREFIX, SQS_QUEUE, SQS_SUFFIX, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION Amazon SQS
beanstalkd BEANSTALKD_QUEUE_HOST, BEANSTALKD_QUEUE, BEANSTALKD_QUEUE_RETRY_AFTER Beanstalkd
sync ninguna Corre el job dentro de la petición que lo despachó. Está bien para un script de prueba; en producción bloquea a la persona que provocó el trabajo

QUEUE_FAILED_DRIVER (por omisión database-uuids) elige dónde se guardan los jobs fallidos. Los lotes de jobs usan la tabla job_batches.

Lo que el kit encola

Job o clase Se despacha cuando Qué hace el worker
App\Jobs\SendAnnouncementEmailsJob App\Observers\AnnouncementObserver ve un aviso guardado con estado Publicado, al crearse o al cambiar a ese estado Recorre a cada usuario no bloqueado, de cien en cien, y envía AnnouncementNotification a cada uno: correo cuando el aviso tiene "enviar correo" y el usuario acepta correo, web push cuando el usuario tiene un navegador suscrito. La entrega del aviso, peticiones push incluidas, ocurre aquí y no en la petición del administrador
WeblaborMx\BillingCore\Jobs\MigrateUsersToPlan Un administrador confirma el modal "Migrar usuarios del plan", abierto desde "Migrar suscriptores" en un precio; se despacha después de que la transacción hace commit Mueve las suscripciones seleccionadas, o todas, al precio destino a través del servicio de migración de planes
Cualquier cosa que escribas que implements ShouldQueue Llamas a dispatch() o notify() Las notificaciones y mailables que implementan ShouldQueue se serializan y las envía el worker

Lo que no se encola, para que sepas dónde buscar cuando algo va lento: las notificaciones construidas sobre App\Notifications\Notification se envían durante la petición, fila de base de datos, correo y push incluidos, porque la clase base usa Queueable sin ShouldQueue. El código de verificación de correo se envía de forma sincrónica con Mail::mailer(...)->send(), y el código por SMS también sale de forma sincrónica. Agrega implements ShouldQueue a una notificación tuya cuando se reparte a muchas personas; el job de avisos es el patrón para hacerlo en masa.

Para crear un job en cola usa el stub del proyecto, que ya implementa ShouldQueue:

php artisan make:job Orders/ArchiveOldOrders

Corre el worker en local

composer dev arranca cuatro procesos en una terminal: php artisan serve, php artisan queue:listen --tries=1, php artisan pail --timeout=0 (la cola del log) y npm run dev. Un job que despaches corre un momento después y su salida aparece en la misma terminal. queue:listen recarga el código en cada job, así que no lo reinicias después de editar una clase de job; por eso se usa aquí y no en producción.

Si prefieres no correr un worker, pon QUEUE_CONNECTION=sync en tu .env local. Todo corre en línea y las excepciones aparecen en la petición que las causó.

Corre el worker en producción

El worker es un proceso de larga vida:

php artisan queue:work --sleep=3 --tries=3 --max-time=3600

queue:work carga la aplicación una vez y la mantiene en memoria, que es lo que lo hace rápido y lo que lo deja viejo después de un despliegue. Mantenlo vivo con Supervisor; el bloque de programa completo está en /help/deploy-your-project. --sleep=3 es la pausa cuando la cola está vacía, --tries=3 los intentos antes de marcar un job como fallido, y --max-time=3600 la vida tras la cual el worker sale para que Supervisor lo reinicie.

Agrega --queue=high,default para atender varias colas con nombre en orden de prioridad, y --timeout=60 para matar un job que corra más de un minuto (mantenlo por debajo del retry_after de la conexión).

Reinícialo en cada despliegue

php artisan queue:restart

El comando escribe una marca de tiempo en el caché; cada worker la revisa después de cada job y sale cuando es más nueva que su propio arranque. Supervisor entonces arranca un worker nuevo, que carga el código nuevo. Dos consecuencias: el almacén de caché tiene que ser uno que compartan el worker y el proceso web (database o redis, nunca array), y un job que está corriendo cuando lo llamas termina primero. Sin este comando el worker sigue ejecutando la versión anterior: una notificación cuyo texto cambiaste sigue llegando con el texto viejo hasta que el worker muera por casualidad.

Jobs fallidos

Un job que lanza una excepción en cada uno de sus --tries intentos cae en failed_jobs con su excepción. La excepción también se reporta por los canales normales: el log, y Slack cuando SLACK_BOT_TOKEN está definido.

php artisan queue:failed            # listarlos, con id, conexión, cola y fecha
php artisan queue:retry 9f1c-...    # devolver uno a la cola por su id
php artisan queue:retry all         # devolverlos todos
php artisan queue:forget 9f1c-...   # borrar uno sin reintentar
php artisan queue:flush             # borrarlos todos
php artisan queue:prune-failed --hours=168   # borrar los de más de una semana

Reintenta después de corregir la causa y reiniciar el worker; reintentar un job sobre el código viejo lo vuelve a fallar.

Tareas programadas

El programador corre todo lo registrado con Schedule:: en routes/console.php y en los service providers de los paquetes. Dos tareas vienen con el kit:

Comando Cuándo Qué hace
php artisan stats:compute-daily Cada día a las 00:00 Cuenta los usuarios cuyo last_logged_at es ayer y guarda el número en la tabla stats bajo la clave daily_logged_users, una fila por día. El tablero del panel de administración lee esa tabla. Declarado en routes/console.php
php artisan billing:sync-exchange-rates Cada día a las 12:00, sólo cuando FEATURE_PLANS_ENABLED o FEATURE_ADDONS_ENABLED está encendida Busca las fechas de pago de suscripciones pagadas cobradas en una moneda distinta de primary_currency en config/pricing.php (por omisión mxn), y obtiene y guarda el tipo de cambio de cada fecha que no lo tenga. El proveedor es Frankfurter (exchange_rates.endpoint, https://api.frankfurter.app, tiempo límite exchange_rates.timeout, diez segundos). --date=2026-09-01 sincroniza una sola fecha. Declarado en el service provider de Billing Core; la configuración de monedas está en /help/currencies-intervals-and-exchange-rates

Las horas están en la zona horaria de la aplicación, UTC en config/app.php. php artisan schedule:list imprime la tabla con la próxima ejecución de cada una.

Nada corre a menos que cron llame al programador cada minuto:

* * * * * cd /var/www/your-project && php artisan schedule:run >> /dev/null 2>&1

schedule:run mira el reloj, corre lo que toque y sale. En local, php artisan schedule:work hace lo mismo en primer plano una vez por minuto, y php artisan schedule:test te deja elegir una tarea y correrla ahora.

Agrega las tuyas

Un job va en app/Jobs, un comando en app/Console/Commands (se registra solo), y su programación en routes/console.php:

Schedule::command('orders:archive-old')->dailyAt('03:00');
Schedule::job(new ArchiveOldOrders)->weeklyOn(1, '04:00');

Un comando programado que tarda, o que no debe traslaparse consigo mismo, lleva ->withoutOverlapping(); uno que deba correr en un solo servidor cuando tienes varios lleva ->onOneServer(), que necesita un almacén de caché compartido.