Voltar ao blog
SaaSMulti-tenantArquiteturaPostgreSQLSegurança

Arquitetura multi-tenant para SaaS: guia completo

2026-06-06
Arquitetura multi-tenant para SaaS: guia completo

Toda decisão difícil em um SaaS começa na mesma pergunta: como separar os dados de um cliente dos dados de outro? A resposta (sua arquitetura multi-tenant) define custo de infraestrutura, esforço de operação, postura de segurança e o teto de escala do produto. Acertar no dia 1 é barato. Corrigir depois de 500 clientes em produção é um dos projetos mais caros e arriscados que uma empresa de software pode encarar.

Este guia destrincha os três modelos clássicos de multi-tenancy, os trade-offs reais de cada um, e como tratar isolamento, billing, observabilidade e performance sem virar refém de decisões precoces.

O que é multi-tenancy, afinal

Em um SaaS multi-tenant, uma única aplicação atende múltiplos clientes (tenants) sobre a mesma base de código e, geralmente, a mesma infraestrutura. Cada tenant enxerga apenas seus próprios dados, usuários e configurações, como se tivesse um sistema só seu.

O oposto é o modelo single-tenant, em que cada cliente roda uma instância isolada da aplicação e do banco. Single-tenant simplifica o isolamento, mas multiplica o custo operacional por cliente: cada deploy, cada migração e cada patch de segurança precisa ser replicado N vezes. Multi-tenancy concentra a operação, e é por isso que praticamente todo SaaS moderno nasce multi-tenant.

A questão central não é se você vai compartilhar recursos, mas em qual camada você desenha a fronteira entre tenants.

Os três modelos de multi-tenancy

1. Banco de dados por tenant (database-per-tenant)

Cada cliente recebe seu próprio banco de dados. A aplicação é compartilhada, mas a camada de dados é fisicamente separada.

  • Isolamento: máximo. Um bug de query jamais vaza dados entre tenants, porque eles vivem em bancos diferentes.
  • Backup e restore por cliente: trivial, você restaura um banco sem tocar nos demais.
  • Custo e operação: o ponto fraco. Centenas ou milhares de bancos viram um pesadelo de provisionamento, monitoramento e migração de schema.

Indicado para poucos tenants de alto valor, com requisitos rígidos de compliance (saúde, finanças, governo) ou contratos que exigem isolamento físico.

2. Schema por tenant (schema-per-tenant)

Um único banco, mas cada tenant tem seu próprio schema (no PostgreSQL, um namespace lógico de tabelas). É um meio-termo: mais isolamento que tabelas compartilhadas, menos overhead que bancos separados.

  • Isolamento: bom, no nível lógico. As tabelas de cada tenant são distintas.
  • Migração: mais complexa. Alterar o schema significa aplicar a mudança em todos os schemas, um a um.
  • Limite prático: o PostgreSQL degrada com dezenas de milhares de schemas e tabelas (catálogo gigante, autovacuum sob pressão). Funciona bem na casa das centenas, não dos milhões.

3. Tabela compartilhada com tenant_id (shared schema / pooled)

Todos os tenants vivem nas mesmas tabelas, e cada linha carrega uma coluna tenant_id que identifica o dono. É o modelo mais comum em SaaS de larga escala.

  • Custo e operação: ótimo. Uma migração serve para todos; um único banco para monitorar.
  • Escala: suporta milhares ou milhões de tenants sem explosão de objetos no catálogo.
  • Risco: o isolamento depende inteiramente da aplicação e de políticas de banco. Esquecer um WHERE tenant_id = ? em uma query é um vazamento de dados entre clientes. Por isso o RLS (adiante) é praticamente obrigatório aqui.

Comparativo rápido

Critério Banco por tenant Schema por tenant Tabela compartilhada
Isolamento de dados Máximo (físico) Bom (lógico) Depende de RLS/app
Custo por tenant Alto Médio Baixo
Facilidade de migração Baixa Média Alta
Escala (nº de tenants) Dezenas/centenas Centenas/milhares Milhares/milhões
Backup/restore por cliente Trivial Médio Complexo
Risco de vazamento entre tenants Mínimo Baixo Alto sem proteção

Não existe modelo "certo": existe o modelo certo para o seu estágio. Muitos SaaS começam com tabela compartilhada e isolam apenas clientes enterprise específicos em bancos dedicados, num modelo híbrido.

Segurança e isolamento: onde a maioria erra

No modelo de tabela compartilhada, a segurança não pode depender de o desenvolvedor lembrar de filtrar por tenant_id. A defesa precisa estar no banco.

O Row-Level Security (RLS) do PostgreSQL aplica filtros automaticamente a toda query, com base no contexto da sessão. Mesmo que uma query esqueça o filtro, o banco só retorna as linhas do tenant ativo:

-- Habilita RLS na tabela
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
ALTER TABLE invoices FORCE ROW LEVEL SECURITY;

-- Política: cada sessão só enxerga linhas do seu tenant
CREATE POLICY tenant_isolation ON invoices
  USING (tenant_id = current_setting('app.current_tenant')::uuid);

-- A aplicação define o tenant no início de cada requisição/transação
SET app.current_tenant = '8f3b...e21';

-- A partir daqui, qualquer SELECT/UPDATE/DELETE é filtrado pelo banco,
-- mesmo sem WHERE tenant_id explícito.
SELECT * FROM invoices;

Boas práticas complementares de isolamento:

  • Defesa em profundidade: RLS no banco e filtro por tenant na camada de aplicação. Uma camada protege a outra.
  • Conexão com role restrita: a aplicação nunca deve conectar como superusuário, que ignora RLS.
  • Criptografia: dados sensíveis criptografados em repouso (disco, backups) e em trânsito (TLS). Para dados altamente sensíveis, considere chaves de criptografia por tenant.
  • Testes automatizados de isolamento: crie testes que tentam, de propósito, ler dados de outro tenant. Eles devem falhar sempre.

Billing e planos

O modelo de cobrança costuma se acoplar à arquitetura. Independentemente do modelo de dados, você precisa de:

  • Identidade clara do tenant ligada a uma assinatura (plano, ciclo, status).
  • Medição de uso (metering): registros de eventos faturáveis (chamadas de API, armazenamento, assentos de usuário) agregados por tenant.
  • Enforcement de limites: o plano define cotas (ex.: 10 mil requisições/mês), e a aplicação precisa aplicá-las graciosamente, sem derrubar o serviço.

Mantenha o metering desacoplado da lógica de produto. Emita eventos de uso e processe-os em um pipeline próprio; assim você troca de provedor de billing (Stripe, etc.) sem reescrever o produto.

Observabilidade por tenant

Em multi-tenancy, "o sistema está lento" é uma frase inútil. Você precisa saber para qual tenant está lento. Instrumente desde cedo:

  • tenant_id em todo log, métrica e trace. Sem isso, diagnosticar incidentes vira adivinhação.
  • Dashboards segmentáveis por tenant, para isolar quem está consumindo recursos ou sofrendo erros.
  • Alertas com contexto de tenant, para que o on-call saiba imediatamente o raio de impacto.

Essa granularidade também alimenta o billing por uso e a análise de churn: o mesmo dado serve a engenharia, finanças e produto.

Performance e o problema do "noisy neighbor"

Quando tenants compartilham recursos, um cliente abusivo pode degradar a experiência de todos os outros. É o clássico noisy neighbor. Mitigações:

  • Rate limiting por tenant, não global. Cada tenant tem seu próprio orçamento de requisições.
  • Cotas de recursos (CPU, conexões de banco, jobs em fila) por tenant.
  • Filas isoladas ou prioridades distintas para evitar que um lote pesado de um cliente trave o processamento dos demais.
  • Índices com tenant_id à frente. Em tabelas compartilhadas, índices compostos começando por tenant_id mantêm as queries rápidas mesmo com bilhões de linhas.
  • Particionamento por tenant (partitioning) para tabelas muito grandes, isolando fisicamente os dados mais quentes.

Migrações e versionamento

Migração de schema é onde os modelos divergem mais na prática. No modelo de tabela compartilhada, uma migração roda uma vez e vale para todos. Simples, porém arriscado: um erro afeta todos os tenants de uma vez. Adote migrações compatíveis para frente e para trás (expand/contract): adicione colunas novas como opcionais, faça o deploy do código que as usa e só então remova o antigo. Isso permite deploys sem downtime e rollback seguro.

No modelo de banco ou schema por tenant, a migração precisa ser orquestrada em lote, com rastreamento de quais tenants já foram migrados e capacidade de retomar de onde parou em caso de falha.

Erros comuns que cobram caro depois

  • Adicionar tenant_id tarde demais. Retrofitar tenancy em um schema que nasceu single-tenant é doloroso. Modele o tenant desde a primeira tabela.
  • Confiar só na aplicação para isolar dados. Sem RLS, um único bug vira incidente de vazamento. Coloque a defesa no banco.
  • Não instrumentar por tenant. Você só sente a falta quando o incidente já está acontecendo e não dá para segmentar nada.
  • Escolher banco por tenant cedo demais. O isolamento máximo seduz, mas o custo operacional pode sufocar uma equipe pequena antes da escala chegar.
  • Ignorar o noisy neighbor. Sem limites por tenant, o cliente que mais cresce vira o que mais degrada a base.

Como escolher o seu modelo

Em resumo prático:

  • Poucos clientes enterprise, compliance pesado, isolamento físico contratual? Banco por tenant.
  • Dezenas a centenas de tenants, com necessidade de isolamento lógico forte? Schema por tenant.
  • Escala alta, custo enxuto, milhares de tenants? Tabela compartilhada com tenant_id e RLS.
  • Realidade da maioria? Híbrido: a base no modelo compartilhado, com contas enterprise isoladas em bancos dedicados conforme a demanda aparece.

A decisão raramente é permanente, mas mudá-la é caro. Modele o tenant_id desde o início, blinde o banco com RLS e instrumente tudo por tenant, assim você mantém a porta aberta para evoluir o modelo sem reescrever o produto.


Construindo um SaaS multi-tenant? Na ConstructorTI, arquitetura multi-tenant, isolamento e billing entram no blueprint desde o dia 1, não como remendo depois que o primeiro cliente enterprise aperta. Se você quer um produto que escala sem virar dívida técnica, fale com a nossa engenharia e saia da conversa com um diagnóstico técnico do seu modelo de tenancy.