Arquitetura Multi-Tenant para SaaS: Guia Completo para CTOs
Os três modelos de multi-tenancy em SaaS B2B — schema isolation, row isolation e instância dedicada — com trade-offs reais e quando usar cada um.
O que é multi-tenancy e por que todo SaaS B2B precisa pensar nisso
Multi-tenancy é a capacidade de um único sistema servir múltiplos clientes (tenants) de forma isolada — cada cliente vê apenas seus próprios dados, mesmo que todos estejam rodando na mesma infraestrutura.
É o modelo que diferencia SaaS de software instalado por cliente. E a forma como você implementa esse isolamento vai afetar custo, performance, segurança e velocidade de desenvolvimento por anos.
Os três modelos principais
Modelo 1: Banco de dados separado por tenant (Silo)
Cada cliente tem seu próprio banco de dados (ou schema + instância dedicada). Isolamento máximo.
Tenant A → Database A (postgres://db-a)
Tenant B → Database B (postgres://db-b)
Tenant C → Database C (postgres://db-c)
Vantagens:
- Isolamento perfeito — um bug nunca vaza dados entre tenants
- Fácil de oferecer customizações por tenant (schema customizado)
- Backup, restore e migração por tenant independente
- Ideal para clientes com requisitos de data residency
Desvantagens:
- Custo operacional linear com o número de tenants
- Migrações de schema precisam ser aplicadas em N bancos
- Connection pool separado por tenant (limite de conexões no PostgreSQL)
- Difícil fazer queries analíticas cross-tenant
Quando usar: enterprise puro, clientes que pagam > US$ 50.000/ano e exigem isolamento contratual, regulated industries (saúde, fintech, governo).
Modelo 2: Schema separation (Pool com isolamento)
Um único banco de dados PostgreSQL, mas cada tenant tem seu próprio schema.
-- Tenant A
SET search_path TO tenant_a;
SELECT * FROM users; -- vê apenas usuários do tenant A
-- Tenant B
SET search_path TO tenant_b;
SELECT * FROM users; -- vê apenas usuários do tenant B
Vantagens:
- Isolamento forte sem o custo de bancos separados
- Uma única migração aplica em todos os schemas (com ferramentas certas)
- Fácil de testar isolamento (mudança de search_path)
- Bom equilíbrio entre isolamento e custo operacional
Desvantagens:
- Escala bem até ~200–500 tenants, depois começa a ter overhead
- Queries analíticas cross-tenant requerem
UNION ALLou views especiais - PostgreSQL tem limite de ~10.000 schemas por banco na prática
Quando usar: SaaS B2B com base de clientes pequena a média (até algumas centenas de tenants ativos), especialmente quando compliance exige isolamento de dados.
Usamos esse modelo no TatameLabs — cada academia tem seu schema isolado, o que simplificou a conformidade com LGPD e facilitou demonstrar isolamento para auditorias.
Modelo 3: Row-level isolation (Pool compartilhado)
Uma única tabela para todos os tenants, diferenciados por tenant_id.
CREATE TABLE users (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL REFERENCES tenants(id),
name TEXT NOT NULL,
email TEXT NOT NULL
);
-- Row-Level Security no PostgreSQL
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON users
USING (tenant_id = current_setting('app.current_tenant')::UUID);
Vantagens:
- Escala horizontal com facilidade (funciona com milhares de tenants)
- Uma única migração atualiza todos os tenants
- Queries analíticas cross-tenant triviais
- Menor custo operacional por tenant
Desvantagens:
- Um bug de autorização pode vazar dados de múltiplos tenants
- Requer disciplina extrema: todo query deve filtrar por tenant_id
- Performance pode degradar em tenants grandes que dominam o espaço
- Difícil de oferecer isolamento contratual para enterprise
Quando usar: SaaS B2C ou B2B com muitos tenants pequenos, PLG (Product-Led Growth), quando velocidade de escala horizontal é prioridade.
Como garantir isolamento no código (independente do modelo)
Middleware de tenant context
// middleware.ts (Next.js)
export function middleware(request: NextRequest) {
const tenantId = extractTenantId(request) // via subdomain ou JWT
request.headers.set('x-tenant-id', tenantId)
return NextResponse.next({ request })
}
// No servidor, sempre injete o tenant no contexto
async function getTenantContext(headers: Headers): Promise<TenantContext> {
const tenantId = headers.get('x-tenant-id')
if (!tenantId) throw new UnauthorizedError()
return { tenantId }
}
Repository pattern com tenant enforced
class UserRepository {
constructor(private readonly db: Database, private readonly tenantId: string) {}
async findById(id: string): Promise<User | null> {
// tenant_id é sempre injetado — nunca esquecemos
return this.db.query(
'SELECT * FROM users WHERE id = $1 AND tenant_id = $2',
[id, this.tenantId]
)
}
}
// Uso: o tenantId sempre vem do contexto autenticado, nunca do cliente
const repo = new UserRepository(db, ctx.tenantId)
Essa abordagem garante que é impossível instanciar o repository sem um tenantId. O compilador te protege.
Identificação de tenant: subdomain vs. path vs. JWT
Subdomain (recomendado para B2B)
acme.seuapp.com → tenant: acme
beta.seuapp.com → tenant: beta
Vantagens: URL limpa, branding por tenant, fácil de fazer custom domain depois.
Path prefix
seuapp.com/acme/dashboard
seuapp.com/beta/dashboard
Vantagens: mais simples de implementar. Desvantagem: URL feia, difícil de fazer custom domain.
JWT claim
O tenant_id fica no payload do JWT após o login. Funciona bem quando o mesmo usuário pode pertencer a múltiplos tenants (ex: consultores que acessam múltiplas contas).
Migrações em arquitetura multi-tenant
Migrações são onde a maioria dos projetos multi-tenant encontra problemas. Cada modelo tem seu desafio:
Schema separation: migração por schema
# Script de migração que percorre todos os schemas
psql -c "SELECT schema_name FROM information_schema.schemata WHERE schema_name LIKE 'tenant_%'" \
| while read schema; do
flyway -schemas=$schema migrate
done
Use ferramentas que suportem multi-schema nativamente: Flyway com scripts por schema, ou um runner customizado.
Row isolation: uma migração, todos os tenants
A vantagem aqui é clara: ALTER TABLE ou nova coluna aplica para todos automaticamente. Mas cuidado com migrations lentas em tabelas grandes — planeje downtime ou use ALTER TABLE ... CONCURRENTLY.
Checklist de decisão
Responda estas perguntas para escolher o modelo certo:
-
Quantos tenants você espera ter em 2 anos?
- < 100 → qualquer modelo funciona
- 100–1.000 → schema separation é bom
-
1.000 → row isolation ou híbrido
-
Clientes enterprise exigem isolamento contratual?
- Sim → banco separado (pelo menos para esses clientes)
- Não → schema separation ou row isolation
-
Você precisa de queries analíticas cross-tenant?
- Sim → row isolation facilita muito
- Não → schema separation é suficiente
-
Qual é o seu modelo de pricing?
- Enterprise (< 50 clientes grandes) → isolamento por banco
- Mid-market (50–500 clientes) → schema separation
- PLG / self-serve (500+ clientes) → row isolation
O modelo híbrido que usamos na Codevops
Para SaaS B2B em crescimento, frequentemente implementamos um modelo híbrido:
- Tenants padrão: row-level isolation com RLS no PostgreSQL
- Tenants enterprise: schema separation sob demanda
- Tenants regulados (saúde, fintech): banco dedicado com contrato
Isso permite começar com custo operacional baixo e oferecer o nível de isolamento certo à medida que os contratos justificam.
Precisa definir a arquitetura multi-tenant do seu SaaS? Fazemos diagnósticos técnicos gratuitos de 45 minutos.
Agendar diagnóstico de arquitetura → · Guia completo de desenvolvimento SaaS → · Quanto custa um SaaS? →
Precisa de ajuda com arquitetura?
A Codevops transforma ideias em produtos reais. Cuidamos de toda a parte técnica para que você foque no seu negócio. Respondemos em até 12 horas.
Falar com especialista →