Erros comuns em produção
2 min de leitura
Tudo o que você viu até aqui funciona na sua máquina. Produção adiciona volume, concorrência e falha de rede - e é aí que aparecem os problemas que nenhum tutorial mostra.
Conexão por mensagem
O erro de performance número um:
// nunca faça isso dentro de um handler de request
async function publicar(evento) {
const conn = await amqp.connect("amqp://localhost")
const ch = await conn.createChannel()
ch.publish("pedidos", "order.created", Buffer.from(evento))
await conn.close()
}
Abrir conexão AMQP é caro - handshake TCP, autenticação, negociação de protocolo. Fazer isso a cada mensagem derruba a vazão e ainda esgota os file descriptors do broker sob carga.
O certo é uma conexão por aplicação, aberta na subida e reaproveitada. Canais são baratos: um por thread ou worker, criados a partir da mesma conexão.
Fila que só cresce
Fila é buffer, não banco de dados. Se o consumidor processa mais devagar que o produtor publica, ela cresce até estourar a memória do broker - e quando isso acontece, o RabbitMQ aplica backpressure e trava os publishers. Sua API começa a pendurar sem motivo aparente.
Duas defesas, e vale ter as duas:
await channel.assertQueue("fila.pagamento", {
durable: true,
maxLength: 100000, // teto de mensagens
deadLetterExchange: "pedidos.dlx", // o excedente vai pra DLQ
})
E monitore a profundidade da fila ao longo do tempo. Fila oscilando é saudável; fila que só sobe é bug ou falta de worker - e você quer saber disso antes do disco encher.
Idempotência
O RabbitMQ garante entrega pelo menos uma vez, não exatamente uma.
Consumidor que caiu depois de processar mas antes do ack vai receber a
mesma mensagem de novo. É comportamento correto, não bug.
Ou seja: seu consumidor precisa aguentar processar a mesma mensagem duas vezes sem cobrar o cliente em dobro.
O jeito mais simples é dar um id único a cada mensagem e registrar o que já foi processado:
const { messageId } = msg.properties
if (await jaProcessado(messageId)) {
channel.ack(msg) // duplicata, ignora
return
}
await processar(msg)
await marcarProcessado(messageId)
channel.ack(msg)
Mensagem grande demais
Fila não é lugar pra PDF de 40MB. Mensagem grande ocupa memória do broker, atrasa todo mundo na fila e complica o retry.
O padrão é claim check: sobe o arquivo pro storage (S3, disco) e manda só a referência.
// em vez do arquivo inteiro
channel.publish("docs", "novo", Buffer.from(JSON.stringify({
arquivoUrl: "s3://bucket/nota-123.pdf",
tamanho: 42_000_000,
})))
O mínimo de observabilidade
Três sinais resolvem a maior parte dos incidentes:
- Profundidade da fila - subindo sem parar significa consumidor lento ou parado.
- Fila morta enchendo - tem bug em produção agora.
- Consumidores conectados - caiu pra zero e ninguém percebeu é o pior cenário possível.
Dica: alerta na DLQ é o de melhor custo-benefício. Fila principal vazia parece saúde, mas se tudo está indo pra DLQ é desastre silencioso - a fila esvazia justamente porque nada está dando certo.
Três coisas pra fixar:
- Uma conexão por aplicação, muitos canais. Nunca conexão por mensagem.
- Entrega é pelo menos uma vez. Consumidor precisa ser idempotente.
- Fila sem teto é incidente esperando acontecer. Ponha
maxLengthe monitore a profundidade.
No próximo nó você junta tudo isso num projeto de verdade.
// Quiz
Por que o consumidor precisa ser idempotente?