Oi a todos,
Estou executando uma plataforma de automação multi-tenant em cima do n8n e comecei a observar problemas onde alguns tenants com alto volume impactam a experiência de todos os outros.
Arquitetura atual:
Fila compartilhada
↓
Pool de workers compartilhado
↓
Infraestrutura compartilhada
Problemas que estou observando:
• Um único tenant pode inundar a fila
• Workflows de longa duração atrasam jobs menores
• Tenants pesados em API consomem capacidade desproporcional de workers
• Latência aumentada para tenants de baixo volume
Meus objetivos são:
• Alocação justa de recursos entre tenants
• Evitar problemas de vizinhos ruidosos
• Manter desempenho previsível
• Escalar sem superdimensionar a infraestrutura
Estou considerando:
• Filas por tenant
• Filas de prioridade
• Pools de workers por tier de tenant
• Rate limiting e quotas
• Infraestrutura dedicada para tenants enterprise
Para equipes operando plataformas de workflow multi-tenant em escala:
Como vocês estão prevenindo problemas de vizinhos ruidosos?
Qual é a mensagem de erro (se houver)?
Por favor compartilhe seu workflow
(Selecione os nós na sua canvas e use os atalhos de teclado CMD+C/CTRL+C e CMD+V/CTRL+V para copiar e colar o workflow.)
Oi @Keira_Becky A abordagem mais comum é combinar limites de taxa, isolamento de fila e isolamento de workers.
Fila Compartilhada + Limites Por Tenant + Workers Dedicados para Tenants Pesados
Isso impede que um único tenant consuma todos os recursos disponíveis.
Estrategias comuns
Aplicar limites de taxa e quotas por tenant
Usar filas prioritárias para diferentes tiers de serviço
Rotear tenants de alto volume para filas ou workers dedicados
Manter tenants menores em infraestrutura compartilhada
Para tenants em larga escala muitos times usam: Enterprise Tenant → Fila Dedicada → Pool de Workers Dedicado
Certifique-se de Evitar
Uma fila global única sem limites
Tratar todos os tenants igualmente independentemente do uso
Permitir retentativas ilimitadas ou jobs de longa duração em workers compartilhados
A solução mais limpa para o problema do vizinho barulhento é parar de compartilhar aquilo pelo qual os inquilinos se debatem. Em vez de uma fila compartilhada única e um pool de workers com limites aplicados, execute uma instância n8n por inquilino: sua própria fila, seus próprios workers, seu próprio banco de dados e chave de criptografia, seu próprio container. O vizinho barulhento deixa de ser algo que você afina e se torna estruturalmente impossível, porque não há fila compartilhada para um inquilino inundar e nenhum pool compartilhado para um inquilino pesado em API esgotar.
A rota de fila compartilhada mais limites (limites de taxa por inquilino, camadas de prioridade, workers dedicados para os pesados) funciona, e é a escolha certa se você está preso a uma instância grande. Mas você está se inscrevendo para afinar limites de taxa, pesos de prioridade, caps de retry e concorrência para sempre, e um valor mal configurado ou um workflow patológico ainda vaza entre inquilinos. A programação justa em um pool compartilhado é um alvo móvel que você nunca para de perseguir.
A troca honesta com instância por inquilino é o inverso: raio de impacto quase nulo, mas mais infraestrutura base ociosa e mais coisas para operar. N instâncias para implantar, fazer backup, monitorar e atualizar. Esse custo operacional é a objeção real, e é o solucionável:
Limite cada container. Limites de CPU e memória por instância (compose ou k8s) para que até mesmo um workflow descontrolado fique limitado ao seu próprio inquilino, não ao host.
Isole a execução também. Execute nós Code em um sidecar externo task-runner por instância, para que a execução de JS/Python seja contida, não apenas a fila.
Escalone. Você não precisa de uma instância por inquilino no primeiro dia. Pequenos inquilinos podem compartilhar uma instância; dê aos inquilinos pesados e empresariais as suas próprias. Até mesmo a camada compartilhada é mais saudável fragmentada entre poucas instâncias do que uma fila global.
Coloque um plano de controle sobre a frota. Passado um punhado de instâncias, você precisa de um lugar para ver erros, saúde e execuções em todas elas, e para implantar e fazer backup sem fazer SSH de máquina para máquina.
A alavanca mais direta no modo de fila do n8n é EXECUTIONS_CONCURRENCY_ACTIVE - defina-a por container worker, não globalmente. Para o problema de vizinho barulhento, o padrão que funciona na prática é: execute 2-3 tiers de worker com caps de concorrência diferentes, depois encaminhe tenants pesados para seu próprio grupo de worker usando um prefixo de chave de fila Redis separado (definido via QUEUE_BULL_PREFIX por worker). Tenants de alto volume recebem um worker dedicado com EXECUTIONS_CONCURRENCY_ACTIVE mais baixo (digamos 5), o tier compartilhado obtém mais workers mas cada um limitado a 10. Workflows de longa execução são o caso mais difícil - use EXECUTIONS_TIMEOUT no nível da instância e EXECUTIONS_TIMEOUT_MAX por workflow para evitar que qualquer execução individual bloqueie um worker indefinidamente. Se dois ou três tenants consistentemente atingem o máximo de sua própria fila, atualizá-los para uma instância n8n dedicada geralmente é o caminho mais limpo do que tentar isolá-los dentro de um cluster compartilhado.
Temos o mesmo problema em uma plataforma n8n+Postgres multi-tenant no início deste ano — chamadas LLM sobrevivendo aos TTLs do Redis, um tenant lento bloqueando todo mundo.
O que realmente parou o sangramento: prefixos de fila BullMQ por tier de tenant (QUEUE_BULL_PREFIX) + tetos EXECUTIONS_CONCURRENCY_ACTIVE separados por pool de workers. O Redis lock é o caminho rápido. Depois, UNIQUE do Postgres (tenant_id, message_id) com ON CONFLICT DO NOTHING como a porta de idempotência durável — essa é a camada de verdade quando as chamadas do Gemini sobrevivem ao TTL do Redis.
Vale a pena verificar aí: seu TTL de Redis lock é mais longo que o P99 da sua chamada LLM? É geralmente onde o sangramento de execução duplicada começa.
Aliás — se p95 LLM fica abaixo de 2s e pool_wait é flat, a contenção está em outro lugar. Geralmente no churn QUEUE_RECOVERY_INTERVAL no scheduler.