en
· 3 min de leitura

Multi-tenancy: a decisão que você não consegue desfazer

Schema compartilhado, schema por tenant ou banco por tenant: cada um tem suas dores, e trocar depois significa migrar tudo.

system-designarchitecture

Existe uma decisão em um produto SaaS que você toma uma vez, cedo, geralmente antes de ter clientes, e que molda toda tabela, toda query, todo backup e toda conversa de compliance pelo resto da vida do produto. É como você isola tenants. Já vi times tentarem mudar isso depois. Nunca é a migração de uma coisa. É a migração de tudo.

Os três modelos

Schema compartilhado: as linhas de todos os tenants vivem nas mesmas tabelas, separadas por uma coluna tenant_id. O mais barato de operar, o mais fácil de migrar, e uma query sem WHERE tenant_id vaza dados entre clientes.

Schema por tenant: um banco, um schema por tenant. Isolamento mais forte, restore por tenant vira viável, mas as migrations agora rodam N vezes e o pool de conexões fica esquisito passando de algumas centenas de tenants.

Banco por tenant: isolamento total, backup e restore por tenant triviais, encaixe natural para exigências de residência de dados. Operacionalmente é uma frota: N bancos para monitorar, atualizar, migrar e pagar.

Nenhum deles é o certo. Cada um é um conjunto diferente de dores, e a questão é escolher as dores com que você consegue conviver.

O que muda de verdade

Isolamento é o eixo óbvio, mas os práticos importam mais. Vizinhos barulhentos: em schema compartilhado, um tenant rodando um relatório pesado deixa todos os outros tenants do mesmo banco lentos, e não existe botão para resolver isso além de throttling por query. Por banco, o barulho fica dentro das próprias paredes.

Backup e restore: um cliente pede para restaurar os dados dele para ontem porque um admin apagou tudo. Com banco por tenant, é operação de rotina. Com schema compartilhado, é uma extração cirúrgica cuidadosa a partir de um restore completo, e leva um dia se você tiver sorte.

Compliance e residência: alguns clientes vão exigir que os dados fiquem em um país específico ou fisicamente separados dos outros clientes. Schema compartilhado não oferece isso sem abrir uma exceção, e a exceção vira uma segunda arquitetura.

Migrations em escala: uma mudança de schema no modelo compartilhado é uma migration. Nos modelos por tenant são centenas, algumas vão falhar no meio, e agora seus tenants estão em versões diferentes do schema.

A disciplina do tenant_id

Se você for de schema compartilhado, e a maioria dos produtos deveria começar por aí, tenant_id vai em toda tabela. Não só nas principais. Toda tabela de junção, todo log, todo anexo. E toda query filtra por ele, com enforcement na camada de acesso a dados, não por convenção.

Depois adicione row level security no banco como rede de proteção. Defina o tenant na conexão, deixe a policy filtrar as linhas, e um WHERE esquecido retorna nada em vez de retornar tudo. Já encontrei o WHERE faltando em mais auditorias do que gostaria de admitir. A rede pega.

A baleia

Todo SaaS uma hora fecha um tenant dez ou cem vezes maior que os outros. A baleia quebra as premissas compartilhadas: os índices dela dominam, os relatórios dela viram o vizinho barulhento de todo mundo, e o time de compliance dela pede coisas que os outros nunca pediram.

Projete para a chegada da baleia mesmo que ainda não esteja construindo para ela. O híbrido mais prático é schema compartilhado por padrão com a capacidade de mover um tenant para o próprio banco, o que exige que tenant_id já exista em todo lugar e que seu código nunca assuma uma única connection string.

Por que não dá para desfazer

Sair de compartilhado para por tenant significa extrair as linhas de cada tenant de cada tabela para casas novas, com o produto rodando, e reescrever cada trecho de código que assume um banco só. Ir no sentido contrário significa fundir N schemas e resolver toda colisão de id. Os dois são projetos de meses com risco real de perda de dados.

Então faça a escolha sabendo o trade-off, documente, e construa a saída de emergência para a baleia no primeiro dia. É a única decisão que não fica mais barata com o tempo.