Multi-tenant: como isolar dados de cada cliente
Compare as três estratégias de isolamento em sistemas multi-tenant, coluna com RLS, schema por cliente e banco por cliente, com custos e riscos de cada uma.
Existem três formas de isolar dados por cliente: coluna tenant_id com Row Level Security, um schema por cliente ou um banco por cliente. Para a maioria dos SaaS, RLS no PostgreSQL é o melhor equilíbrio, porque o isolamento passa a ser garantido pelo banco e não pela disciplina de quem escreve a consulta.
Em um sistema que atende vários clientes na mesma instalação, o isolamento de dados pode ser feito de três formas: coluna tenant_id protegida por Row Level Security, um schema por cliente ou um banco de dados por cliente. Para a maioria dos SaaS, a primeira opção com RLS ativo é a escolha certa, porque transfere a garantia do isolamento do código para o banco. As outras duas entram quando o contrato ou o volume exigem.
Comparativo entre as três estratégias
| Critério | tenant_id + RLS | Schema por cliente | Banco por cliente |
|---|---|---|---|
| Força do isolamento | Alta, garantida pelo banco | Alta | Máxima |
| Custo por cliente novo | Quase zero | Baixo | Alto |
| Complexidade de migração de schema | Uma vez | Repetida por schema | Repetida por banco |
| Restaurar backup de um cliente só | Difícil | Médio | Trivial |
| Limite prático de clientes | Milhares | Centenas | Dezenas a poucas centenas |
| Consulta agregada entre clientes | Simples | Média | Difícil |
| Encaixa exigência de "dado separado" | Às vezes não convence | Parcialmente | Sim |
Opção 1: tenant_id com Row Level Security
Todas as tabelas ganham uma coluna tenant_id. Até aí, é o modelo que quase todo mundo usa. A diferença está em quem garante o filtro.
Filtrar na aplicação funciona até o dia em que alguém escreve um relatório novo às pressas e esquece o WHERE tenant_id = ?. Não é hipótese: é o modo de falha mais comum de sistema multi-tenant, e o resultado é dado de um cliente aparecendo na tela de outro.
Row Level Security resolve isso no PostgreSQL. Você ativa a política na tabela e o banco passa a aplicar o filtro sozinho, em qualquer consulta:
ALTER TABLE pedidos ENABLE ROW LEVEL SECURITY;
ALTER TABLE pedidos FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolado ON pedidos
USING (tenant_id = current_setting('app.tenant_id')::uuid);
A aplicação define app.tenant_id no início de cada transação, a partir da sessão autenticada. Uma consulta sem filtro passa a retornar zero linhas em vez de retornar tudo. Erro de programação vira ausência de dado, não vazamento.
Três cuidados que costumam ser esquecidos:
FORCE ROW LEVEL SECURITYé necessário porque o dono da tabela, por padrão, ignora as políticas. Rode a aplicação com um usuário que não seja o dono.- Índices compostos começando por
tenant_id((tenant_id, criado_em)), senão o desempenho cai conforme a base cresce. - Rotinas de manutenção e relatórios rodam fora do contexto da requisição e precisam de um caminho explícito e auditado.
Opção 2: um schema por cliente
Cada cliente ganha um schema próprio dentro do mesmo banco, com as mesmas tabelas. A aplicação define o search_path de acordo com o cliente conectado.
Vantagens reais: separação visível, backup e restauração por schema mais simples, e a possibilidade de um cliente ter uma coluna extra sem afetar os outros.
O custo aparece na manutenção. Toda alteração de estrutura precisa rodar em todos os schemas, o que vira um processo demorado e sujeito a divergência quando são centenas. Bancos com muitos milhares de tabelas também sofrem no planejador de consultas e no tempo de dump.
Faz sentido para dezenas ou poucas centenas de clientes corporativos, especialmente se houver customização por cliente.
Opção 3: um banco por cliente
Isolamento máximo. Vazamento entre clientes deixa de depender do código e passa a depender de credencial. Restaurar apenas o cliente que apagou dados por engano fica trivial. Cliente grande com exigência de "base separada" no contrato fica atendido sem discussão.
O preço é operacional e cresce linear com a base de clientes: cada banco precisa de migração, backup, monitoramento, ajuste de conexão e recursos mínimos. Trinta clientes com trinta bancos ainda dá para automatizar; trezentos exigem ferramental próprio e alguém dedicado.
Sinais de que essa é a opção certa: poucos clientes de ticket alto, exigência contratual ou regulatória de separação física, ou necessidade de manter clientes em regiões diferentes.
O modelo híbrido, que é o mais usado na prática
A arquitetura que costuma sobreviver ao crescimento não escolhe uma só:
- Padrão: todos os clientes em banco compartilhado, com
tenant_ide RLS. - Exceção: clientes que exigem por contrato, ou que sozinhos representam volume desproporcional, ganham banco dedicado.
- Requisito de projeto: a aplicação resolve a conexão a partir do identificador do cliente, sem saber se ele está no banco compartilhado ou no dedicado.
Se essa resolução de conexão for construída desde o começo, mesmo que exista um único banco, promover um cliente para base dedicada depois vira configuração, e não reescrita. É o padrão que usamos nos sistemas multi-tenant que a Retti Tech mantém em produção.
Isolamento não termina no banco
Dado separado no banco e misturado no resto não isola nada. Verifique também:
- Arquivos enviados. Prefixo por cliente no armazenamento e URLs assinadas com prazo, nunca caminho previsível e público.
- Cache. Chave de cache sem o identificador do cliente é a forma mais silenciosa de vazar dado entre clientes.
- Filas e tarefas em segundo plano. O worker precisa reestabelecer o contexto do cliente ao processar a mensagem, fora da requisição HTTP, o RLS não tem de onde tirar o
tenant_id. - Busca e índices vetoriais. Em sistemas com RAG, o filtro por cliente tem que entrar na consulta ao índice, não na filtragem posterior.
- Logs e mensagens de erro. Rastro de exceção com dado de cliente vai parar em ferramenta de terceiro.
Como testar se o isolamento funciona
Não confie na leitura do código. Escreva testes automatizados que provem a separação:
- Crie dois clientes com dados semelhantes.
- Autentique como o cliente A e tente ler, alterar e apagar um registro do cliente B pelo identificador direto.
- Repita para cada endpoint da API, inclusive relatórios e exportações.
- Verifique que a resposta é 404 ou 403, nunca o dado.
- Repita o teste para arquivos, cache e tarefas em segundo plano.
Esses testes precisam rodar em cada alteração. Isolamento é o tipo de propriedade que quebra em silêncio: nada falha, nada dá erro, e alguém simplesmente enxerga o que não deveria.
Perguntas frequentes
Qual a forma mais segura de isolar dados em multi-tenant?
Banco separado por cliente é a mais segura, porque o vazamento entre clientes exige uma falha de credencial, não um bug de consulta. O custo operacional é o mais alto: migrações, backups e monitoramento se multiplicam por cliente.
Filtrar por tenant_id na aplicação é suficiente?
É o padrão mais comum e o mais frágil. Basta um desenvolvedor esquecer o filtro em uma consulta para expor dado de outro cliente. Se optar por esse caminho, o filtro precisa ser imposto por Row Level Security no banco, não confiado ao código.
Dá para começar com um modelo e mudar depois?
Dá, e é o caminho normal. Comece com tenant_id e RLS, que serve a maior parte dos casos, e migre para banco dedicado apenas os clientes que exigirem por contrato. Modelos híbridos são comuns em SaaS que atendem empresas grandes.
Tem um processo que consome o time?
Me conta como funciona hoje. Se der para automatizar, eu te digo por onde começar — a conversa de diagnóstico não é cobrada.
Natiam Gabriel é AI Engineer & Full-Stack Developer e fundador da Retti Tech, estúdio de desenvolvimento de software que atende empresas em todo o Brasil. Constrói sistemas web sob medida, automações e integrações, com ou sem inteligência artificial, do levantamento de requisitos até a operação em produção. Mais de 20 empresas usam em produção os sistemas que desenvolveu.