01 - Multi-Tenant

Pasted image 20251206093315.png

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.

Pasted image 20251206094358.png

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:

  1. Criamos uma política chamada select_machines_isolation_policy na tabela application.machines.

  2. Essa política é as permissive que 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.

  3. 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 insertupdatedelete ou até all.

  4. Iremos aplicar ao role application_user, ou seja, apenas esse role terá sua visibilidade das linhas controlada por essa política.

  5. Por fim, com o using, o PostgreSQL só retorna linhas onde tenant_id for igual ao valor da variável de configuração app.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.

Pasted image 20251206104337.png

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