Laravel Horizon
Com monitoritzar i gestionar cues Redis amb Laravel Horizon: supervisors, balanceig, mètriques, notificacions i desplegament.
Què és Horizon?#
Laravel Horizon és un paquet oficial que proporciona un dashboard elegant i un sistema de gestió avançat per a les cues basades en Redis. Quan la teva aplicació processa centenars o milers de jobs en segon pla, necessites visibilitat: quants jobs s'estan processant? Quant de temps triguen? Quants fallen? Quina cua acumula més feina? Sense un sistema de monitorització, estàs treballant a cegues, i qualsevol problema amb les cues pot passar desapercebut fins que un usuari es queixa perquè el seu correu no ha arribat o el seu informe no s'ha generat.
Horizon resol aquest problema proporcionant un dashboard web en temps real que mostra l'estat complet de tota la infraestructura de cues: jobs completats, fallits i pendents, temps d'espera, throughput per minut, temps d'execució mitjà, mètriques per cua i per job, i molt més. Però Horizon no és només un dashboard: també substitueix el worker per defecte de Laravel per un de propi que ofereix balanceig automàtic de workers entre cues, de manera que els recursos s'assignen dinàmicament segons la càrrega real.
Per què Horizon i no queue:work?#
La comanda php artisan queue:work és suficient per a projectes petits o entorns de desenvolupament, però presenta limitacions importants en producció. Amb queue:work, no tens cap interfície visual per veure l'estat de les cues. Si vols saber quants jobs estan pendents, has de consultar Redis directament. Si un job falla, has d'anar a la taula failed_jobs de la base de dades i llegir l'error manualment. Si una cua acumula massa feina, no tens cap manera de redistribuir workers automàticament.
Horizon canvia tot això. Amb un sol dashboard web, tens visibilitat completa sobre cada job: quan s'ha encuat, quant de temps ha esperat, quant ha trigat a executar-se, si ha fallat i per quin motiu. Pots reintentar jobs fallits directament des del dashboard amb un clic. I el més important: Horizon pot balancejar automàticament el nombre de workers per cua segons la càrrega, assignant més workers a les cues saturades i menys a les buides, sense cap intervenció manual.
Instal·lació i configuració inicial#
Horizon requereix Redis com a driver de cues. Si la teva aplicació utilitza un altre driver (database, SQS, Beanstalkd), Horizon no és compatible. Assegura't que tens Redis instal·lat i configurat abans de continuar.
composer require laravel/horizon
php artisan horizon:install
php artisan migrateLa comanda horizon:install crea diversos fitxers: el fitxer de configuració config/horizon.php, el service provider app/Providers/HorizonServiceProvider.php i els assets del dashboard. La migració crea les taules necessàries per emmagatzemar les mètriques i l'historial de jobs.
Assegura't que el fitxer .env utilitza Redis com a driver de cues:
QUEUE_CONNECTION=redisEl dashboard#
El dashboard d'Horizon és accessible a la ruta /horizon de la teva aplicació. Proporciona una visió completa de l'estat de les cues organitzada en diverses seccions que permeten entendre d'un cop d'ull el que passa a la infraestructura de cues.
La pantalla principal mostra el throughput (jobs processats per minut), el temps d'execució mitjà i el nombre de jobs en cada estat. La secció de jobs completats mostra l'historial de jobs processats amb el temps d'espera a la cua i el temps d'execució. La secció de jobs fallits mostra els errors amb l'excepció completa i el stack trace, i permet reintentar-los directament des de la interfície. La secció de jobs pendents mostra quants jobs estan esperant a cada cua.
Les mètriques són especialment valuoses. Horizon registra automàticament el throughput i el temps d'execució de cada tipus de job, cosa que permet identificar els jobs més lents o els que fallen amb més freqüència. Pots veure les mètriques dels darrers minuts, hores o dies per detectar patrons i tendències.
La secció de supervisors mostra l'estat de cada supervisor configurat: quants processos (workers) té actius, quines cues gestiona, quin és l'equilibri actual entre cues i si el supervisor està en execució, en pausa o aturat.
Configuració en profunditat#
El fitxer config/horizon.php és on defineixes com Horizon gestiona els workers. La configuració es basa en dos conceptes fonamentals: entorns i supervisors.
Entorns#
Horizon permet definir configuracions diferents per a cada entorn de l'aplicació. Això és essencial perquè les necessitats de producció i desenvolupament són molt diferents. En producció, probablement necessites 10 o més workers processant múltiples cues amb balanceig automàtic. En local, amb 2 o 3 workers processant una sola cua és suficient:
// config/horizon.php
'environments' => [
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['high', 'default', 'emails', 'reports'],
'balance' => 'auto',
'autoScalingStrategy' => 'time',
'minProcesses' => 1,
'maxProcesses' => 10,
'balanceMaxShift' => 1,
'balanceCooldown' => 3,
'tries' => 3,
'timeout' => 90,
'maxTime' => 3600,
'maxJobs' => 1000,
'memory' => 128,
],
],
'local' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['high', 'default'],
'balance' => 'simple',
'minProcesses' => 1,
'maxProcesses' => 3,
'tries' => 3,
'timeout' => 60,
],
],
],Horizon detecta automàticament l'entorn actual (APP_ENV) i aplica la configuració corresponent. Si l'entorn no té una entrada a la configuració, Horizon no arrencarà.
Supervisors#
Cada entorn pot tenir un o més supervisors. Un supervisor és un grup de processos worker que gestionen un conjunt de cues. La majoria d'aplicacions només necessiten un supervisor, però en casos avançats pots crear-ne múltiples per separar la gestió de cues amb característiques molt diferents.
Per exemple, si tens cues que processen jobs molt llargs (com la generació de vídeo) i cues que processen jobs molt curts (com l'enviament de correus), pot ser útil tenir supervisors separats amb timeouts i límits de memòria diferents:
'production' => [
// Supervisor per a jobs ràpids (correus, notificacions)
'supervisor-fast' => [
'connection' => 'redis',
'queue' => ['emails', 'notifications', 'default'],
'balance' => 'auto',
'minProcesses' => 2,
'maxProcesses' => 8,
'timeout' => 30,
'memory' => 64,
'tries' => 3,
],
// Supervisor per a jobs pesants (vídeo, informes, imports)
'supervisor-heavy' => [
'connection' => 'redis',
'queue' => ['video-processing', 'reports', 'imports'],
'balance' => 'auto',
'minProcesses' => 1,
'maxProcesses' => 4,
'timeout' => 600,
'memory' => 512,
'tries' => 2,
],
],Cada supervisor opera de manera independent. El supervisor supervisor-fast pot tenir fins a 8 workers amb un timeout de 30 segons, mentre que supervisor-heavy té fins a 4 workers amb un timeout de 10 minuts. Això evita que els jobs pesants bloquegin els workers que haurien d'estar processant correus ràpidament.
Opcions del supervisor#
Cada supervisor accepta múltiples opcions que controlen el seu comportament. Entendre cada opció és important per afinar el rendiment segons les necessitats de la teva aplicació:
connection-- La connexió Redis que utilitza el supervisor. Normalmentredis.queue-- Les cues que gestiona el supervisor. L'ordre importa quanbalanceésfalse: les cues es processen per ordre de prioritat.balance-- L'estratègia de balanceig de workers entre cues (auto,simpleofalse).minProcesses-- El nombre mínim de workers que el supervisor mantindrà actius, fins i tot si no hi ha feina.maxProcesses-- El nombre màxim de workers que el supervisor pot crear.balanceMaxShift-- Quants workers pot afegir o treure a la vegada durant un cicle de rebalanceig. Un valor baix fa transicions més suaus.balanceCooldown-- Quants segons esperar entre cicles de rebalanceig. Evita oscil·lacions ràpides.tries-- Nombre màxim de reintentos per job.timeout-- Segons màxims que un job pot executar-se abans de ser matat.maxTime-- Temps màxim en segons que un worker funciona abans de reiniciar-se. Evita fuites de memòria.maxJobs-- Nombre màxim de jobs que un worker processa abans de reiniciar-se.memory-- Límit de memòria en MB per worker.
Estratègies de balanceig#
L'estratègia de balanceig és una de les funcionalitats més potents d'Horizon. Determina com es distribueixen els workers entre les cues que gestiona un supervisor. Hi ha tres estratègies disponibles, cadascuna adequada per a situacions diferents.
Estratègia auto#
L'estratègia auto és la més intel·ligent i la recomanada per a producció. Horizon monitoritza contínuament la càrrega de cada cua i redistribueix els workers automàticament per equilibrar els temps d'espera. Si la cua emails acumula molts jobs, Horizon mou workers de les cues menys saturades cap a emails fins que el temps d'espera s'estabilitza.
'supervisor-1' => [
'balance' => 'auto',
'autoScalingStrategy' => 'time', // o 'size'
'minProcesses' => 1,
'maxProcesses' => 10,
'balanceMaxShift' => 1,
'balanceCooldown' => 3,
],L'opció autoScalingStrategy controla el criteri del balanceig. Amb time, Horizon optimitza per reduir el temps d'espera dels jobs a la cua. Amb size, optimitza per reduir el nombre de jobs pendents. En la majoria de casos, time és la millor opció perquè prioritza l'experiència de l'usuari.
Estratègia simple#
L'estratègia simple distribueix els workers de manera equitativa entre totes les cues. Si tens 6 workers i 3 cues, cada cua rep 2 workers, independentment de la càrrega. Aquesta estratègia és previsible i fàcil d'entendre, però no s'adapta a pics de càrrega:
'supervisor-1' => [
'balance' => 'simple',
'maxProcesses' => 6,
],Estratègia false#
Quan balance és false, Horizon no fa cap balanceig. Cada worker processa les cues en l'ordre definit a la configuració, igual que queue:work --queue=high,default,low. El primer worker agafa un job de la cua high; si està buida, prova amb default; si també està buida, prova amb low. Aquesta estratègia dona prioritat estricta a les primeres cues de la llista:
'supervisor-1' => [
'balance' => false,
'maxProcesses' => 4,
'queue' => ['high', 'default', 'low'],
],Executar Horizon#
Per iniciar Horizon localment, utilitza la comanda Artisan corresponent. Horizon arrencarà els supervisors configurats per a l'entorn actual i començarà a processar jobs:
php artisan horizonHorizon també proporciona comandes per controlar l'execució sense aturar el procés completament:
# Pausar el processament de jobs (els supervisors segueixen actius)
php artisan horizon:pause
# Reprendre el processament després d'una pausa
php artisan horizon:continue
# Pausar un supervisor específic
php artisan horizon:pause-supervisor supervisor-fast
# Reprendre un supervisor específic
php artisan horizon:continue-supervisor supervisor-fast
# Aturar Horizon elegantment (espera que els jobs actius acabin)
php artisan horizon:terminate
# Veure l'estat actual
php artisan horizon:statusLa comanda horizon:pause és útil quan necessites fer manteniment a la base de dades o a un servei extern del qual depenen els jobs. En lloc d'aturar Horizon completament, el pauses temporalment perquè no processi nous jobs. Els jobs que arriben durant la pausa s'acumulen a la cua i es processaran quan reprengueu el processament amb horizon:continue.
Desplegament en producció#
En producció, Horizon ha de funcionar com un dimoni (procés de llarga durada) que es reinicia automàticament si mor. L'eina estàndard per a això a Linux és Supervisor (no confondre amb els supervisors d'Horizon: Supervisor és un gestor de processos del sistema operatiu).
Crea un fitxer de configuració per a Supervisor:
# /etc/supervisor/conf.d/horizon.conf
[program:horizon]
process_name=%(program_name)s
command=php /var/www/app/artisan horizon
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/www/app/storage/logs/horizon.log
stopwaitsecs=3600A diferència de la configuració de queue:work, on necessites múltiples processos (un per worker), amb Horizon només necessites un sol procés Supervisor. Horizon gestiona internament tots els workers necessaris segons la configuració de config/horizon.php. Per això numprocs no s'especifica (el valor per defecte és 1).
L'opció stopwaitsecs=3600 és important: dona fins a una hora perquè Horizon acabi els jobs actius abans de forçar l'aturada. Si els teus jobs més llargs triguen 10 minuts, un valor de 900 (15 minuts) seria suficient amb marge.
# Carregar la nova configuració
sudo supervisorctl reread
sudo supervisorctl update
# Iniciar Horizon
sudo supervisorctl start horizon
# Verificar l'estat
sudo supervisorctl status horizonReiniciar després de desplegar#
Quan desplegueu codi nou, Horizon ha de reiniciar-se perquè els workers carreguin el codi actualitzat. La comanda horizon:terminate envia un senyal d'aturada elegant: Horizon espera que tots els jobs actius acabin i després es tanca. Supervisor detecta que el procés ha mort i el reinicia automàticament amb el codi nou.
Afegeix aquesta comanda al teu script de desplegament, després de les migracions i l'optimització de cache:
php artisan migrate --force
php artisan optimize
php artisan horizon:terminateNo utilitzis kill o supervisorctl restart per reiniciar Horizon. Això mataria els workers immediatament, potencialment interrompent jobs a meitat d'execució i deixant dades en un estat inconsistent. Sempre utilitza horizon:terminate, que fa una aturada elegant.
Notificacions#
Horizon pot enviar notificacions quan els temps d'espera a les cues superen un llindar configurable. Això és un indicador crític que alguna cosa no va bé: potser necessites més workers, potser un servei extern està lent i els jobs s'acumulen, o potser hi ha un job amb un error que bloqueja la cua.
Les notificacions es configuren al HorizonServiceProvider:
// app/Providers/HorizonServiceProvider.php
namespace App\Providers;
use Illuminate\Support\Facades\Gate;
use Laravel\Horizon\Horizon;
use Laravel\Horizon\HorizonApplicationServiceProvider;
class HorizonServiceProvider extends HorizonApplicationServiceProvider
{
public function boot(): void
{
parent::boot();
// Notificar per correu si un job espera més de 5 minuts
Horizon::routeMailNotificationsTo('ops@example.com');
// Notificar per Slack
Horizon::routeSlackNotificationsTo(
'https://hooks.slack.com/services/xxx/yyy/zzz',
'#alertes-cues'
);
// Notificar per SMS (necessita configuració de Nexmo/Vonage)
Horizon::routeSmsNotificationsTo('+34600000000');
}
}El llindar de temps d'espera es configura al fitxer config/horizon.php:
// config/horizon.php
// Temps d'espera màxim en segons abans de notificar
'waits' => [
'redis:high' => 30, // Notificar si un job 'high' espera 30s
'redis:default' => 60, // Notificar si un job 'default' espera 1 min
'redis:emails' => 120, // Notificar si un job 'emails' espera 2 min
],Cada entrada segueix el format connexió:cua. Pots definir llindars diferents per a cada cua segons la seva prioritat. La cua high hauria de tenir un llindar molt baix perquè els jobs d'alta prioritat s'han de processar immediatament. La cua emails pot tenir un llindar més alt perquè un petit retard en un correu no és tan crític.
Mètriques i tags#
Horizon registra automàticament mètriques per a cada tipus de job: throughput (jobs processats per minut), temps d'execució mitjà i temps d'espera a la cua. Aquestes mètriques apareixen al dashboard i permeten identificar els jobs més lents o problemàtics.
Tags#
Els tags permeten classificar els jobs per facilitar la cerca i el filtratge al dashboard. Horizon assigna automàticament tags basats en els models Eloquent que el job rep al constructor. Per exemple, si un job rep un model User, Horizon l'etiqueta automàticament amb App\Models\User:42 (on 42 és l'ID de l'usuari).
Pots afegir tags personalitzats sobreescrivint el mètode tags al job:
class ProcessOrder implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
public Order $order
) {}
public function tags(): array
{
return [
'order:' . $this->order->id,
'customer:' . $this->order->customer_id,
'type:' . $this->order->type,
];
}
public function handle(): void
{
// Processar la comanda
}
}Amb tags personalitzats, pots filtrar el dashboard per veure tots els jobs d'un client específic o d'un tipus de comanda concret. Això és molt útil per depurar problemes que afecten un subconjunt específic de jobs.
Seguretat del dashboard#
En producció, el dashboard d'Horizon ha d'estar protegit perquè mostra informació sensible sobre la infraestructura de l'aplicació. Per defecte, Horizon només és accessible en entorn local. Per permetre l'accés en altres entorns, has de definir una política d'autorització al gate del HorizonServiceProvider:
// app/Providers/HorizonServiceProvider.php
protected function gate(): void
{
Gate::define('viewHorizon', function ($user) {
return in_array($user->email, [
'admin@example.com',
'devops@example.com',
]);
});
}Pots fer la lògica tan complexa com necessitis. Per exemple, verificar un rol o un permís:
protected function gate(): void
{
Gate::define('viewHorizon', function ($user) {
return $user->hasRole('admin') || $user->hasPermission('view-horizon');
});
}Jobs silenciats#
Algunes aplicacions tenen jobs que s'executen molt freqüentment però que no aporten informació rellevant al dashboard (per exemple, un job de heartbeat que es dispara cada segon). Aquests jobs poden saturar el dashboard i dificultar la visualització dels jobs importants. Horizon permet silenciar-los perquè no apareguin a la llista:
// app/Providers/HorizonServiceProvider.php
public function boot(): void
{
parent::boot();
Horizon::silenced([
\App\Jobs\HeartbeatJob::class,
\App\Jobs\UpdateMetrics::class,
]);
}Els jobs silenciats es segueixen processant amb normalitat i les seves mètriques es continuen registrant, però no apareixen a la llista de jobs recents ni de jobs completats al dashboard.
Horizon vs queue:work#
Per ajudar-te a decidir quan necessites Horizon, aquí tens una comparació directa. Si la teva aplicació fa servir Redis per a les cues i els jobs són una part important de la infraestructura, Horizon és gairebé sempre la millor opció.
Amb queue:work estàndard, no tens dashboard visual, no tens mètriques automàtiques, no tens balanceig de workers i has de gestionar múltiples processos Supervisor manualment. Amb Horizon, tot això ve inclòs en un sol paquet que necessita un sol procés Supervisor.
L'únic cas on queue:work és preferible és si no fas servir Redis com a driver de cues. Horizon requereix Redis i no funciona amb database, SQS ni cap altre driver. Si fas servir database o SQS per a les cues, has d'utilitzar queue:work directament.
Bones pràctiques#
Configura llindars de notificació des del primer dia. No esperis a tenir un problema per configurar les alertes de temps d'espera. Definir llindars conservadors des del principi et permetrà detectar problemes abans que afectin els usuaris.
Utilitza l'estratègia auto en producció. L'estratègia simple és previsible però ineficient. L'estratègia auto adapta els recursos a la càrrega real i gairebé sempre ofereix millors resultats.
Separa supervisors per tipus de job quan sigui necessari. Si tens jobs que triguen segons i jobs que triguen minuts, posar-los sota el mateix supervisor amb el mateix timeout no és ideal. Crea supervisors separats amb configuracions adaptades.
Afegeix horizon:terminate al script de desplegament. Oblidar-se de reiniciar Horizon després d'un desplegament és un error comú que provoca que els workers segueixin executant codi antic.
Revisa el dashboard regularment. Les mètriques d'Horizon són una font d'informació valuosa per optimitzar els jobs. Si un job triga molt, potser pot ser optimitzat. Si una cua acumula molts jobs, potser necessita més workers o un supervisor dedicat.