Arquitetura

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.

Everton Tubarao··6 min de leitura

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 ALL ou 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:

  1. 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

  2. Clientes enterprise exigem isolamento contratual?

    • Sim → banco separado (pelo menos para esses clientes)
    • Não → schema separation ou row isolation
  3. Você precisa de queries analíticas cross-tenant?

    • Sim → row isolation facilita muito
    • Não → schema separation é suficiente
  4. 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 →