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 tenant —
pg_dump --schema=proj_acmeexporta 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:
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
subdo 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
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
$ 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.
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:
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 sobreinformation_schema.schemata WHERE schema_name LIKE 'proj_%'. - JOIN entre schemas exige FQN.
proj_a.users JOIN proj_b.orderssó 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_statusemproj_a, ele não existe emproj_b. Pra tipos compartilhados, use o schemapublice dê grant. - Não tente sharing por views públicas. Funciona mas vira pesadelo de policy. Use
rpcfunctions empublicque recebemproj_slugcomo 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
- RLS — a camada de segurança dentro de cada schema
- Service-role vs anon — quem tem permissão de atravessar schemas
- PostgREST — como o REST sabe qual schema usar
- Glossário: multi-tenant
- Postgres docs — Schemas