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ção | Alvo P95 | Limite 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/a | dimensionado 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)
| Caso | Estratégia | Helper canônico |
|---|---|---|
| Leitura UI paginada | range(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 lote | fetchAllRows permitido + fila persistente em *_sessoes + EdgeRuntime.waitUntil | padrã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
fetchAllRowscontinua permitido em:- Jobs background (
*-import.ts,*-compute.tsquando dentro dewaitUntil) - Crons
- Snapshots
- Jobs background (
- Proibido em:
- Qualquer handler que devolve dados para
useQueryda UI - Qualquer
stats/dashboard/batch_init
- Qualquer handler que devolve dados para
Guarda automática (a criar): scripts/audit/no-fetchallrows-in-ui-handlers.ts.
4. React Query — checklist por hook escalável
- [ ]
queryKeyincluiescola_id(multi-tenant safety) - [ ]
staleTime≥ 30s para dado operacional, 5min para metadata - [ ]
keepPreviousData: trueem qualquer query paginada - [ ] Sem
useState/useEffectpara 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:
- Tabela
*_sessoescomid, dados, progresso, status, mensagem, criado_em. - Handler valida → cria row → devolve
{ taskId }em <300ms. - Processamento em
EdgeRuntime.waitUntil. - Batches de 50 (
BATCH_SIZE). - Update de
progressopor batch. - Hook genérico
useBackgroundTask({ taskId })no front com polling 3s + retomada via localStorage. - Idempotência por chave canônica (UUID, ou natural única validada).
- 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', ...): apenaspapeis,series_escolares(enum-like, controladas por seed).
8. Critérios de PR para padrão novo
PR que toca handler escalável DEVE atender:
- Cenário sintético 8k inscrições rodando localmente (mock factory).
- Sem
fetchAllRowsem handler de leitura UI. - Stats agregados via SQL (não
count()em JS sobre array drenado). - Write em lote >50 com
task_id+waitUntil. - Hook React Query com checklist §4.
- 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-standardcom o resumo. - Reescrever
docs/development/LARGE_DATASET_HANDLING.mdapontando para este como SSOT e mantendo só o "como" dofetchAllRowsem jobs. - Abrir planos de implementação Onda A / B / C.