Skip to main content

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
ComponenteO que éImpacto principal
ConnectionConexão TCP física entre aplicação e RabbitMQsocket, file descriptor, memória
ChannelConexão lógica dentro de uma connectionmemória, processo interno
ExchangeComponente de roteamentoCPU, bindings, roteamento
QueueBuffer ordenado de mensagensmemória, disco, processos, métricas
ConsumerProcesso/aplicação que consome mensagenschannel, prefetch, ack, memória
MessagePayload trafegadomemória, disco, rede
danger

Foi feito o seguinte teste:

Havia uma fila com 54 mil mensagens. A imagem abaixo é o que constava no dashboard

alt text

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

alt text

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çãoSignificado
basicnó RabbitMQ padrão
discnó com estado em disco
rssmemória residente usada pelo processo
númeroquantidade/configuração relacionada ao runtime/schedulers conforme ambiente

Relação com filas:

  • disc importa porque filas duráveis e mensagens persistentes dependem do disco
  • rss ajuda 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

ModeloVantagemProblemaRecomendação
1 fila por clienteisolamento fortealto custo operacionalusar só com justificativa forte
1 fila globalsimplespode gerar gargaloútil para baixo volume
filas por shardequilíbrio entre escala e controleexige roteamento lógicorecomendado para muitos clientes
topic exchange + poucas filasflexívelexige desenho de routing keysrecomendado
RabbitMQ direto no desktopsimples no começoexpõe broker e credenciaisevitar 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étricaPor que monitorar
memory usedpressão de memória
disk freerisco de bloqueio de publisher
file descriptorsrisco de limite no SO
socket descriptorsexcesso de connections
Erlang processescrescimento de topologia/conexões
queue depthbacklog
consumer countcapacidade de consumo
publish rateentrada
deliver/get ratesaída
ack rateconfirmaçã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