Uso excessivo de filas no RabbitMQ | RabbitMQ
- Uso excessivo de filas no RabbitMQ | RabbitMQ
Objetivo
Documentar por que a criação excessiva de filas no RabbitMQ, como uma fila por cliente, uma fila por usuário ou milhares de filas por entidade de negócio, pode representar um problema arquitetural e operacional.
O RabbitMQ permite trabalhar com muitas filas, mas fila não deve ser tratada como um recurso gratuito. Cada fila representa estado dentro do broker e impacta memória, CPU, processos internos, disco, métricas e manutenção operacional.
Contexto
Em alguns cenários surge a ideia de criar uma fila para cada cliente, por exemplo:
cliente.0001.fila
cliente.0002.fila
cliente.0003.fila
...
cliente.6000.fila
A motivação geralmente é:
- isolar mensagens por cliente
- facilitar roteamento
- evitar que um cliente leia mensagem de outro
- simplificar o consumo
- permitir backlog individual
Embora pareça simples, esse modelo pode gerar custo operacional elevado quando aplicado em larga escala.
Conceito importante
Uma fila no RabbitMQ não é apenas um “nome lógico”.
Ela é uma estrutura ativa dentro do broker, responsável por:
- armazenar mensagens
- controlar estado de entrega
- controlar consumers
- controlar acknowledgements
- manter metadados
- aplicar políticas
- participar das métricas
- interagir com memória e disco
A própria documentação do RabbitMQ define fila como uma coleção ordenada de mensagens, onde mensagens entram e saem para consumidores no modelo FIFO, salvo exceções como prioridade, requeue e múltiplos consumidores. (rabbitmq.com)
Relação entre fila, conexão, canal e consumidor
Producer / Consumer
|
v
Connection TCP
|
v
Channel AMQP
|
v
Exchange
|
v
Queue
|
v
Consumer
| Componente | O que é | Impacto principal |
|---|---|---|
| Connection | Conexão TCP física entre aplicação e RabbitMQ | socket, file descriptor, memória |
| Channel | Conexão lógica dentro de uma connection | memória, processo interno |
| Exchange | Componente de roteamento | CPU, bindings, roteamento |
| Queue | Buffer ordenado de mensagens | memória, disco, processos, métricas |
| Consumer | Processo/aplicação que consome mensagens | channel, prefetch, ack, memória |
| Message | Payload trafegado | memória, disco, rede |
Foi feito o seguinte teste:
Havia uma fila com 54 mil mensagens. A imagem abaixo é o que constava no dashboard

Após apagar, percebemos que o Erlang process diminuiu e também a memória

O RabbitMQ recomenda o uso de canais porque conexões AMQP podem ser multiplexadas: uma aplicação pode abrir múltiplos channels sobre uma única connection TCP. Isso evita manter muitas conexões TCP abertas, que consomem recursos do sistema. (rabbitmq.com)
Métricas do Node e relação com filas
File descriptors
File descriptors representam handles abertos pelo processo no sistema operacional.
No RabbitMQ, podem ser consumidos por:
- conexões TCP
- sockets
- arquivos internos
- arquivos de mensagens persistentes
- logs
- operações de disco
Relação com filas:
- filas duráveis e mensagens persistentes tendem a aumentar uso de disco e arquivos
- muitas conexões de producers/consumers aumentam sockets/file descriptors
- muitos recursos simultâneos aumentam pressão sobre o sistema operacional
Socket descriptors
Socket descriptors indicam conexões de rede abertas.
Relação com RabbitMQ:
- cada connection TCP consome socket
- producers usam connection
- consumers usam connection
- muitos clientes conectados diretamente ao RabbitMQ aumentam esse número
Exemplo de risco:
6000 clientes desktop conectados diretamente ao RabbitMQ
= potencialmente 6000 conexões TCP
= 6000 sockets
= pressão sobre o broker e sistema operacional
Erlang processes
RabbitMQ roda sobre Erlang/OTP. Internamente, várias partes do broker são representadas como processos leves Erlang.
A documentação oficial informa que o runtime possui limite de processos Erlang e cita ambientes com alto número de conexões simultâneas ou número muito grande de filas como casos que podem exigir ajuste desse limite. (rabbitmq.com)
Relação com filas:
- filas usam processos internos
- connections usam processos internos
- channels usam processos internos
- consumers também contribuem para processos internos
- muitos milhares de filas aumentam a quantidade de processos monitorados pelo runtime
Exemplo conceitual:
6000 filas
6000 consumers
6000 connections
vários channels
= aumento significativo de processos internos
Memory
Memory indica o consumo de memória do nó RabbitMQ.
A documentação oficial do RabbitMQ possui uma seção específica para análise de uso de memória e explica que o broker separa o consumo por categorias, como connections, channels, processos e tabelas internas. (rabbitmq.com)
Relação com filas:
- fila vazia consome metadados
- fila com mensagens consome mais memória
- backlog aumenta uso de memória
- consumers com prefetch alto aumentam mensagens em trânsito
- quorum queues mantêm índices em memória
No caso de quorum queues, cada processo de fila mantém um índice em memória das mensagens. A documentação cita pelo menos 32 bytes de metadados por mensagem e recomenda manter filas curtas para preservar baixo uso de memória. (rabbitmq.com)
Disk space
Disk space indica espaço disponível para persistência.
Relação com filas:
- filas duráveis armazenam metadados
- mensagens persistentes usam disco
- quorum queues usam WAL e segmentos
- backlog persistente aumenta consumo de disco
- logs também competem pelo mesmo recurso
Quando o disco fica abaixo do limite configurado, o RabbitMQ pode bloquear publishers para evitar perda ou corrupção de dados.
Uptime
Indica há quanto tempo o nó está em execução.
Uso prático:
- avaliar estabilidade
- identificar reinícios inesperados
- correlacionar degradação com tempo de execução
- verificar se crescimento de memória é progressivo
Relação com filas:
- muitas filas e backlog acumulado podem gerar comportamento degradado ao longo do tempo
- reinícios podem ficar mais demorados quando existe muito estado persistente
Info
A coluna Info exibe características do nó, como:
| Informação | Significado |
|---|---|
| basic | nó RabbitMQ padrão |
| disc | nó com estado em disco |
| rss | memória residente usada pelo processo |
| número | quantidade/configuração relacionada ao runtime/schedulers conforme ambiente |
Relação com filas:
discimporta porque filas duráveis e mensagens persistentes dependem do discorssajuda a entender memória real ocupada no sistema operacional
Por que criar muitas filas pode ser mau uso
1. Aumenta o estado interno do broker
Cada fila adiciona estado:
- nome
- propriedades
- bindings
- consumers
- política
- estatísticas
- mensagens
- índices internos
Com poucas filas, isso é administrável. Com milhares, o broker passa a gastar mais recursos mantendo a topologia do que entregando valor de negócio.
2. Aumenta o custo de memória
Mesmo vazias, filas possuem metadados.
Com mensagens acumuladas, o custo cresce mais:
fila vazia
= metadados
fila com mensagens
= metadados + mensagens + índices + estado de entrega
Em quorum queues, a documentação deixa explícito que há índice em memória por mensagem. (rabbitmq.com)
3. Aumenta o custo de CPU
Mais filas significam:
- mais processos internos
- mais scheduling no runtime Erlang
- mais métricas para coletar
- mais bindings para avaliar
- mais trabalho para management UI
- mais custo em operações administrativas
O problema não é apenas throughput. É custo operacional contínuo.
4. Piora observabilidade
Com milhares de filas, a observabilidade fica mais difícil:
- tela de queues fica pesada
- dashboards ficam poluídos
- alertas perdem precisão
- métricas por fila explodem em cardinalidade
- Prometheus/Grafana podem sofrer com excesso de séries temporais
RabbitMQ possui plugin Prometheus e expõe métricas para nodes, connections, queues e rates; isso é recomendado para produção, mas quanto maior a cardinalidade da topologia, maior o custo de observabilidade. (rabbitmq.com)
5. Dificulta operação e manutenção
Muitas filas tornam mais difícil:
- aplicar policies
- identificar filas abandonadas
- limpar filas antigas
- migrar topologia
- auditar permissões
- explicar o modelo para o time
- fazer troubleshooting
Arquiteturalmente, isso aumenta complexidade acidental.
Exemplo de mau uso
Modelo problemático
1 cliente = 1 fila
cliente.0001.comandos
cliente.0002.comandos
cliente.0003.comandos
...
cliente.6000.comandos
Problemas:
- 6000 filas permanentes
- 6000 pontos de backlog
- 6000 métricas principais
- 6000 objetos administrativos
- maior pressão de memória/processos
- maior custo de operação
Esse modelo só deve ser usado quando houver justificativa forte de isolamento, backlog individual e controle operacional por cliente.
Modelo mais saudável
Exchange topic + routing key
Em vez de criar uma fila por cliente, usar uma exchange topic para roteamento lógico.
Exchange: comandos.topic
Routing keys:
comando.cliente.123456.enviar
comando.cliente.789000.enviar
comando.cliente.555111.enviar
Exemplo de bindings:
comando.cliente.*.enviar
comando.cliente.123456.#
comando.cliente.#
Vantagens:
- reduz quantidade de filas
- centraliza roteamento
- usa routing key para identificação
- facilita sharding lógico
- reduz custo operacional
A documentação do RabbitMQ explica o modelo AMQP com exchanges, bindings e routing keys: a exchange roteia mensagens para filas conforme a chave de roteamento e os bindings configurados. (rabbitmq.com)
Alternativa com sharding lógico
Em vez de 6000 filas, criar um número fixo de filas particionadas:
comandos.shard.00
comandos.shard.01
comandos.shard.02
...
comandos.shard.31
O produtor decide o shard com base no hash do cliente:
shard = hash(cnpj) % 32
Exemplo:
CNPJ 123456 -> comandos.shard.07
CNPJ 789000 -> comandos.shard.19
CNPJ 555111 -> comandos.shard.07
Vantagens:
- controla número de filas
- permite paralelismo
- reduz explosão de topologia
- mantém distribuição previsível
- facilita escala horizontal de consumers
Comparação entre abordagens
| Modelo | Vantagem | Problema | Recomendação |
|---|---|---|---|
| 1 fila por cliente | isolamento forte | alto custo operacional | usar só com justificativa forte |
| 1 fila global | simples | pode gerar gargalo | útil para baixo volume |
| filas por shard | equilíbrio entre escala e controle | exige roteamento lógico | recomendado para muitos clientes |
| topic exchange + poucas filas | flexível | exige desenho de routing keys | recomendado |
| RabbitMQ direto no desktop | simples no começo | expõe broker e credenciais | evitar em clientes externos |
Quando muitas filas fazem sentido
Muitas filas podem ser aceitáveis quando:
- cada fila representa um domínio real de processamento
- há necessidade de backlog isolado por entidade
- há consumidores dedicados por fila
- existe política específica por fila
- existe monitoramento dimensionado para isso
- o broker foi testado com carga real
- o time aceita o custo operacional
Exemplo válido:
fila.pagamentos
fila.notas-fiscais
fila.email
fila.webhook
fila.integracao-erp
Exemplo questionável:
fila.cliente.1
fila.cliente.2
fila.cliente.3
...
fila.cliente.6000
Sinais de alerta
Considere o desenho problemático se:
- a fila representa um usuário, cliente ou sessão
- o número de filas cresce conforme a base de clientes
- a aplicação cria filas dinamicamente sem ciclo de vida claro
- a fila não tem consumer dedicado real
- o backlog por fila raramente é necessário
- o Management UI começa a ficar lento
- o Prometheus começa a ter cardinalidade alta
- policies ficam difíceis de aplicar
- o time não sabe quais filas ainda são usadas
Boas práticas
Preferir routing em vez de fila por cliente
Use:
exchange topic + routing key
em vez de:
fila por cliente
Controlar cardinalidade
Defina limites para:
- quantidade de filas
- quantidade de bindings
- quantidade de consumers
- quantidade de connections
- backlog por fila
- tamanho máximo da mensagem
Usar prefetch
Consumers devem usar prefetch para evitar que muitas mensagens fiquem em trânsito sem processamento.
Exemplo:
await channel.BasicQosAsync(
prefetchSize: 0,
prefetchCount: 10,
global: false
);
Usar filas duráveis apenas quando necessário
Fila durável e mensagem persistente têm custo maior.
Use quando:
- a mensagem precisa sobreviver a restart
- perda de mensagem não é aceitável
- existe processo de retry/dead-letter
Evite persistência para eventos descartáveis ou notificações efêmeras.
Definir TTL e DLQ
Para evitar acúmulo infinito:
x-message-ttl
x-expires
x-dead-letter-exchange
x-max-length
Isso evita que filas abandonadas ou lentas consumam recursos indefinidamente.
Monitorar métricas essenciais
| Métrica | Por que monitorar |
|---|---|
| memory used | pressão de memória |
| disk free | risco de bloqueio de publisher |
| file descriptors | risco de limite no SO |
| socket descriptors | excesso de connections |
| Erlang processes | crescimento de topologia/conexões |
| queue depth | backlog |
| consumer count | capacidade de consumo |
| publish rate | entrada |
| deliver/get rate | saída |
| ack rate | confirmação real de processamento |
Recomendação arquitetural
Para o cenário de muitos clientes, como milhares de desktops, evite expor RabbitMQ diretamente para cada cliente e evite criar uma fila por cliente.
Modelo recomendado:
Cliente externo
|
v
API / Gateway
|
v
Exchange topic
|
v
Filas por domínio ou shard
|
v
Workers / Serviços internos
Para comunicação server → desktop em tempo real, normalmente é mais adequado usar:
SignalR / WebSocket / MQTT
Para processamento assíncrono interno:
RabbitMQ
Decisão sugerida
Não recomendado
6000 clientes
6000 filas
6000 consumers
6000 conexões diretas no RabbitMQ
Recomendado
1 exchange topic
16 a 64 filas por shard
N workers consumidores
routing key por cliente/CNPJ
controle de concorrência no worker
Exemplo:
Exchange:
comandos.topic
Routing key:
comando.cliente.{cnpj}.solicitado
Fila:
comandos.shard.00
comandos.shard.01
comandos.shard.02
...
comandos.shard.31
Regra prática
Use fila para representar fluxo de processamento, não para representar cada cliente.
Bom:
fila.processar-comando
fila.enviar-email
fila.gerar-nota
fila.integracao-erp
Ruim:
fila.cliente-1
fila.cliente-2
fila.cliente-3
...
fila.cliente-6000