Skip to content

99 — Proposta de Regra de Qualidade de Performance (SSOT)

Ref: 00_INDEX · Proposta — não implementar até aprovação

1. Orçamento de tempo (definição de "rápido")

OperaçãoAlvo P95Limite duro
Mount de tela de feature do coord≤ 1.5s TTFB≤ 3s
Troca de página em lista paginada≤ 300ms≤ 700ms
Mutação UI simples (toggle, save 1 form)≤ 500ms≤ 1.5s
Response de write em lote (devolução de task_id)≤ 300ms≤ 1s
Processamento em background (até finalizar)n/adimensionado por feature, com progresso

Critério de aceite para PR de feature escalável: cenário sintético de 8k inscrições / 5k alunos dentro do orçamento.

2. Três caminhos para "muitas linhas" (separar antes de codar)

CasoEstratégiaHelper canônico
Leitura UI paginadarange(from, to) no servidor + count='exact' para total. Filtro/sort no SQL, NUNCA em JS._shared/paginated-query.ts (a criar)
Agregação (contadores, stats, dashboard cards)SQL: SELECT COUNT(*) FILTER (...) FROM ... GROUP BY .... Devolver linhas O(1).RPC dedicada por feature
Job de write em lotefetchAllRows permitido + fila persistente em *_sessoes + EdgeRuntime.waitUntilpadrão Alunos (ver 04)

→ Renomear LARGE_DATASET_HANDLING.md para deixar essa separação explícita.

3. Banimento de fetchAllRows em rota de leitura UI

  • fetchAllRows continua permitido em:
    • Jobs background (*-import.ts, *-compute.ts quando dentro de waitUntil)
    • Crons
    • Snapshots
  • Proibido em:
    • Qualquer handler que devolve dados para useQuery da UI
    • Qualquer stats/dashboard/batch_init

Guarda automática (a criar): scripts/audit/no-fetchallrows-in-ui-handlers.ts.

4. React Query — checklist por hook escalável

  • [ ] queryKey inclui escola_id (multi-tenant safety)
  • [ ] staleTime ≥ 30s para dado operacional, 5min para metadata
  • [ ] keepPreviousData: true em qualquer query paginada
  • [ ] Sem useState/useEffect para fetch (já é regra core)
  • [ ] Debounce ≥300ms em busca antes de invalidar
  • [ ] Skeleton apenas no primeiro mount (Anti-Flash — mem://architecture/performance/anti-flash-rendering-standard); troca de página NÃO mostra skeleton

5. Writes em lote — padrão obrigatório acima de 50 itens

Replicar A1–A7 do 04:

  1. Tabela *_sessoes com id, dados, progresso, status, mensagem, criado_em.
  2. Handler valida → cria row → devolve { taskId } em <300ms.
  3. Processamento em EdgeRuntime.waitUntil.
  4. Batches de 50 (BATCH_SIZE).
  5. Update de progresso por batch.
  6. Hook genérico useBackgroundTask({ taskId }) no front com polling 3s + retomada via localStorage.
  7. Idempotência por chave canônica (UUID, ou natural única validada).
  8. Cron de limpeza para sessões zumbis (>1h sem progresso).

6. Hook genérico proposto — useBackgroundTask

ts
// src/hooks/useBackgroundTask.ts (a criar)
useBackgroundTask({
  storageKey: 'recompute-task',
  fetchStatus: (taskId) => invokeAction('gestao-resultados', 'get_task_status', { taskId }),
  pollIntervalMs: 3000,
  onFinish: () => queryClient.invalidateQueries(...),
})

Replicar a forma exata do useImportacaoSessao mas genérica. Cada feature passa apenas fetchStatus e onFinish.

7. Lookup por UUID (de 07)

  • Entidades de domínio → APENAS UUID.
  • Chaves externas (matrícula, CPF, INEP) → traduzir para UUID na entrada.
  • Multi-escola → todo lookup por chave externa inclui escola_id.
  • Whitelist para .eq('nome', ...): apenas papeis, series_escolares (enum-like, controladas por seed).

8. Critérios de PR para padrão novo

PR que toca handler escalável DEVE atender:

  1. Cenário sintético 8k inscrições rodando localmente (mock factory).
  2. Sem fetchAllRows em handler de leitura UI.
  3. Stats agregados via SQL (não count() em JS sobre array drenado).
  4. Write em lote >50 com task_id + waitUntil.
  5. Hook React Query com checklist §4.
  6. Doc atualizada (docs/features//SSOT) na mesma entrega.

9. Próximos passos (não desta auditoria)

  • Aprovar este SSOT.
  • Após aprovação: criar mem://architecture/performance/scalable-feature-standard com o resumo.
  • Reescrever docs/development/LARGE_DATASET_HANDLING.md apontando para este como SSOT e mantendo só o "como" do fetchAllRows em jobs.
  • Abrir planos de implementação Onda A / B / C.