01 - Multi-Tenant

Tenants separados por linhas
Imagine agora que temos um único schema com as relações da nossa aplicação. Em todas as tabelas, colocaremos o tenant a qual aquela linha faz parte. Veja o exemplo abaixo. Temos uma tabela para as orgs, uma para contacts e uma para opportunities. Ambos contatos e oportunidades possuem org_id que referencia as organizações.

Modelar por linhas é muito mais escalável simplesmente por que a manutenção é muito mais simples. Migrations acontecem uma única vez, a estrutura é única e não existe o risco de divergência entre schemas. O fato de o PostgreSQL já ter a criação de políticas pronta facilita mais ainda.
Aqui não existe isolamento físico, todos os dados convivem nas mesmas tabelas. O que existe é isolamento lógico, garantido pelas políticas de Row Level Security. Você precisa ter muito cuidado na criação das políticas exatamente por isso. Configurar mal o RLS pode ser arriscado.
Vamos explorar mais como utilizar o RLS na prática.
Aplicação prática
Vamos imaginar uma aplicação que registre dados de máquinas. Vamos ter máquinas, empresas, que são nossos tenants, e usuários
create table multitenancy.tenants(
id uuid not null default gen_random_uuid(),
company_name text unique not null,
created_at timestamp with time zone not null default current_timestamp,
primary key(id)
);
create table multitenancy.users(
id uuid not null default gen_random_uuid(),
email text not null,
pwd text not null,
pwd_validity tstzrange not null,
primary key(id)
);
create table application.machines(
macaddr macaddr8 not null,
ip inet not null,
os text not null,
os_ver text not null,
owner uuid not null,
last_access timestamp with time zone not null default current_timestamp,
active boolean not null default true,
tenant_id uuid not null,
foreign key (tenant_id) references multitenancy.tenants(id) on delete cascade,
foreign key (owner) references multitenancy.users(id) on delete cascade,
primary key(macaddr)
);
Quando colocamos um tenant_id similar ao exemplo acima com org_id, sem as políticas de RLS, teríamos que inserir em todas as queries um SELECT … WHERE com a empresa do usuário.
select * from application.machines
where tenant_id = “MyCompany”;
Para não precisar disso e aplicar políticas de segurança, vamos ativar o Row Level Security. Quando o RLS está ativo, o PostgreSQL passa a aplicar políticas de segurança para decidir quais linhas um usuário pode ver, inserir, atualizar ou deletar.
alter table application.machines enable row level security;
Isso ativa o controle de acesso por linha na tabela application.machines.
Sem uma política, o usuário simplesmente não enxerga nenhuma linha (por segurança). Vamos criar uma política para ver como funciona:
create policy select_machines_isolation_policy
on application.machines
as permissive
for select
to application_user
using text = current_setting(’app.current_tenant’, true);
Vamos entender melhor como a política funciona:
-
Criamos uma política chamada
select_machines_isolation_policyna tabelaapplication.machines. -
Essa política é
as permissiveque concatena políticas, ou seja, é um “or” lógico. Quando você quer que uma política seja adicionada independente das outras, criamos uma política restrictive, ou seja, um “and” lógico. -
Seleciona o tipo de operação em
for select. Nesse caso, a política vale apenas para operações de select, mas poderia ser para insert, update, delete ou até all. -
Iremos aplicar ao role
application_user, ou seja, apenas esse role terá sua visibilidade das linhas controlada por essa política. -
Por fim, com o
using, o PostgreSQL só retorna linhas ondetenant_idfor igual ao valor da variável de configuraçãoapp.current_tenant
O usuário application_user só consegue fazer select nas máquinas que pertencem ao tenant atual.
Se você quiser ver mais detalhes em um caso real, sugiro ver essa aplicação que está disponível no meu GitHub.
https://github.com/andresavalerio/multi-tenancy
Bônus: Sharding + RLS
No início, comentei que o modelo linha-por-tenant não oferece isolamento físico. Porém, existe uma alternativa: combinar sharding com RLS. Assim você cria múltiplos nós isolados e ainda mantém a simplicidade do RLS dentro de cada shard.

Aplicações mudam. Frameworks mudam. Mas seus dados ficam. Por isso, colocar o SGBD no centro da arquitetura não é só uma decisão técnica, é uma estratégia de longevidade.
Referências
https://www.crunchydata.com/blog/designing-your-postgres-database-for-multi-tenancy
FONTE:
https://andresavalerio.substack.com/p/arquitetando-apis-multi-tenant-modernas
Próximo Artigo
02 - Conceito mais detalhado de RLS no PostgresSQL na prática