Capítulo 13: Filas Assíncronas (Queues) com Redis e Laravel Horizon
Especialização em Backend com PHP & Laravel • Laravel 11 & PHP 8.3+ • Eloquent ORM, Sanctum, Filas, Horizon e APIs Corporativas
🗺️ Mapa Conceitual do Tópico
flowchart TD
A["Controller dispara Job::dispatch()"] --> B["Serialização do Job (SerializesModels)"]
B --> C["Fila no Redis"]
C --> D["Worker (queue:work) puxa o Job"]
D --> E{"handle() lançou exceção?"}
E -->|Não| F["Job concluído"]
E -->|Sim, tries restantes| G["Reenfileira após backoff"]
G --> D
E -->|Sim, esgotou tries| H["failed_jobs + método failed()"]
I["Laravel Horizon"] -.monitora.-> C
I -.monitora.-> D
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style C fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style D fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style F fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style H fill:#ffebee,stroke:#c62828,stroke-width:2px
style I fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
🏛️ 1. Por que Enfileirar? Latência de Requisição vs. Trabalho Pesado
Qualquer tarefa que não precise de resposta imediata ao usuário (envio de e-mail, geração de PDF, chamada a uma API externa lenta, processamento de imagem) não deveria bloquear a thread da requisição HTTP. Um Job é uma classe PHP simples que implementa ShouldQueue; ao ser despachado, o Laravel serializa a instância (incluindo os Models Eloquent envolvidos, via trait SerializesModels, que grava apenas a chave primária e a recarrega do banco no worker) e publica esse payload na fila configurada:
<?php
namespace App\Jobs;
use App\Models\Pedido;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProcessarFaturaJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Pedido $pedido) {}
public function handle(): void
{
// Gera PDF, calcula impostos, chama gateway de pagamento, etc.
}
}
O despacho é feito com ProcessarFaturaJob::dispatch($pedido). É possível atrasar a execução (->delay(now()->addMinutes(5))) ou direcionar para uma fila nomeada (->onQueue('faturas')), permitindo priorização.
⚠️ 2. Retries, Backoff e o Método failed() — o Gotcha Real de Produção
Um worker (php artisan queue:work) roda em processo de longa duração e não recarrega automaticamente o código PHP alterado — depois de um deploy, os workers precisam ser reiniciados (queue:restart) ou perderão a nova versão do código.
Falhas transitórias (timeout de rede, API externa fora do ar) não devem descartar o job imediatamente. As propriedades $tries e $backoff controlam quantas vezes e com qual intervalo o job será reprocessado:
<?php
public int $tries = 3;
public array $backoff = [10, 30, 60]; // 10s antes da 2ª tentativa, 30s antes da 3ª...
public function failed(\Throwable $exception): void
{
\Log::error("Falha permanente ao processar fatura do pedido {$this->pedido->id}", [
'erro' => $exception->getMessage(),
]);
// Notificar equipe, gravar em failed_jobs (automático), etc.
}
Quando as tentativas se esgotam, o Laravel move o registro para a tabela failed_jobs e chama failed() — esse é o único lugar confiável para lógica de compensação (notificar o cliente, reverter estado), porque nesse ponto o job não será tentado novamente automaticamente. É possível reprocessar manualmente com php artisan queue:retry {id} ou --all.
📊 3. Laravel Horizon: Observabilidade sobre Filas Redis
Horizon substitui a operação “às cegas” de queue:work por um painel com throughput, taxa de falhas, tempo de execução por job e wait time por fila, além de gerenciar múltiplos processos de worker por meio de supervisors configurados em config/horizon.php:
<?php
// config/horizon.php
return [
'defaults' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['default', 'faturas'],
'balance' => 'auto', // ajusta processos dinamicamente por carga
'minProcesses' => 1,
'maxProcesses' => 10,
],
],
];
Horizon exige a conexão de fila configurada como redis (não funciona com o driver database ou sync) e roda como processo separado (php artisan horizon), tipicamente supervisionado por Supervisor ou systemd em produção.