Solução de Problemas de Processamento Lento de Mensagens: Identificando Gargalos no RabbitMQ

Diagnostique lentidões no RabbitMQ separando gargalos de produtor, broker, fila, consumidor, disco e confirmação.

Solução de Problemas de Processamento Lento de Mensagens: Identificando Gargalos no RabbitMQ

Quando uma fila do RabbitMQ acumula, a fila está apenas mostrando o sintoma. O gargalo pode ser um consumidor lento, um publicador bloqueado, um alarme de disco, um valor de prefetch ruim, uma carga útil de mensagem enorme ou um banco de dados downstream que silenciosamente começou a expirar. Reiniciar o RabbitMQ pode limpar o gráfico por alguns minutos, mas raramente corrige o motivo pelo qual as mensagens estavam lentas.

O caminho mais rápido para solucionar problemas é separar o fluxo em partes: publicação no RabbitMQ, roteamento para filas, armazenamento de mensagens, entrega aos consumidores, processamento do trabalho e confirmação da conclusão. Cada parte deixa evidências diferentes.

Primeira divisão: prontas ou não confirmadas

Comece com os contadores da fila:

rabbitmqctl list_queues name messages_ready messages_unacknowledged consumers state

messages_ready significa que as mensagens estão na fila aguardando entrega. Se esse número crescer, o RabbitMQ não tem consumidores disponíveis, os consumidores estão no limite de prefetch ou a entrega está sendo bloqueada por outra condição.

messages_unacknowledged significa que as mensagens já foram entregues aos consumidores e o RabbitMQ está aguardando um ack, nack, rejeição ou fechamento de canal. Se esse número crescer, o gargalo geralmente está dentro do consumidor ou em algo que o consumidor chama.

Essa distinção é importante. Se as mensagens prontas são altas e as não confirmadas são baixas, adicionar mais memória ao broker não fará os consumidores aparecerem. Se as não confirmadas são altas, adicionar mais partições de fila pode não ajudar porque o trabalho já saiu da fila.

Verifique se os consumidores estão realmente presentes

Um número surpreendente de incidentes de "RabbitMQ está lento" são na verdade incidentes de "os consumidores não estão rodando". A implantação falhou, a autoescalabilidade foi a zero, as credenciais mudaram, o virtual host errado foi usado ou o serviço está conectado ao ambiente de staging enquanto os produtores publicam no de produção.

Use:

rabbitmqctl list_consumers queue_name channel_pid consumer_tag ack_required prefetch_count active

Se não houver consumidores, corrija isso primeiro. Se os consumidores estiverem presentes, mas inativos, verifique os logs do aplicativo e o estado da conexão. Se cada consumidor tem um prefetch de 1 e cada mensagem leva vários segundos, a baixa concorrência de entrega pode ser esperada. Se cada consumidor tem um prefetch de 500 e as não confirmadas são enormes, os consumidores podem estar acumulando trabalho que não conseguem concluir rapidamente.

Meça o tempo de processamento do consumidor

O RabbitMQ pode informar que as mensagens não estão confirmadas. Ele não pode informar se o consumidor está analisando uma carga útil gigante, aguardando o PostgreSQL, repetindo uma chamada HTTP ou travado em um bloqueio.

Adicione temporização ao redor do manipulador real:

message_received_at
decode_ms
business_logic_ms
database_ms
external_api_ms
ack_ms
message_completed_at

Você não precisa de um sistema de rastreamento perfeito para aprender algo. Mesmo alguns campos de log estruturados podem mostrar que o manipulador normalmente leva 80 ms, mas agora gasta 4 segundos aguardando uma API downstream.

Se o trabalho é lento, mas paralelizável, adicione instâncias de consumidor ou aumente a concorrência interna do trabalhador. Se o sistema downstream é o limite, adicionar consumidores pode piorar as coisas. Você pode precisar de limitação de taxa, loteamento, cache ou uma fila de repetição separada.

Ajuste o prefetch depois de entender o manipulador

O prefetch controla quantas mensagens não confirmadas o RabbitMQ pode enviar para cada consumidor. Ele está frequentemente envolvido em incidentes de processamento lento porque muda onde o backlog é visível.

Com prefetch baixo, as mensagens permanecem prontas no RabbitMQ até que um consumidor esteja pronto para mais. Isso é justo e fácil de observar, mas pode subutilizar consumidores muito rápidos.

Com prefetch alto, as mensagens se movem rapidamente para os consumidores. Isso pode melhorar a taxa de transferência para manipuladores rápidos, mas também pode ocultar a latência. Um consumidor lento com um valor de prefetch grande pode ficar com centenas de mensagens enquanto outros consumidores ficam sem trabalho.

Uma ação prática em incidentes é reduzir o prefetch para consumidores lentos ou instáveis e observar se a latência final melhora. Para consumidores rápidos com baixo uso de CPU e altas contagens de prontas, aumente o prefetch com cautela e meça novamente.

Procure por gargalos no lado do publicador

Às vezes, a fila não está acumulando porque os consumidores estão lentos. Ela está acumulando porque os produtores publicam em rajadas e depois esperam ineficientemente por confirmações.

As confirmações do publicador são a ferramenta certa quando os publicadores precisam saber que o RabbitMQ aceitou as mensagens. O padrão lento é esperar por cada confirmação antes de publicar a próxima mensagem. Isso transforma cada publicação em uma viagem de ida e volta.

Padrões melhores usam confirmações assíncronas ou lotes limitados. O publicador pode manter um número limitado de mensagens em trânsito, lidar com nacks e ainda evitar bloquear em cada mensagem individual. O limite é importante. Publicações ilimitadas em trânsito podem mover o gargalo para a memória do publicador ou pressão no broker.

Verifique as métricas do publicador: taxa de publicação, latência de confirmação, contagem de confirmações em trânsito, reconexões, mensagens retornadas e exceções de canal. Na interface de gerenciamento, compare as taxas de publicação com as taxas de entrega/confirmação. Se a publicação estiver baixa mesmo com o aplicativo ocupado, o produtor pode estar aguardando confirmações, transações ou rotatividade de conexão.

Evite transações AMQP para publicação de alta taxa de transferência, a menos que haja um motivo específico. Elas são muito mais caras do que as confirmações do publicador para publicação confiável típica.

Verifique o disco antes de culpar o RabbitMQ

Mensagens persistentes, filas quorum, streams e grandes backlogs envolvem disco. Quando a latência do disco aumenta, o fluxo de mensagens pode desacelerar drasticamente.

No nó RabbitMQ, verifique:

rabbitmq-diagnostics status
rabbitmq-diagnostics alarms
rabbitmq-diagnostics memory_breakdown

No nível do sistema operacional, use ferramentas como iostat, vmstat ou seus gráficos de monitoramento em nuvem. Observe a latência do disco e a espera de E/S, não apenas a taxa de transferência. Um disco em nuvem que esgotou os créditos de burst pode parecer normal na configuração e terrível na prática.

Se o disco for o gargalo, as correções possíveis incluem armazenamento mais rápido, menos gravações persistentes, mensagens menores, melhor loteamento de confirmações do publicador, divisão de filas ou movimentação de cargas de trabalho do tipo replay para streams ou outro sistema orientado a logs. Não desative a persistência para mensagens que a empresa não pode perder apenas para deixar um gráfico verde.

Verifique alarmes e conexões bloqueadas

O RabbitMQ se protege com alarmes de memória e disco. Quando um alarme está ativo, os publicadores podem ser bloqueados. Isso pode parecer lentidão do aplicativo do lado do produtor.

Execute:

rabbitmq-diagnostics alarms
rabbitmqctl list_connections name user state channels send_pend recv_cnt send_cnt

Se os alarmes de memória estiverem ativos, descubra se a memória está sendo usada por filas, conexões, mensagens não confirmadas, binários ou plugins. Se os alarmes de disco estiverem ativos, libere espaço ou adicione capacidade antes de tentar enviar mais mensagens pelo broker.

Conexões bloqueadas não são um bug por si só. Elas são o RabbitMQ dizendo aos publicadores para desacelerar porque o nó está protegendo a disponibilidade.

O tamanho da mensagem pode ser o culpado silencioso

Um sistema que lida com 10.000 mensagens pequenas por segundo pode ter dificuldades com 500 mensagens grandes por segundo. Cargas úteis grandes aumentam a transferência de rede, pressão na memória, gravações em disco, trabalho de coleta de lixo e tempo de processamento do consumidor.

Se as mensagens contêm documentos grandes, imagens, relatórios ou arrays grandes, considere armazenar a carga útil em armazenamento de objetos ou um banco de dados e enviar uma referência pelo RabbitMQ. Inclua metadados suficientes para roteamento e idempotência, mas mantenha o broker fora da função de armazenamento em massa quando possível.

Verifique também as escolhas de compressão. Comprimir cargas úteis enormes pode reduzir o uso de rede e disco, mas aumentar a CPU. Se isso ajuda depende de onde está o gargalo.

As repetições podem criar o gargalo

Um serviço downstream com falha pode transformar uma mensagem em muitas tentativas. Se os consumidores reenfileirarem imediatamente as falhas, eles podem processar as mesmas mensagens ruins repetidamente enquanto o trabalho novo espera. A profundidade da fila pode aumentar, a CPU pode parecer ocupada e muito pouco trabalho útil é feito.

Procure por altas taxas de reentrega e logs de erro repetidos com os mesmos IDs de mensagem. Se a mesma carga útil falha repetidamente, mova-a para fora do fluxo principal. Uma exchange de letra morta, fila de repetição atrasada ou mecanismo de repetição agendada dá tempo para a dependência se recuperar e impede que mensagens venenosas bloqueiem o trabalho normal.

Cuidado com tempestades de repetição. Se uma API está fora por dez minutos e cada mensagem repete a cada segundo, a recuperação se torna mais difícil quando a API volta. Use backoff. Limite as tentativas. Torne a falha final visível em uma fila de letra morta com contexto suficiente para investigar.

A idempotência também faz parte da solução de problemas de desempenho. Se um consumidor repete após concluir parcialmente o trabalho, duplicatas podem criar contenção no banco de dados, erros de chave única ou chamadas downstream extras. Um manipulador que pode processar com segurança a mesma mensagem duas vezes é muito mais fácil de escalar e recuperar.

As taxas da interface de gerenciamento precisam de contexto

A interface de gerenciamento do RabbitMQ é útil, mas os gráficos de taxa podem enganar se você ler uma linha sozinha. Uma alta taxa de entrega com uma baixa taxa de confirmação significa que o trabalho está sendo distribuído mais rápido do que está sendo concluído. Uma alta taxa de confirmação com uma alta contagem de prontas pode significar que os consumidores estão trabalhando, mas não o suficiente para alcançar. Uma baixa taxa de publicação durante um incidente pode significar que os produtores estão bloqueados ou aguardando confirmações.

Observe várias taxas juntas:

  • publish: mensagens entrando nas exchanges.
  • deliver/get: mensagens enviadas aos consumidores.
  • ack: mensagens concluídas pelos consumidores.
  • redeliver: mensagens sendo entregues novamente após falha anterior ou fechamento de canal.

Para uma fila de trabalho saudável e estável, as taxas de publicação e confirmação devem estar próximas ao longo do tempo. Rajadas curtas são normais. Longas lacunas significam que o backlog está acumulando ou diminuindo. Se as reentregas aumentarem drasticamente, não adicione apenas mais consumidores. Descubra por que as mensagens estão voltando.

As janelas de amostragem importam. Um gráfico de um minuto pode ocultar uma parada de cinco segundos que prejudica os usuários. Um gráfico de um segundo pode fazer a normalidade parecer caótica. Corresponda a janela do gráfico à latência que seus usuários ou sistemas downstream se importam.

Separe o backlog normal do backlog quebrado

Nem todo backlog é uma emergência. Um sistema em lote pode intencionalmente enfileirar trabalho durante o dia e drená-lo à noite. Um fluxo de trabalho voltado ao usuário pode ser insalubre se as mensagens esperam por trinta segundos. A mesma profundidade de fila pode ser aceitável em um sistema e severa em outro.

Defina um sinal baseado em idade, não apenas uma contagem. A contagem de mensagens informa quantas estão esperando; a idade da mensagem informa se o negócio está ficando para trás. Se seu monitoramento pode rastrear a idade da mensagem mais antiga ou o tempo de ponta a ponta da publicação à confirmação, ele detectará lentidões mais cedo do que a profundidade da fila sozinha.

Vincule alertas a essa expectativa. Alertar sobre 10.000 mensagens pode ser ruidoso para uma fila de exportação noturna e muito tarde para uma fila de redefinição de senha. Alertar sobre "mensagem mais antiga mais velha que o objetivo de serviço" geralmente está mais próximo do que os usuários se importam.

Uma fila quente ainda é uma fila quente

Adicionar nós ao cluster não divide automaticamente uma fila entre todos os nós. Uma única fila quente pode permanecer limitada por seu líder, seus consumidores e seu caminho de armazenamento.

Se uma fila carrega tipos de trabalho não relacionados, divida-a por comportamento de processamento real. Por exemplo, redimensionamento de imagem, envio de e-mail e captura de faturamento não devem compartilhar uma fila genérica jobs se tiverem necessidades diferentes de latência e repetição. Filas separadas permitem escalar consumidores independentemente e isolar mensagens venenosas.

Se um tipo de trabalho ainda estiver muito quente, fragmente apenas quando os requisitos de ordenação permitirem. Fragmentar por ID de cliente, locatário, região ou outra chave estável pode funcionar, mas empurra a complexidade para roteamento e operações. Não fragmente apenas para evitar corrigir um manipulador lento.

Uma ordem calma de solução de problemas

Em um incidente, uso esta ordem:

  1. Verifique alarmes: memória, disco e conexões bloqueadas.
  2. Verifique contadores da fila: prontas, não confirmadas, consumidores.
  3. Verifique logs do consumidor e temporização do manipulador.
  4. Verifique prefetch e distribuição de não confirmadas por consumidor.
  5. Verifique latência de confirmação do publicador e mensagens retornadas.
  6. Verifique latência do disco e pressão de recursos do nó.
  7. Verifique tamanho da mensagem e mudanças recentes na carga útil.
  8. Só então mude a topologia ou adicione nós ao broker.

Esta ordem evita um erro comum: escalar o broker quando o gargalo é um trabalhador, ou escalar trabalhadores quando o gargalo é o disco.

O RabbitMQ geralmente é muito claro quando você lê os contadores certos. Uma contagem crescente de prontas diz que o trabalho está esperando. Uma contagem crescente de não confirmadas diz que o trabalho está em andamento, mas não está terminando. Um publicador bloqueado diz que o broker está se protegendo. Trate cada sinal como uma pista, e a correção se torna muito menos dramática.