Parecer sobre o schema de um SaaS B2B (~200 tenants, PostgreSQL 15) trazido pelo cliente para revisão. Auditoria contra o cânon de modelagem e as necessidades declaradas: crescimento 3× em 18 meses e certificação SOC 2 no roadmap.
VEREDITO: APROVADO_COM_RESSALVAS
Achados
- [ALTA] Isolamento de tenant por disciplina, não por construção. 14 de 22 tabelas têm
tenant_id, mas 6 consultas do relatório de uso fazem join sem o filtro — um esquecimento em query nova vaza dados entre clientes. Recomendação: escopo automático na camada de acesso (ou RLS) + teste de isolamento no CI. - [ALTA]
DELETEfísico em tabelas com valor contratual. Faturas e eventos de uso são apagados no cancelamento. Para SOC 2 (e para disputas comerciais), isso é destruição de evidência. Recomendação: soft delete + política de retenção explícita. - [MÉDIA] Índice ausente no caminho mais quente.
events (tenant_id, created_at)não existe; o dashboard varre a tabela inteira por tenant. Com 3× o volume, o p95 desse dashboard degrada primeiro. - [MÉDIA]
varchar(255)como reflexo, não como decisão. 31 colunas com limite que não corresponde a nenhuma regra de negócio; duas já truncaram dados reais (URL e razão social). - [BAIXA] Timestamps sem timezone em 8 tabelas.
timestamp→timestamptz: barato agora, caro depois da primeira integração fora do fuso.
Justificativa
O schema sustenta a operação atual e está bem normalizado no domínio central — o time fez o essencial certo. As duas ressalvas ALTAS não são teóricas: uma é risco de vazamento entre clientes, a outra compromete a trilha de auditoria que a certificação vai exigir. Corrigidas as duas (estimadas em um sprint), o modelo comporta o crescimento planejado sem redesenho.
Plano de correção priorizado
- Escopo automático de tenant + teste de isolamento (bloqueia release sem ele).
- Soft delete em faturas/eventos + retenção documentada.
- Índice
events (tenant_id, created_at)— ganho imediato de p95. - Sweep de
varchararbitrário →textcomCHECKonde houver regra real.