ADR-001 — Um banco por organização

Data: 2026-08-09 (virada multi-tenant) · Situação: vigente

Contexto

O AppStock nasceu como sistema de uma empresa só, acoplado a um CRM e a um ERP específicos. Ao virar SaaS, era preciso escolher como isolar clientes. As três opções usuais:

  1. Coluna org_id em cada tabela — barato de implementar, caro de garantir: basta um WHERE esquecido para vazar dado entre clientes.
  2. Schema por organização — isolamento razoável, um banco só, search_path por conexão.
  3. Banco por organização — isolamento físico.

Decisão

Banco por organização, em dois clusters: app_<slug> (portal) e sync_<slug> (espelho do ERP), com um appstock_core para o plano de controle.

O DSN de cada banco vive em org_databases, e db/tenants.py resolve a engine por (org_id, kind). get_db/get_sync_db mantiveram nome e assinatura — o que permitiu virar cerca de 100 arquivos de rotas e serviços sem tocá-los.

Por quê

  • Vazamento cross-org deixa de ser um bug possível e vira um bug impossível. Não existe WHERE a esquecer: a conexão já é do banco certo.
  • Isolamento de recurso. Consulta pesada de um cliente esgota o pool dele, não o dos outros — é por isso que o pool do espelho tem pool_timeout=10.
  • Backup, restore e migração de tier viram operações por cliente.
  • O espelho pode ser read-only de verdade, com credencial separada.

Consequências

Boas — o isolamento simplificou o produto: a visibilidade por sharing que existia no CRM de origem virou desnecessária na v1 (todo usuário da organização vê a organização). O recorte fino por carteira/equipe voltou como escopo nativo, não como espelho de permissão de terceiro.

Ruins, e são reais:

  • N bancos = N × 70 migrations. Toda migration precisa rodar em todo app_<org>. Hoje isso é feito no provisionamento (ALEMBIC_URL por subprocess); não existe rotina de “atualizar todas as organizações” — é o próximo problema a aparecer.
  • Provisionar ficou caro. Criar dois bancos e rodar 70 migrations dentro de uma requisição HTTP é o que trava o flx-onboarding.
  • Consulta agregada entre clientes é impossível sem um caminho explícito. Para métricas de produto (quantos clientes, quanto uso) vai precisar de coleta própria.
  • Conexões multiplicam. Cada organização ativa mantém até 2 pools de 5+10. Escala bem até uma ordem de grandeza; depois exige pooler externo.

Alternativa descartada e por quê

Schema por organização economizaria bancos, mas mantém um único cluster e um único conjunto de credenciais — o isolamento de recurso e o read-only do espelho ficariam por convenção, não por estrutura. Para um produto que guarda o estoque e o financeiro de empresas concorrentes entre si, “por convenção” não bastava.