minions-ai-agents/src/knowledge/standards/database_standards.md

4.4 KiB

🗄️ Padrões de Banco de Dados (O Protocolo "Integridade Relacional")

Público: Agentes de Backend & Arquitetos. Objetivo: Construir schemas escaláveis e compatíveis que preferem PostgreSQL, mas aceitam limitações do MySQL.

[!CRITICAL] O Mandato dos Dados: "Código é temporário. Dados são permanentes. Schemas quebrados são uma sentença perpétua."

1. 🏗️ Arquitetura & Stack

A Camada de Abstração

  • ORM Obrigatório: Use SQLAlchemy (Async) ou Prisma (se Node).
  • Justificativa: Precisamos alternar entre Postgres e MySQL sem reescrever queries. SQL puro é proibido, a menos que seja para relatórios otimizados específicos.
  • Migrações: Alembic (Python) ou Prisma Migrate.
    • Regra: Nunca modifique o DB manualmente. Code-first sempre.

O Duelo: PostgreSQL vs MySQL

Preferimos PostgreSQL.

  • Por que: JSONB, Melhor Indexação, Confiabilidade.
  • Suporte MySQL: Devemos suportá-lo, então evite lógica que dependa exclusivamente de extensões obscuras do Postgres, a menos que esteja por trás de uma feature flag.

2. 🏛️ Regras de Design de Schema

Convenções de Nomenclatura (Snake_Case)

  • Tabelas: Plural, snake_case (users, order_items, audit_logs).
  • Colunas: Singular, snake_case (created_at, user_id, is_active).
  • Chaves:
    • Primária: id (UUIDv7 ou BigInt otimizado).
    • Estrangeira: target_id (ex: user_id referenciando users.id).

Disciplinas de Tipo

  • Timestamps: SEMPRE use UTC.
    • Coluna: created_at (TIMESTAMP WITH TIME ZONE).
    • Coluna: updated_at (Trigger de auto-update).
  • JSON: Use JSONB (Postgres) / JSON (MySQL).
    • Restrição: Não trate o DB como um document store. Use JSON apenas para metadados variáveis, não para relações principais.
  • Booleans: Use BOOLEAN. (MySQL define como TinyInt(1) automaticamente, o ORM lida com isso).

3. 🛡️ Performance & Confiabilidade

Estratégia de Indexação

  • Chaves Estrangeiras: DEVEM ser indexadas.
  • Busca: Se buscar texto, use Trigram (Postgres) ou FullText (MySQL).
  • Unicidade: Force no nível do DB (unique=True), não apenas no nível do código.

O Pecado "N+1"

  • Eager Loading: Agentes devem fazer join explícito de tabelas (select_related / joinedload).
  • Paginação: NUNCA retorne SELECT * sem LIMIT/OFFSET (Paginação por cursor preferida para grandes conjuntos).

4. 🔒 Checklist de Compatibilidade (Postgres vs MySQL)

Antes de commitar uma migração, verifique:

  1. Quoting: Postgres usa aspas duplas "tabela", MySQL usa crases `tabela`. Resultado: Use o ORM para lidar com isso.
  2. Case Sensitivity: MySQL no Windows é case-insensitive. Postgres é case-sensitive. Resultado: Mantenha snake_case minúsculo explicitamente.
  3. Enums: ENUMs nativos são complicados em migrações. Resultado: Use colunas VARCHAR com validação de Enum no nível da Aplicação OU tabelas de lookup.

5. 🤖 A Auditoria "Auto-Query" do Agente

"Antes de eu executar esta query/migração..."

  • Eu usei um arquivo de migração?
  • O padrão de created_at está definido como now()?
  • Estou buscando 10.000 linhas? (Adicione LIMIT).
  • Se eu deletar um Pai, o que acontece com o Filho? (Defina ON DELETE CASCADE ou SET NULL).

6. ⏱️ Autodiagnóstico de Performance (A Checagem "Slow Query")

Agentes devem rodar estas checagens mentais ou reais em qualquer query complexa:

Teste A: O Ritual "Explain"

Antes de finalizar uma query, simule EXPLAIN (Postgres) ou EXPLAIN ANALYZE.

  • Condição de Falha: O resultado mostra Seq Scan em uma tabela com > 1000 linhas?
  • Correção: Adicione um index na coluna filtrada (WHERE coluna = ...).

Teste B: A Armadilha "Sem Limites"

  • Condição de Falha: Uma query sem lógica de LIMIT ou PAGE_SIZE.
  • Correção: Injete forçadamente LIMIT 100 durante dev/teste para verificar.

Teste C: O Detector "N+1"

  • Condição de Falha: Usar um loop para buscar dados relacionados.
    users = session.query(User).all()
    for user in users:
        print(user.address) # 🚨 RUIM: Uma query por usuário
    
  • Correção: Use Eager Loading.
    users = session.query(User).options(joinedload(User.address)).all() # ✅ BOM: Único JOIN