O cenário se repete: a empresa dispara uma campanha, o WhatsApp recebe em minutos o volume de um dia inteiro, e o ERP cai. Alguém sugere RabbitMQ, a fila entra, o ERP para de cair — e o time conclui que a fila "escalou" o sistema.
Não escalou. A capacidade do ERP continua exatamente a mesma. O que a fila fez foi trocar um problema por outro, e o novo problema é muito melhor. Vale entender a troca com precisão, porque ela define o que você precisa dizer ao cliente.
1. O que quebra na integração síncrona
Na integração direta, cada mensagem que chega vira uma chamada ao ERP na hora. Enquanto o volume é menor que a capacidade, funciona. No pico, três coisas acontecem em sequência:
- As chamadas se acumulam em execução simultânea e o tempo de resposta sobe.
- O ERP atinge o limite de conexões e passa a recusar — ou pior, aceita e trava.
- O canal que originou a chamada estoura o timeout. O pedido não existe em lugar nenhum: não foi gravado e o cliente recebeu erro.
O passo 3 é o que custa dinheiro. Não é lentidão — é perda. Para dimensionar, simulei uma campanha: 60s de tráfego normal, 60s de pico (disparo de lista), 60s de normalização, contra um ERP que processa 5 pedidos/s e recusa acima de 20 chamadas simultâneas.
def sincrono(c: Carga) -> dict:
"""Cada pedido chama o ERP na hora. Acima do limite, o ERP recusa."""
em_voo = 0; perdidos = 0; processados = 0
for novos in c.chegadas:
em_voo = max(0, em_voo - c.erp_por_segundo)
processados += min(em_voo + novos, c.erp_por_segundo)
espaco = c.concorrencia_max - em_voo
aceitos = min(novos, max(0, espaco))
perdidos += novos - aceitos # pedido que nunca existiu
em_voo += aceitos
return {"processados": processados, "perdidos": perdidos}
def com_fila(c: Carga) -> dict:
"""Pedidos vão para a fila. O consumidor drena no ritmo do ERP."""
fila = 0; processados = 0; espera_max = 0; segundo = 0
for novos in c.chegadas:
fila += novos
vazao = min(fila, c.erp_por_segundo)
fila -= vazao; processados += vazao; segundo += 1
espera_max = max(espera_max, fila / c.erp_por_segundo)
while fila > 0: # drena o resto depois do pico
vazao = min(fila, c.erp_por_segundo)
fila -= vazao; processados += vazao; segundo += 1
return {"processados": processados, "perdidos": 0,
"espera_max_s": round(espera_max, 1), "fim_s": segundo}
pedidos recebidos: 2640 SINCRONO processados: 555 | PERDIDOS: 2085 (79%) COM FILA processados: 2640 | PERDIDOS: 0 | espera maxima: 420.0s | fim aos 564s
2. A troca, dita com clareza
A integração síncrona perdeu 79% dos pedidos. A fila não perdeu nenhum. Mas repare na outra coluna: a espera máxima chegou a 420 segundos, e a fila só terminou de drenar aos 564s — o pico durou 60s.
A fila não criou capacidade. Um ERP de 5 pedidos/s continua sendo de 5 pedidos/s; 2.640 pedidos levam no mínimo 528 segundos, e a simulação terminou em 564. O que a fila fez foi converter perda em espera — e espera é um problema que se administra, enquanto pedido perdido é receita que não volta.
Isso tem uma consequência de produto que costuma passar batido: se o pedido vai demorar 7 minutos para entrar no ERP, a confirmação ao cliente não pode depender do ERP. Ela tem que sair no momento em que a mensagem entra na fila — "recebemos seu pedido" — e a confirmação definitiva vira um segundo aviso. Adotar fila sem mudar essa comunicação troca "deu erro" por "ficou mudo", que é pior.
E se a espera de 7 minutos for inaceitável para o negócio, a resposta não é mexer na fila: é aumentar a vazão do consumidor ou do ERP. A fila só torna o gargalo visível e mensurável — o que já é muito, porque agora ele é um número, e não um incidente.
3. Entrega ao menos uma vez: o duplicado é regra, não exceção
RabbitMQ garante entrega ao menos uma vez. Se o consumidor processa e cai antes de confirmar, a mensagem volta para a fila e é entregue de novo. Sem proteção, isso é pedido duplicado no ERP — e cliente cobrado duas vezes.
A proteção não é de infraestrutura, é de aplicação: o consumidor precisa ser idempotente, com chave de negócio persistida.
def deduplicar(eventos: list[tuple[str, str]]) -> list[str]:
"""RabbitMQ entrega ao menos uma vez: o consumidor precisa ser idempotente."""
vistos: set[str] = set()
aplicados: list[str] = []
for chave, pedido in eventos:
if chave in vistos:
continue
vistos.add(chave)
aplicados.append(pedido)
return aplicados
# entregas recebidas: 5 -> aplicadas: 3 ['A', 'B', 'C']
Em produção o set vira uma constraint UNIQUE na chave de
idempotência, dentro da mesma transação que grava o pedido. Guardar a chave em memória do
processo não protege: o processo que caiu é justamente o que perdeu a memória.
4. Quando não usar fila
Fila adiciona um componente para operar, monitorar e recuperar. Ela não se justifica quando:
- A resposta precisa ser imediata e exata — consulta de saldo, validação de cupom, autorização de pagamento. Assíncrono aqui só empurra o problema para a interface.
- O volume nunca se aproxima da capacidade. Se o pico é 3 pedidos/s contra um ERP de 50/s, a fila resolve um problema que você não tem.
- Não há quem opere. Fila sem monitoramento de profundidade e sem fila de mensagens mortas (DLQ) transforma perda visível em perda silenciosa — o pior dos dois mundos.
O critério é o mesmo que aplicamos em escolha de ferramenta: componente novo só entra quando o que existe comprovadamente não resolve.
5. O resumo
Fila não aumenta a capacidade do seu ERP — converte pedido perdido em pedido atrasado. Essa troca quase sempre vale (79% de perda contra 0%), mas exige duas decisões de produto: confirmar o recebimento sem esperar o ERP, e tornar o consumidor idempotente porque o duplicado vai acontecer. Sem essas duas, a fila só esconde o gargalo.
Seu canal de vendas aguenta o próximo disparo?
Diagnóstico sem custo: medimos a capacidade real do seu ERP, simulamos o pico da sua campanha e dizemos se fila resolve ou se o gargalo é outro.