Desenvolvimento e bases de dados

Processos persistentes: workers de filas, Horizon e comandos em execução contínua

Revisto a 15 min de leitura processos filas Laravel Horizon

Algumas aplicações precisam de um programa que nunca termine: um worker que processe a fila assim que chega um trabalho, o Horizon, um consumidor de mensagens ou um bot. Uma tarefa cron arranca, faz o seu trabalho e termina; um processo persistente fica em execução e, se cair, volta a arrancar sozinho.

Estão disponíveis nos planos Avanza, Max e Elastic:

Plano Processos persistentes
Avanza 3
Max 5
Elastic 8

No Inicia, no Webmaster e nas contas de um alojamento multiconta não há processos persistentes. As filas podem ser processadas na mesma com o scheduler ou com uma tarefa cron, com até um minuto de espera: explicamo-lo em Instalar o Laravel e em tarefas cron.

Que tipo escolher

  • Worker de filas: executa php artisan queue:work com as filas que indicar. Para trabalhos do Laravel na base de dados ou no Redis, sem Horizon.
  • Horizon: executa php artisan horizon. Para filas no Redis geridas pelo Horizon, com o seu painel e as suas métricas.
  • Redis persistente: Valkey 8, compatível com Redis, para filas, sessões e Horizon. Ativa-se no Gestor de pacotes.
  • Comando próprio: uma linha de shell. Para o Symfony Messenger, consumidores de mensagens, bots ou qualquer programa que não receba ligações.

Com o Horizon, não acrescente também um worker de filas para as mesmas filas: o Horizon arranca e distribui os seus próprios workers. O Horizon precisa de filas no Redis, e para isso existe o Redis persistente: ocupa um dos processos do plano.

Um processo persistente destina-se a trabalhos em segundo plano: filas, Horizon, o Redis persistente, que se liga por socket, ou um script próprio. Não se destina a servidores que recebem ligações: não damos suporte a programas que escutem numa porta, como um servidor Node.js, uma aplicação Python, o Laravel Reverb ou outro servidor de WebSockets. Para WebSockets no Laravel, utilize um serviço compatível com o Pusher, como explicamos em WebSockets no Laravel; se o seu projeto precisar do seu próprio servidor, precisa de um VPS.

Criar um processo

  1. Na área de cliente, abra Alojamento, entre no seu serviço e vá a Ferramentas > Processos.
  2. No bloco Processos persistentes, clique em Adicionar processo e escolha o tipo.
  3. Indique a pasta da aplicação, relativa à sua home e sem /home/usuario. Se implementar com o Deploy+, é deploy/miapp/current, com o nome do seu repositório em vez de miapp; se instalou a aplicação manualmente, é a pasta do projeto, por exemplo mi-proyecto. Num comando próprio é opcional: sem pasta, arranca na sua home.
  4. No worker de filas e no Horizon, escolha a Versão de PHP: da 8.1 em diante. O processo usa o binário dessa versão, por exemplo /opt/alt/php84/usr/bin/php para o PHP 8.4, e não o php que encontrar no terminal, que pode ser outro. Escolha a mesma que o seu site utiliza e ative nessa versão as extensões de que a aplicação precisa, no seletor de PHP.
  5. Guarde. O processo aparece como A arrancar e passa a Em curso em menos de um minuto.

Worker de filas

Indique as filas separadas por vírgulas, por ordem de prioridade: com high,default, o worker esvazia primeiro high. Se não indicar outra coisa, processa default com a ligação de QUEUE_CONNECTION do seu .env. Estas são as opções predefinidas:

php artisan queue:work --queue=default --sleep=3 --tries=3 --max-time=3600 --memory=256
  • --sleep=3: com a fila vazia, espera 3 segundos antes de voltar a verificar.
  • --tries=3: três tentativas por trabalho antes de o dar como falhado. Os falhados ficam em failed_jobs; pode vê-los com php artisan queue:failed e repeti-los com php artisan queue:retry.
  • --max-time=3600: ao fim de uma hora, o worker termina e volta a arrancar num segundo. Assim liberta memória e carrega o código novo, mesmo que ninguém o reinicie.
  • --memory=256: se passar de 256 MB, termina ao acabar o trabalho em curso e volta a arrancar.

Para filas com muito volume, pode criar vários workers sobre as mesmas filas, um por cada processo do plano. Comece com um e acrescente outro só se os trabalhos se acumularem.

Comando próprio

Escreva uma única linha, tal como a escreveria no terminal, com caminhos absolutos para o PHP e para o que não estiver na pasta escolhida. Por exemplo, um consumidor do Symfony Messenger:

/opt/alt/php84/usr/bin/php bin/console messenger:consume async --time-limit=3600 --memory-limit=256M

Um comando próprio pode lançar um script Python ou de outra linguagem disponível na conta para um trabalho em segundo plano, mas só damos suporte oficial às aplicações PHP: o que não for PHP fica por sua conta.

O programa tem de ficar em primeiro plano. Não acrescente &, nohup nem opções que o enviem para segundo plano: o vigilante vê-lo-ia terminar de imediato, voltaria a lançá-lo e acabaria Com falha, com várias cópias soltas. Tal como com --max-time no Laravel, convém que o programa termine de vez em quando por si próprio para libertar memória: volta a arrancar sozinho.

Como se mantém em execução

  • Um vigilante verifica a cada minuto os processos da conta e arranca os que estiverem em baixo. Vê-lo-á nas suas tarefas cron como uma linha que termina em # hostsuar-procesos: não a edite nem a apague; é gerida em Processos.
  • Se o programa terminar, por --max-time, por memória ou após um php artisan queue:restart, volta a arrancar num segundo. Entra novamente na pasta em cada arranque, por isso, depois de um reinício, carrega a versão publicada nesse momento.
  • Se cair 5 vezes seguidas logo ao arrancar, antes de chegar aos 30 segundos, fica Com falha e não volta a tentar até que o reinicie a partir do painel. Entre uma tentativa e a seguinte espera um pouco mais de cada vez, para não gastar a CPU da conta num programa que não consegue arrancar.
  • É executado com o utilizador da sua conta, dentro dos seus recursos: a CPU, a memória e o número de processos do plano são os mesmos que os seus sites usam. Se a conta atingir o limite de memória, o sistema pode interromper o processo e o vigilante volta a arrancá-lo. Consulte o consumo em utilização de CPU e recursos.

Estados e ações

  • Em curso: o programa está ativo há alguns segundos sem cair.
  • A arrancar: foi pedido e ainda não consta como ativo, ou acabou de arrancar; o vigilante arranca-o em menos de um minuto.
  • A tentar novamente: está a cair logo ao arrancar e o vigilante volta a lançá-lo. Se cair 5 vezes seguidas, fica Com falha. A razão está no seu registo.
  • Parado: foi parado por si e não arranca sozinho.
  • Com falha: caiu 5 vezes seguidas ao arrancar e aguarda que o reinicie.

Em cada processo pode:

  • Reiniciar: para-o e arranca-o no momento, também se estiver Com falha.
  • Parar e Arrancar: parado, não volta a ser lançado, mas continua na lista e mantém a sua configuração.
  • Ver registo: as últimas linhas do que o programa escreveu, com uma linha arranca em cada arranque e outra terminó con el código N tras S s (terminou com o código N após S s) sempre que termina. Quando o registo passa de 1 MB, é cortado e mantém a parte mais recente.
  • Editar: ao guardar, o processo é reiniciado com as alterações.
  • Eliminar: para-o e retira-o da conta.

No Laravel, os erros dos trabalhos também ficam em storage/logs/laravel.log, e os trabalhos falhados em failed_jobs.

Se um processo ficar «Com falha»

Abra o seu registo: quase sempre a última linha antes de cada terminó con el código explica a causa. As mais habituais:

  • «no existe la carpeta de trabajo» (a pasta de trabalho não existe): a pasta não está correta. Lembre-se de que é relativa à sua home, sem /home/usuario.
  • Falta uma extensão de PHP, como redis, ou pcntl e posix no Horizon: ative-a na versão de PHP do processo, que pode não ser a do seu site.
  • A aplicação não liga ao Redis: o .env aponta para um Redis que não está ativo ou para outro socket.
  • Um comando próprio que passa para segundo plano ou que termina de imediato porque não tem nada para fazer.
  • Um erro ao arrancar a aplicação, por exemplo após uma implementação com uma dependência em falta.

Corrija a causa e clique em Reiniciar. Enquanto estiver Com falha, não consome recursos.

Implementações e processos persistentes

Um worker carrega o código ao arrancar e mantém-no em memória, por isso tem de ser reiniciado para usar uma versão nova.

  • Com o Deploy+ não tem de fazer nada: assim que termina cada implementação, e também ao voltar a uma versão anterior, reinicia os processos persistentes cuja pasta é o current dessa aplicação ou uma pasta dentro dele. Cada worker termina o trabalho em curso e volta a arrancar num segundo com o código novo, e o registo da implementação indica como ficou cada um. Não coloque queue:restart nem horizon:terminate no script: este é executado antes de ativar a versão e o worker voltaria a arrancar sobre a anterior. Na ficha do repositório no Deploy+ pode adicionar o worker de filas dessa aplicação com a pasta já preenchida.
  • Se publicar de outra forma (Git manualmente, FTP), clique em Reiniciar no processo quando terminar, ou execute php artisan queue:restart, php artisan horizon:terminate ou php bin/console messenger:stop-workers quando o código novo já estiver no lugar. O Laravel avisa os workers através da sua cache: o comando e os workers têm de usar o mesmo CACHE_STORE.

SSH enquanto houver processos

Enquanto a conta tiver processos persistentes, mesmo que estejam parados, o acesso SSH fica ativado e não pode ser desativado: ao desligá-lo, todos os processos da conta seriam encerrados. Se quiser desativar o SSH, elimine antes os processos persistentes. Os processos não precisam que abra nenhuma sessão.

Mudar de plano

  • Para um plano com menos processos: param os mais recentes que não caibam, e a memória do Redis persistente ajusta-se ao máximo do novo plano.
  • Para um plano sem processos persistentes: param todos e continuam na lista, para que veja o que tinha. Se voltar ao Avanza, Max ou Elastic, pode arrancá-los novamente.

Antes de mudar de plano, prepare a aplicação para processar as suas filas sem eles.

Também lhe pode ser útil

Algo não bate certo ou mudou? Diga-nos e revemos.