Pular para o conteúdo
🧭 CONCEITOS

Multi-tenant — um prédio, vários apartamentos.

O SuperDB isola cada projeto em um schema Postgres próprio (proj_<slug>). Por que essa escolha em vez de tabelas compartilhadas ou bancos separados, e o que isso muda na prática.

O que é?

Multi-tenant significa servir várias "casas" (tenants) com a mesma infraestrutura. Cada tenant é um cliente, um projeto, uma empresa — e os dados de um não podem aparecer pro outro. A questão é como você separa.

Existem 4 modelos clássicos. O SuperDB escolheu o terceiro:

Modelo Como funciona Isolamento Custo
Shared table + tenant_id Uma tabela users com coluna tenant_id Fraco — esquecer um WHERE vaza tudo Mínimo
Shared schema Tabelas com prefixo tenant_a_users Médio — separadas mas no mesmo schema Baixo
Schema-per-tenant (SuperDB) Schema proj_acme, proj_xyz Forte — schemas são isolados nativamente Baixo
DB-per-tenant Banco Postgres separado por tenant Máximo — instâncias independentes Alto — conexões, backups, replicas × N

Schema-per-tenant é o sweet spot: isolamento forte como db-per-tenant, custo baixo como shared schema.

Por que importa?

Multi-tenancy mal feita é uma das fontes mais comuns de incidentes de segurança em SaaS. Se você compartilha tabela e esquece um WHERE tenant_id = ?, vaza dados de outro cliente. Aconteceu com gigantes.

Schema-per-tenant elimina essa classe inteira de bug:

  • Zero risco de vazamento cross-tenant — Postgres não mistura schemas em queries sem você pedir explicitamente
  • RLS aplicada por padrão — cada schema tem suas próprias policies
  • Backup por tenantpg_dump --schema=proj_acme exporta um cliente sem tocar os outros
  • Migrations isoladas — pode aplicar mudança num tenant antes de propagar pros demais
  • Limpeza por tenant — cliente cancelou? DROP SCHEMA proj_acme CASCADE
💡

Vendendo pra empresa: "schema-per-tenant" é argumento técnico que área de segurança de cliente B2B entende e aprova. Vale citar em SOC 2 / ISO 27001.

Como funciona no SuperDB

Quando você cria um projeto no Dashboard (ou via CLI / MCP), o SuperDB executa por baixo dos panos:

Postgres — internals
CREATE SCHEMA proj_acme;
GRANT USAGE ON SCHEMA proj_acme TO authenticated, anon, service_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA proj_acme
  GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO authenticated;

-- E uma row em superdb.projects pra registrar o projeto
INSERT INTO superdb.projects (slug, owner_id, schema_name)
VALUES ('acme', auth.uid(), 'proj_acme');

A partir daí:

  • Toda tabela que você cria pelo Studio vai pro schema proj_acme
  • PostgREST detecta o schema automaticamente via lookup no banco usando o claim sub do token — não via claim de projeto
  • O SDK envia o token em todo request — o servidor resolve o schema via sub → memberships
  • RLS roda dentro do schema, com auth.uid() referindo ao usuário daquele projeto
app.superdb.com.br/dash/acme/editor
v0.4.2
query.sql ×
1-- você só vê o schema do seu projeto
2select * from tasks; -- = proj_acme.tasks
3
4-- tentar acessar outro projeto:
5select * from proj_xyz.tasks;
6-- ERROR: permission denied for schema proj_xyz

Quando usar (e quando não)

Schema-per-tenant é o default do SuperDB porque cobre os casos comuns sem você pensar. Mas vale entender quando faz sentido e quando seria overkill:

Faz sentido quando…

  • SaaS B2B — cada cliente tem dados sensíveis, não pode ver os outros (CRM, ERP, contabilidade)
  • White-label — você vende a infra pra agências que revendem pra clientes finais
  • Compliance forte — LGPD, SOC 2, ISO 27001 — auditoria pede isolamento provável
  • Backups por cliente — quer poder exportar/restaurar 1 cliente sem tocar os outros

Talvez não precise quando…

  • App single-tenant — você tem 1 produto pra todos os usuários (rede social, marketplace público)
  • Pouquíssimos tenants gigantes — 2-3 clientes enterprise é melhor com DB-per-tenant
ℹ️

Nota: mesmo num app single-tenant, o SuperDB ainda cria um schema proj_<slug>. O overhead é zero (Postgres não cobra por schema vazio), então não vale evitar.

Exemplos práticos

1. Criar projeto via CLI gera schema novo

terminal
$ superdb projects create acme --region br-sao
✔ Projeto "acme" criado.
  Schema: proj_acme
  URL: https://api.superdb.com.br
  Anon key: eyJhbGciOi...

2. Signup no projeto X grava em proj_X.users

Um usuário do projeto acme que faz signup recebe um token com o claim sub (UUID). Quando ele acessa dados, PostgREST usa o sub para fazer lookup na tabela de memberships e resolve o schema para proj_acme automaticamente — sem o usuário saber.

app.tsx — fetch direto (recomendado)
const ANON_KEY = process.env.NEXT_PUBLIC_SUPERDB_ANON_KEY!

// Dados via fetch direto — api.superdb.com.br (sem /rest/v1/)
// O PostgREST resolve proj_acme.profiles via lookup do sub no token
const res = await fetch('https://api.superdb.com.br/profiles', {
  headers: {
    'Authorization': `Bearer ${userToken}`,
    'apikey': ANON_KEY,
  },
})
const profiles = await res.json()

3. Cross-schema query — só com service-role

Se você precisa agregar dados de múltiplos tenants (relatório de uso global, billing), use a service-role key em um job server-side:

scripts/usage-report.ts
const admin = createClient(URL, SERVICE_ROLE_KEY)

const { data } = await admin.rpc('global_usage_report')
// A função SQL itera por information_schema.schemata
// e soma rows.count de cada proj_*.events

Armadilhas comuns

⚠️

Cross-tenant queries são intencionalmente difíceis. Se você se pegar querendo JOIN proj_a.users JOIN proj_b.orders, pare. 99% das vezes isso é bug — e na 1% legítima (dashboards admin), use service-role num SQL function, não no client.

  • Migrations precisam rodar em cada schema. Use o Migration Runner do SuperDB (rodada por schema com SET search_path) ou um loop sobre information_schema.schemata WHERE schema_name LIKE 'proj_%'.
  • JOIN entre schemas exige FQN. proj_a.users JOIN proj_b.orders só funciona com service-role e tem que escrever os 2 schemas explicitamente. Sem service-role: permission denied.
  • Sequences e enums também são por schema. Se cria um enum order_status em proj_a, ele não existe em proj_b. Pra tipos compartilhados, use o schema public e dê grant.
  • Não tente sharing por views públicas. Funciona mas vira pesadelo de policy. Use rpc functions em public que recebem proj_slug como argumento.
  • Limite Postgres: em prática, Postgres suporta milhares de schemas sem problema. Acima de ~10k, queries no catálogo (pg_catalog) começam a ficar lentas — aí vale pensar em sharding por região.

Termos relacionados

Essa página ajudou?