Uma fila assíncrona evita que uma requisição web espere a conclusão de tarefas custosas, mas não resolve por si só a concorrência entre trabalhos. O problema surge quando uma importação, um reprocessamento ou uma campanha cria milhares de mensagens e ocupa todos os consumidores. Uma ação com impacto imediato — confirmar um pedido, reservar estoque, bloquear uma conta ou enviar uma notificação transacional — fica atrás de um trabalho que pode esperar.
Gerenciar prioridades em filas PHP não consiste apenas em adicionar um campo numérico à mensagem. É uma decisão de arquitetura que deve refletir o fluxo de negócio, proteger dependências limitadas e manter um comportamento previsível quando a carga aumenta.
Classifique o trabalho por impacto, prazo e custo

Antes de criar filas, construa um inventário de trabalhos assíncronos. Para cada um, identifique quem o inicia, qual dependência utiliza, quanto costuma durar, qual prazo de negócio possui e o que acontece se atrasar. A urgência não equivale necessariamente à importância: uma conciliação financeira pode ser muito importante, mas tolerar várias horas de espera; uma validação de pagamento pode exigir uma resposta rápida embora sua execução seja breve.
Uma classificação útil costuma incluir quatro classes de serviço:
- Crítica: ações que protegem dinheiro, segurança, consistência ou compromissos imediatos. Devem ter uma espera-alvo muito baixa e capacidade reservada.
- Interativa: trabalho iniciado por uma pessoa ou necessário para concluir uma experiência próxima do tempo real, como gerar um documento solicitado pela aplicação.
- Adiada: tarefas necessárias, mas sem prazo imediato, como sincronizações periódicas, resumos ou atualizações de índices.
- Em massa: importações, migrações, reindexações, campanhas e reprocessamentos. Seu volume ou custo exige limitar seu ritmo mesmo quando não há outra carga.
Registre também o custo por trabalho. Uma mensagem que chama uma API com cota limitada, executa uma consulta intensiva ou processa um arquivo grande não deve competir da mesma forma que uma atualização local breve. A classe de serviço deve expressar o prazo e o tipo de pressão que o trabalho gera sobre o sistema.
Separe filas quando precisar de isolamento real
Uma única fila com prioridades pode servir se os trabalhos tiverem uma execução homogênea, usarem as mesmas dependências e o transporte oferecer uma prioridade confiável. No entanto, a ordem de consumo não garante por si só que exista capacidade: um trabalho em massa que já esteja em execução continuará ocupando um worker, uma conexão ou uma cota externa.
Separe filas quando existir algum destes limites:
- Os trabalhos críticos e em massa têm objetivos de espera claramente distintos.
- Um tipo de trabalho acessa uma dependência frágil ou com limites de taxa, como uma API de pagamentos, e-mail ou ERP.
- A duração é muito desigual e os trabalhos longos retêm processos por tempo demais.
- É necessário controle independente de deploy, pausa, nova tentativa ou escalonamento.
- Um erro ou uma entrada anômala de um fluxo não deve degradar outro fluxo.
Em uma aplicação PHP, o padrão mais legível costuma ser rotear as mensagens para filas explícitas, por exemplo critical, interactive, deferred e bulk. O componente de mensageria pode ser Symfony Messenger, Laravel Queues ou uma integração própria com o broker escolhido; o princípio não depende do framework. A prioridade dentro de uma fila pode complementar essa separação para ordenar trabalhos semelhantes, e não substituir o isolamento entre classes incompatíveis.
Defina capacidade reservada e concorrência máxima
Atribua consumidores por classe e estabeleça tanto mínimos quanto máximos operacionais. A fila crítica precisa de capacidade que não possa ser absorvida por importações. A fila em massa, por outro lado, deve ter um máximo de concorrência para não saturar banco de dados, CPU, armazenamento ou provedores externos.
Evite configurar todos os workers para ler todas as filas com preferência absoluta pela crítica. Essa abordagem pode deixar capacidade ociosa se os consumidores reservados não puderem assumir outros trabalhos, ou provocar inanição se puderem fazê-lo sem regras. Uma alternativa prática é combinar:
- Workers dedicados à crítica e à interativa.
- Workers compartilhados que atendam às filas adiadas e em massa segundo cotas.
- Limites por tipo de dependência, não apenas pelo número total de processos.
- Escalonamento baseado na profundidade da fila e na idade das mensagens, não exclusivamente no uso de CPU.
O número correto não é universal. Ele deve partir da concorrência que o banco de dados e as APIs toleram, da duração observada e do objetivo de espera de cada classe.
Evite inanição e aplique backpressure
Dar preferência ao urgente não significa que o trabalho adiado nunca deva terminar. Se sempre houver mensagens críticas, uma política de prioridade estrita pode produzir inanição: os trabalhos de nível inferior envelhecem indefinidamente. Estabeleça uma regra de fairness mensurável, como processar uma cota de mensagens adiadas após um número limitado de críticas, ou reservar uma pequena fração da capacidade para trabalho não urgente.
A regra deve respeitar os limites das dependências. Se crítica e em massa escrevem na mesma tabela com bloqueios custosos, executá-las em paralelo pode piorar a latência. Nesse caso, a cota deve ser aplicada ao recurso compartilhado ou convém redesenhar o trabalho em lotes menores.
O backpressure surge quando entra mais trabalho do que pode ser concluído. Isso não se resolve aumentando indefinidamente os workers. Defina como reagir:
- Limite o tamanho, a frequência ou a concorrência das importações na origem.
- Divida lotes em unidades retomáveis e controle quantas são publicadas de uma vez.
- Adie o trabalho adiado com um agendamento explícito quando a fila ou uma dependência ultrapassar um limite.
- Respeite respostas de limite de taxa com pausas e novas tentativas adiadas, em vez de novas tentativas imediatas.
- Comunique ao produto quando uma operação é aceita para processamento e quando está realmente concluída.
É importante distinguir aceitação de execução: informar que uma importação foi recebida não implica que ela possa ser iniciada imediatamente. Essa transparência evita que uma mudança técnica seja interpretada como uma promessa de disponibilidade instantânea.
Controle novas tentativas, lentidão e idempotência
As novas tentativas consomem capacidade e podem se tornar uma carga prioritária acidental. Classifique os erros entre transitórios e permanentes. Uma interrupção temporária de rede pode justificar uma nova tentativa com espera crescente e dispersão temporal; uma validação inválida, um recurso inexistente ou uma credencial revogada deve seguir para um circuito de revisão, e não se repetir sem fim.
Estabeleça o tempo máximo de execução por tipo de trabalho. Um job lento não deve reter um worker indefinidamente. Se puder ser dividido, processe páginas, arquivos ou segmentos em mensagens independentes que registrem o progresso. Caso contrário, use limites estritos, cancelamento seguro e um procedimento para revisar os trabalhos esgotados.
A prioridade aumenta o risco de repetir efeitos quando um produtor reenvia uma mensagem ou um consumidor falha depois de chamar uma API externa. Projete handlers idempotentes: use uma chave estável de operação, persista o estado da transição e faça com que processar duas vezes tenha o mesmo efeito de negócio que processar uma vez. A deduplicação do broker pode reduzir duplicados, mas não substitui a idempotência na aplicação nem nas integrações externas.
Observe a espera, não apenas o tamanho da fila
Uma fila curta pode ocultar um problema se suas mensagens mais antigas esperarem tempo demais ou se os consumidores falharem constantemente. Meça, por classe de serviço, a idade da mensagem mais antiga, o tempo entre a publicação e o início, a duração da execução, a porcentagem de erros, as novas tentativas e os trabalhos enviados para revisão.
Complete essas métricas com concorrência ativa, profundidade, taxa de entrada e saída, uso de conexões, tempos das dependências e limites de taxa recebidos. Os sinais mais úteis são: a espera crítica supera seu objetivo, a fila em massa cresce enquanto sua cota está limitada, as novas tentativas dominam o tráfego ou a capacidade reservada fica ociosa durante picos de outra classe.
Configure alertas sobre tendências e objetivos de serviço, não apenas sobre um número fixo de mensagens. Mil mensagens podem ser normais em uma importação; dez podem ser graves se pertencerem a confirmações de pedido que estão esperando há vários minutos.
Exemplo de fluxo compartilhado e lista de implantação

Imagine uma plataforma que processa pedidos urgentes, notificações e uma importação em massa de catálogo. Os pedidos são roteados para critical; as notificações transacionais, para interactive; e a importação é fragmentada em páginas enviadas para bulk. Os workers de pedidos têm capacidade reservada. A importação tem concorrência limitada e reduz seu ritmo se a latência do banco de dados aumentar. As notificações respeitam a cota do provedor, com novas tentativas adiadas. Se um processo se repetir, a chave de operação evita criar duas reservas ou enviar duas mudanças de estado.
Para introduzir este modelo em uma aplicação existente:
- Faça o inventário dos handlers e atribua uma classe de serviço com base em prazo, impacto e dependência.
- Meça duração, espera e erros antes de alterar o roteamento.
- Separe primeiro os fluxos críticos dos fluxos em massa e reserve capacidade mínima.
- Defina limites de concorrência por dependência e políticas de backpressure.
- Torne os efeitos de negócio idempotentes e limite novas tentativas e tempos de execução.
- Teste picos de carga, queda de provedores e uma entrada em massa antes de ativar a nova distribuição.
- Revise periodicamente cotas e classes: uma prioridade é uma política de negócio que muda com o produto.
O resultado buscado não é que tudo seja prioritário, mas que cada trabalho receba uma capacidade e um prazo coerentes, sem transformar uma operação de grande volume em um bloqueio para o restante do negócio.



