Em quase todo SaaS multi-tenant existe um cliente que consome mais recurso que dezenas de outros somados. Na maior parte do mês ninguém percebe. Aí chega o fechamento contábil dele, uma importação de planilha gigante ou uma integração mal escrita que dispara milhares de requisições por minuto, e o painel de todos os outros clientes fica lento. O suporte recebe chamados de quem não fez nada de errado. A saída é isolar os tenants em camadas: limitar o que cada cliente pode consumir na entrada, separar filas e workers por perfil de uso e, quando o volume justificar, mover o cliente pesado para um shard ou célula própria. Fazer tudo isso de uma vez, sem critério, costuma sair caro e complicar a operação sem necessidade.
- O que é noisy neighbor em um SaaS multi-tenant
- Modelos de isolamento de tenants: pool, bridge e silo
- Antes de isolar, meça: telemetria por tenant
- Na entrada: balanceamento de carga SaaS e rate limiting
- Filas e workers: onde o vizinho barulhento mais estraga
- Banco de dados: sharding e o tenant que domina o disco
- Perguntas frequentes
- Conclusão
Este texto segue a ordem em que essas camadas costumam aparecer na prática: primeiro a visibilidade, depois a entrada, o processamento assíncrono e por último o banco, que é a parte mais difícil de mexer. Se o seu SaaS já está sofrendo com um cliente específico, dá para pular direto para a seção que dói mais. Recomendo ler a de telemetria antes, porque é nela que a maioria dos times erra.
O que é noisy neighbor em um SaaS multi-tenant
Noisy neighbor é o tenant cujo consumo de CPU, memória, I/O de disco, conexões de banco ou rede degrada a experiência dos outros clientes que dividem a mesma infraestrutura. Em um SaaS multi-tenant isso é esperado, porque o modelo existe justamente para compartilhar recursos. O objetivo não é eliminar o compartilhamento. É impedir que um único cliente consuma a capacidade de todos.
O problema raramente aparece como "servidor no limite". Os sintomas típicos são mais traiçoeiros:
- latência do p99 sobe enquanto a média continua bonita no dashboard;
- o pool de conexões do banco esgota e requisições simples ficam esperando na fila;
- jobs agendados de outros clientes atrasam horas porque a fila está entupida com tarefas de um só tenant;
- o cache perde eficiência porque um cliente com volume enorme expulsa as chaves quentes dos demais;
- o autoscaling sobe instâncias, a conta da nuvem dispara e o gargalo continua no banco.
Esse último item merece atenção. Escalar horizontalmente a camada de aplicação resolve pouco quando o recurso disputado é compartilhado mais abaixo. Dez instâncias novas da API, todas abrindo conexões para o mesmo Postgres, pioram a situação. Isolamento de tenants é, antes de tudo, saber qual recurso está sendo disputado.
Modelos de isolamento de tenants: pool, bridge e silo
Existem três modelos clássicos de arquitetura multi-tenant, e quase nenhum SaaS maduro usa um só. O que costuma funcionar é um modelo híbrido: a maioria dos clientes compartilhando tudo, e os poucos que justificam (por volume, por contrato ou por exigência regulatória) ganhando isolamento parcial ou total.
| Modelo | Como funciona | Vantagem | Limitação | Quando faz sentido |
|---|---|---|---|---|
| Pool | Todos os tenants na mesma aplicação e no mesmo banco, separados por tenant_id | Menor custo por cliente, deploy único, operação simples | Maior risco de noisy neighbor e de vazamento de dados por bug de query | Base grande de clientes pequenos e médios com uso parecido |
| Bridge | Aplicação compartilhada, dados separados por schema ou por banco lógico | Backup e restore por cliente ficam viáveis, isolamento de dados melhor | Migrations precisam rodar em centenas de schemas; catálogo do banco incha | Clientes que exigem separação de dados mas não infraestrutura própria |
| Silo | Stack dedicada por tenant (app, banco, às vezes rede) | Isolamento total de desempenho e falhas | Custo alto, frota de deploys para manter, observabilidade fragmentada | Enterprise com contrato que paga o custo, ou exigência legal |
Uma variação que vem ganhando espaço é a arquitetura em células (cell-based). Em vez de um silo por cliente, você replica a stack inteira em N células idênticas e distribui os tenants entre elas. Cada célula é um pool pequeno. Se um cliente explode, ele derruba no máximo os vizinhos da própria célula, não a base inteira. E o cliente pesado pode ganhar uma célula só para ele usando exatamente o mesmo código e o mesmo pipeline de deploy.
A pergunta certa não é "pool ou silo?". É "qual o menor raio de impacto que eu consigo pagar?". Célula é a forma de tornar essa resposta ajustável sem reescrever o produto.
Um alerta de quem já passou por isso: o modelo bridge com um schema por tenant parece elegante com 50 clientes e vira dor de cabeça com 5 mil. Migrations que levam segundos passam a levar horas, ferramentas de backup se perdem no número de objetos e o planner do banco sofre. Se você espera crescer em quantidade de clientes, prefira pool com tenant_id bem indexado e row-level security, e reserve a separação física para quem realmente precisa.
Antes de isolar, meça: telemetria por tenant
Você não consegue isolar o que não enxerga. O erro mais comum é tentar resolver noisy neighbor com intuição ("deve ser o cliente X, ele é grande") e descobrir semanas depois que o problema era um cliente pequeno com uma integração em loop. Toda métrica relevante precisa poder ser quebrada por tenant.
Na prática, isso significa propagar o identificador do tenant por toda a cadeia de execução:
- Resolva o tenant o mais cedo possível, no gateway ou no middleware de autenticação, a partir do token, do subdomínio ou de um header.
- Coloque o
tenant_idno contexto da requisição e nos logs estruturados. - Propague o identificador em traces distribuídos e no payload de mensagens enviadas para filas.
- Anote as queries no banco (um comentário SQL com o tenant, por exemplo) para que o log de queries lentas mostre de quem é a query.
- Agregue consumo por tenant: requisições, tempo de CPU estimado, tempo de banco, bytes armazenados, jobs enfileirados.
Aqui mora uma pegadinha clássica. Usar tenant_id como label em métricas do Prometheus parece óbvio, mas com milhares de tenants você cria um problema de cardinalidade que derruba o próprio sistema de monitoramento. Duas saídas funcionam bem. A primeira é expor métricas por tenant só para o "top N" consumidores e agrupar o resto como "outros". A segunda é calcular consumo por tenant a partir de logs e traces em um pipeline separado, deixando as métricas de série temporal com baixa cardinalidade.
Com esses dados em mãos, a conversa muda de nível. Você passa a ter uma curva de distribuição de consumo, e quase sempre ela é muito concentrada: um punhado de tenants responde pela maior fatia da carga. É essa curva que vai dizer quais clientes merecem tratamento diferenciado e quais limites são razoáveis para o plano padrão.
Um efeito colateral bom: telemetria por tenant vira insumo comercial. Saber quanto cada cliente custa para ser servido ajuda a precificar planos, identificar contas que estão no plano errado e fundamentar a conversa de upgrade com dados em vez de achismo.
Na entrada: balanceamento de carga SaaS e rate limiting
A borda do sistema é o lugar mais barato para conter um tenant abusivo. Uma requisição rejeitada no gateway custa quase nada. A mesma requisição aceita, processada pela API, enviada para o banco e depois para uma fila consome recursos em quatro camadas antes de você perceber o estrago.
Rate limiting por tenant, não por IP
Limitar por IP é a configuração padrão de muito proxy reverso, e para SaaS B2B ela é quase inútil. Um cliente corporativo pode ter centenas de usuários atrás do mesmo NAT, e uma integração pode vir de dezenas de IPs de um provedor de nuvem. O limite precisa ser por tenant, idealmente com uma segunda dimensão por usuário ou por chave de API.
O algoritmo mais usado é o token bucket: cada tenant tem um balde que se reabastece a uma taxa constante e comporta um certo pico. Isso permite rajadas curtas (alguém abrindo um relatório pesado) sem permitir consumo sustentado acima do contratado. O estado costuma ficar em Redis, com um script atômico para evitar condição de corrida entre instâncias do gateway.
Alguns cuidados que fazem diferença no dia a dia:
- Retorne 429 com
Retry-After. Integrações bem escritas respeitam o header; sem ele, o cliente tenta de novo imediatamente e o problema piora. - Diferencie rotas por custo. Um
GETde um registro e um endpoint de exportação não podem consumir o mesmo token. Atribua peso por rota, ou mantenha baldes separados para operações caras. - Defina limites por plano. O limite vira parte do produto, documentado e visível para o cliente, em vez de surpresa.
- Decida o que fazer se o Redis cair. Fail-open (deixa passar) protege a disponibilidade; fail-closed protege a infraestrutura. Para a maioria dos SaaS, fail-open com um limite local em memória como rede de segurança é o equilíbrio razoável.
Rate limit não basta: limite de concorrência
Rate limiting controla quantas requisições chegam por segundo. Ele não controla quanto tempo cada uma ocupa. Um tenant pode estar dentro do limite de 20 requisições por segundo e, ainda assim, cada uma levar oito segundos porque consulta um volume de dados enorme. Na prática, ele está ocupando 160 slots de processamento simultâneos.
Por isso, para endpoints pesados, combine rate limit com limite de requisições simultâneas por tenant. Se o cliente já tem N requisições em andamento, a próxima espera ou recebe 429. Essa é uma das proteções mais eficazes contra noisy neighbor na camada de API, e uma das menos implementadas.
Balanceamento ciente de tenant
O balanceamento de carga SaaS tradicional (round robin ou least connections entre instâncias idênticas) distribui bem a carga média, mas espalha o tenant problemático por todas as instâncias. Quando ele pesa, todas pesam juntas.
Duas técnicas mudam esse comportamento:
- Roteamento por tenant para pools ou células. O gateway consulta um mapa tenant → destino e encaminha o tráfego. Clientes enterprise vão para um pool dedicado; o restante vai para o pool compartilhado. Com isso, mover um cliente pesado de lugar vira uma mudança de configuração.
- Shuffle sharding. Cada tenant é atribuído a um subconjunto pequeno e aleatório de instâncias (por exemplo, 2 de 16). Dois tenants raramente compartilham exatamente o mesmo subconjunto, então quando um deles derruba suas instâncias, a maioria dos outros ainda tem pelo menos uma instância saudável disponível.
Shuffle sharding é elegante, mas só se paga com uma frota razoável de instâncias. Com quatro servidores de aplicação, a matemática não ajuda muito. Nesse tamanho, um pool dedicado para os dois ou três clientes mais pesados resolve com menos complexidade.
Filas e workers: onde o vizinho barulhento mais estraga
Em muitos SaaS, a API até aguenta bem e o problema real está no processamento assíncrono. Importações, geração de relatórios, envio de e-mails em massa, sincronizações com ERPs: tudo isso costuma cair em uma fila única, consumida por um grupo de workers em ordem de chegada. Para isolamento de tenants, a FIFO global é o pior cenário possível.
Imagine um SaaS de gestão financeira em que um cliente grande, no fechamento do mês, importa um arquivo que gera centenas de milhares de jobs de conciliação. Eles entram na fila em segundos. Um cliente pequeno, logo depois, pede um relatório simples, e o job dele fica atrás de todo aquele lote. Para o cliente pequeno, o sistema "travou". Nenhum servidor está fora do ar e nenhum alerta de infraestrutura disparou.
As abordagens para resolver isso, da mais simples à mais sofisticada:
- Filas por prioridade ou por tipo de job. Jobs interativos (o usuário está esperando) em uma fila; jobs em lote em outra, com workers próprios. Resolve boa parte dos casos com pouco esforço.
- Limite de concorrência por tenant nos workers. Cada tenant pode ter no máximo N jobs em execução ao mesmo tempo. O restante espera, mas não bloqueia ninguém. Dá para implementar com um semáforo distribuído no Redis.
- Fair queuing. Os workers fazem round robin entre tenants em vez de consumir na ordem de chegada. Cada tenant tem sua subfila lógica, e o despachante pega um job de cada tenant ativo por vez. Um lote de 300 mil jobs de um cliente não impede o job único do outro de rodar na próxima rodada.
- Filas dedicadas para tenants pesados. O cliente que gera volume desproporcional ganha fila e workers próprios, dimensionados e cobrados à parte.
Na minha experiência, a combinação de filas separadas para interativo e lote com limite de concorrência por tenant resolve a maioria dos casos antes de você precisar de fair queuing completo. O fair queuing é superior, mas exige que o broker suporte o padrão ou que você construa o despachante, e isso tem custo de manutenção.
Um detalhe que pega muita gente: retries. Se um job falha porque o banco está saturado e volta para a fila imediatamente, o tenant problemático multiplica a própria carga. Use backoff exponencial com jitter e um limite de tentativas, e mande para uma dead letter queue o que não tiver jeito.
Banco de dados: sharding e o tenant que domina o disco
O banco é quase sempre o último recurso compartilhado e o mais difícil de dividir. Você consegue subir mais instâncias de API em minutos; separar dados em produção leva semanas de planejamento. Por isso, vale esgotar as proteções mais baratas antes de partir para sharding de banco de dados.
Proteções antes do sharding
- Timeouts de query. No PostgreSQL,
statement_timeoutpode ser definido por role ou por sessão. Uma query de relatório que roda por dez minutos não deveria existir no caminho de uma requisição web. - Pooler com limites. PgBouncer ou equivalente evita que o autoscaling da aplicação abra milhares de conexões. Pools separados por tipo de carga (web, workers, relatórios) impedem que um esgote o outro.
- Réplicas de leitura para cargas analíticas. Relatórios e exportações vão para a réplica. O primário fica para escrita e leitura transacional. Atenção ao lag de replicação: o usuário que acabou de salvar algo e abre um relatório pode não ver o dado.
- Índices que começam por
tenant_id. Em modelo pool, um índice composto com o tenant na frente evita que a consulta de um cliente pequeno varra dados de todos. - Paginação por cursor.
OFFSETalto em tabelas de clientes grandes é um gerador clássico de carga desnecessária.
Quando partir para sharding
Sharding passa a fazer sentido quando o primário já está bem otimizado, os sintomas persistem e o crescimento projetado ultrapassa o que um único servidor maior consegue segurar. Para SaaS multi-tenant, o caminho natural é sharding por tenant: todos os dados de um cliente ficam no mesmo shard. Isso preserva joins e transações dentro do tenant, que é onde 99% das operações acontecem.
A peça central é um diretório de tenants: uma tabela (ou serviço) que diz em qual shard cada tenant mora. A aplicação consulta o diretório (com cache) e abre conexão no shard certo. Evite distribuir tenants por hash puro do ID. Com hash, mover um único cliente de lugar fica difícil, e mover o cliente pesado é exatamente o que você vai querer fazer.
Com o diretório, o fluxo de isolar um tenant pesado fica assim:
- Provisionar um shard novo (pode ser dedicado a esse cliente).
- Copiar os dados do tenant com replicação lógica, filtrando pelo
tenant_id. - Acompanhar o lag até ficar próximo de zero.
- Colocar o tenant em modo somente leitura por alguns segundos ou minutos, aguardar a sincronização final e virar a entrada no diretório.
- Liberar a escrita, validar e só depois remover os dados do shard antigo.
O passo quatro é onde mora o risco. Planeje a janela, comunique o cliente e tenha rollback pronto (voltar a entrada do diretório). Times que pulam o modo somente leitura acabam com escritas perdidas ou duplicadas, e reconciliar isso depois é muito pior do que alguns minutos de indisponibilidade parcial combinada.
| Estratégia de distribuição | Facilidade de mover tenant | Balanceamento automático | Comentário |
|---|---|---|---|
| Hash do tenant_id | Baixa | Bom para tenants homogêneos | Ruim quando poucos clientes concentram carga |
| Diretório (lookup) | Alta | Manual ou por regra | Melhor opção para SaaS com clientes de porte desigual |
| Por faixa (range) | Média | Ruim | Tende a concentrar clientes novos no mesmo shard |
Quando NÃO fazer sharding
Se o seu SaaS tem algumas dezenas de clientes, um banco bem dimensionado aguenta muito mais do que parece. Sharding cedo demais transforma relatórios globais, migrations e backups em projetos de engenharia. Um servidor maior, réplicas de leitura e um pooler bem configurado compram anos em muitos casos. A complexidade que você evita hoje é tempo que o time investe no produto.
Escalabilidade SaaS não depende de ter a arquitetura mais distribuída possível. Depende de saber qual será o próximo gargalo e ter um plano para ele antes que ele chegue.
Cache e armazenamento também contam
Dois pontos esquecidos. No cache, use namespace por tenant nas chaves e, se possível, quotas de memória por grupo de clientes: um tenant com milhões de objetos cacheáveis pode expulsar as chaves quentes de todo mundo. No armazenamento de arquivos, aplique quotas por tenant e cobre armazenamento como dimensão do plano. Disco cheio no volume compartilhado é um noisy neighbor que derruba todos de uma vez.
Perguntas frequentes
Qual a diferença entre rate limiting e quota?
Rate limiting controla a velocidade de consumo, como requisições por segundo ou por minuto, e protege a infraestrutura contra picos. Quota controla o volume total em um período longo, como chamadas de API por mês ou gigabytes armazenados, e costuma estar ligada ao plano comercial. Um SaaS bem desenhado usa os dois.
Preciso de Kubernetes para isolar tenants?
Não. Kubernetes facilita criar pools dedicados com namespaces, limites de CPU e memória e node pools separados, mas o isolamento de tenants depende mais da lógica da aplicação (identificação do tenant, limites, filas) do que do orquestrador. Dá para implementar tudo isso em VMs ou servidores dedicados, com menos camadas para operar.
Um schema por tenant resolve o noisy neighbor?
Não resolve. Schemas separados isolam os dados logicamente, mas todos continuam disputando a mesma CPU, a mesma memória e o mesmo disco do servidor de banco. Para isolamento de desempenho, o tenant precisa estar em outra instância ou ter limites aplicados antes de chegar ao banco.
Como identificar o tenant que está causando lentidão?
Propague o identificador do tenant em logs, traces e comentários nas queries SQL. Com isso, o log de queries lentas, o tempo de banco por requisição e o volume de jobs enfileirados podem ser agrupados por cliente. Sem esse rastreamento, você fica dependendo de palpite.
Devo cobrar mais do cliente que exige isolamento dedicado?
Em geral, sim. Um silo ou célula dedicada tem custo de infraestrutura e de operação que não se dilui entre outros clientes. O caminho mais comum é um plano enterprise com isolamento como benefício explícito, sustentado pelos dados de consumo por tenant.
Shuffle sharding vale a pena para SaaS pequeno?
Raramente. A técnica depende de uma frota com instâncias suficientes para gerar muitas combinações diferentes. Com poucos servidores, um pool dedicado para os clientes mais pesados entrega proteção parecida com bem menos complexidade.
Conclusão
Um SaaS multi-tenant saudável não depende de clientes bem-comportados. Ele parte do princípio de que algum cliente vai fugir do padrão e já tem as barreiras no lugar quando isso acontece. A ordem que costuma dar o melhor retorno é esta:
- telemetria por tenant, para saber quem consome o quê;
- rate limiting e limite de concorrência por tenant na borda;
- filas separadas e controle de concorrência nos workers;
- proteções no banco (timeouts, pooler, réplicas) e, só quando os dados pedirem, sharding com diretório de tenants;
- células ou pools dedicados para os clientes que justificam o custo.
Cada camada reduz o raio de impacto de um vizinho barulhento sem obrigar você a abrir mão das vantagens do compartilhamento. O erro mais caro costuma ser pular etapas, seja fazendo sharding antes de medir, seja colocando autoscaling para resolver um gargalo que está no banco.
Se o seu SaaS já sente o peso de um ou dois clientes grandes, ou se você está planejando a infraestrutura para crescer sem esse problema, a equipe da Toda Solução pode ajudar a desenhar a arquitetura. Isso inclui separar pools, dimensionar servidores de banco e réplicas e montar ambientes dedicados para os tenants que precisam de isolamento, em cloud, VPS ou infraestrutura própria.
