Noções Básicas de Multi-Tenant
8 exemplos para você começar com padrões Multi-Tenant - 5 básicos e 3 intermediários.
Pré-requisitos
- PostgreSQL 18.4 com privilégios de criação de role.
- Um domínio SaaS onde cada objeto de negócio pertence a um tenant (organização, conta, workspace).
Exemplos Básicos
1. Coluna de Tenant em Cada Tabela de Negócios
O padrão mais simples: tenant_id em todas as linhas pertencentes a um tenant.
CREATE TABLE tenants (
tenant_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
plan text NOT NULL DEFAULT 'starter'
);
CREATE TABLE projects (
project_id bigint GENERATED ALWAYS AS IDENTITY,
tenant_id uuid NOT NULL REFERENCES tenants (tenant_id),
name text NOT NULL,
PRIMARY KEY (tenant_id, project_id)
);- Chave primária composta
(tenant_id, project_id)limita a identidade por tenant. - Cada consulta deve filtrar
tenant_id- a falta de filtro arrisca vazamentos entre tenants. - Indexe começando com
tenant_idpara consultas OLTP.
Relacionado: Schema Compartilhado + tenant_id - padrões de indexação e consulta
2. Restrições UNIQUE Escopadas
A unicidade se aplica por tenant, não globalmente.
CREATE TABLE projects (
tenant_id uuid NOT NULL,
project_id bigint GENERATED ALWAYS AS IDENTITY,
slug text NOT NULL,
PRIMARY KEY (tenant_id, project_id),
UNIQUE (tenant_id, slug)
);UNIQUE (tenant_id, slug)permite que dois tenants usem o slugapi.UNIQUE (slug)global quebra SaaS multi-tenant.- Revisões de aplicação e SQL devem sinalizar erros globais de unicidade.
3. Contexto de Tenant da Sessão
Define o tenant uma vez por conexão ou transação para filtragem consistente.
-- Aplicação define após autenticação
SET app.tenant_id = 'f47ac10b-58cc-4372-a567-0e02b2c3d479';
SELECT project_id, name FROM projects
WHERE tenant_id = current_setting('app.tenant_id')::uuid;- GUC customizado (
app.tenant_id) centraliza o contexto do tenant. - Emparelhe com RLS para que mesmo cláusulas
WHEREesquecidas sejam seguras. - Reinicie o GUC ao retornar a conexão para o pool do PgBouncer (
DISCARD ALLouRESET ALL).
Relacionado: Tenancy com Row-Level Security - modelos de política
4. Consulta de Detecção de Vizinho Barulhento
Encontre tenants consumindo recursos desproporcionais.
SELECT
tenant_id,
COUNT(*) AS row_count
FROM audit_events
WHERE logged_at > now() - interval '1 hour'
GROUP BY tenant_id
ORDER BY row_count DESC
LIMIT 10;- Contagens de
tenant_idcom picos sinalizam vizinhos barulhentos em schema compartilhado. - Combine com
pg_stat_statementsagrupado por tenant quando marcado em comentários. - O nível Enterprise pode precisar de isolamento schema-por-tenant ou database-por-tenant.
5. Tenant na Coluna Líder do Índice
Alinhe índices com caminhos de acesso WHERE tenant_id = ?.
CREATE INDEX projects_tenant_name_idx ON projects (tenant_id, name);
CREATE INDEX audit_events_tenant_time_idx
ON audit_events (tenant_id, logged_at DESC);tenant_idlíder permite varreduras de índice por tenant.- Dados de tenant de séries temporais se beneficiam de
(tenant_id, logged_at DESC). - A falta de
tenant_idem índices causa varreduras sequenciais em tabelas compartilhadas grandes.
Exemplos Intermediários
6. Esqueleto de Política de Row-Level Security
Isolamento de tenant imposto pelo banco de dados como rede de segurança.
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
ALTER TABLE projects FORCE ROW LEVEL SECURITY;
CREATE POLICY projects_tenant_isolation ON projects
USING (tenant_id = current_setting('app.tenant_id', true)::uuid)
WITH CHECK (tenant_id = current_setting('app.tenant_id', true)::uuid);FORCE ROW LEVEL SECURITYaplica políticas mesmo a proprietários de tabelas.WITH CHECKbloqueia inserções no tenant errado.- Políticas complementam, não substituem, filtros de aplicação.
Relacionado: Tenancy com Row-Level Security - modelos de nível empresarial
7. Schema por Tenant (Esboço)
Isolamento de namespace mais forte para o nível empresarial de médio porte.
CREATE SCHEMA tenant_acme;
CREATE SCHEMA tenant_globex;
CREATE TABLE tenant_acme.invoices (
invoice_id bigint PRIMARY KEY,
amount numeric(12, 2) NOT NULL
);- Cada schema de tenant duplica o DDL da tabela - risco de dispersão de migração.
search_pathda conexão ou nomes qualificados roteiam consultas.- Melhor isolamento do que tabelas compartilhadas; pior operação do que schema compartilhado + RLS.
Relacionado: Schema por Tenant - trade-offs
8. Banco de Dados por Tenant para Nível Regulamentado
Isolamento forte para clientes com alta conformidade.
CREATE DATABASE tenant_acme_db;
CREATE DATABASE tenant_globex_db;
\c tenant_acme_db
CREATE TABLE invoices (
invoice_id bigint PRIMARY KEY,
amount numeric(12, 2) NOT NULL
);- Backup, restauração e upgrade separados por banco de dados de tenant.
- Pool PgBouncer por banco de dados ou roteamento dinâmico no serviço de conexão.
- Custo operacional mais alto; maior contenção de raio de explosão.
Relacionado: Banco de Dados por Tenant - isolamento empresarial
Versões da Stack: Esta página foi escrita para PostgreSQL 18.4 (estável 18, manutenção 17), pgvector 0.8+, PgBouncer 1.x, Patroni 3.x e PostGIS 3.5+.