ADR-016 — Recompute pós-mutação assíncrono
Status: Aceito · Data: 2026-06-11 · Categoria: Backend / Performance Relacionado: ADR-015 — Manual override de situação,
mem://architecture/edge-function-background-task-standard
1. Contexto
Edição de UMA célula no modal "Editar resultado do aluno" (Resultados → Coordenador) demorava ~10s para fechar e popar toast. Reproduzido em Conquista (272 inscrições); projeção para Centro de Excelência Master (Canguru, 1417) ultrapassaria 30s.
A action set_situacao_aluno executava, em série e dentro do mesmo Response:
upsertda linha alvo (rápido — 1 row);recomputeFaseForOlimpiada—NUPDATEs individuais para reordenar ranks de todos os alunos da fase;recomputeSnapshotsIfPublished— releitura do universo e regravação demural_dados_publicados;registrarLog— INSERT emlogs.
Apenas o passo (1) afeta o feedback imediato ao coordenador. Os passos (2)–(4) são trabalho derivado, idempotente, e podem atrasar alguns segundos sem prejuízo funcional.
2. Decisão
Toda mutação singular (1 linha editada) que dispara recompute global, snapshot, ou log derivado segue este padrão:
- Síncrono no Response: validações + persistência da linha alvo.
EdgeRuntime.waitUntilenvolve todo trabalho derivado: recompute global, snapshot, log.- Recompute em si usa diff em memória: compara estado calculado com estado lido e escreve APENAS linhas que mudaram (
changed.lengthtipicamente 1 em mutações singulares). - Frontend invalida React Query imediatamente (linha alvo já correta) e agenda um segundo
invalidateAll()em ~2.5s para capturar ranks recomputados em background.
Não-objetivos
- Não substitui o padrão de "background task" para escritas grandes em lote (importações > 50 linhas, que continuam usando
*_sessoes+ polling). Aquele padrão devolvetaskId; este aqui devolve o resultado final, só posterga ranks e auditoria. - Não promete consistência imediata para os OUTROS alunos. Janela aceitável: ≤ 2.5s.
3. Consequências
Positivas
- Latência percebida cai de ~10s para <500ms (medida no Network panel para 272 inscrições).
- Custo do recompute deixa de crescer linearmente com N no caminho crítico — N agora afeta só latência do background task.
- Padrão idêntico ao já usado em
gestao-resultados/index.tspara importações; sem nova primitiva.
Negativas / riscos aceitos
- Race entre edições rápidas: coordenador edita 2 alunos em <2s. Mitigação: recompute é idempotente e commutativo; o segundo waitUntil converge corretamente. Ranks podem mostrar estado intermediário por ≤2.5s.
- Falha silenciosa do background task: se
recomputeFaseForOlimpiadalança, erro vai parafunction_edge_logsmas linha alvo já foi persistida. Auditoria deve monitorar a taxa via Logflare; alerta planejado fica fora do escopo desta ADR. - Frontend re-invalida 2×: custo desprezível (queries de Resultados são consolidadas em
batch_init).
4. Aplicabilidade
Aplica-se HOJE a:
set_situacao_aluno(edição manual via modal).- Deve ser aplicado a
set_premiacoes_manualno próximo PR (mesmo arquivo, mesmo padrão).
Qualquer NOVA action que combine "mutação singular" + "recompute global derivado" nasce neste padrão. Checklist:
- [ ] Response devolve o estado final da linha alvo.
- [ ] Recompute/snapshot/log estão dentro de
EdgeRuntime.waitUntil. - [ ] Recompute global filtra
changed.length === 0antes de escrever. - [ ] Frontend agenda re-invalidate de ~2.5s.
5. Referências
supabase/functions/_shared/resultados-compute.ts— diff-based recompute.supabase/functions/_shared/resultados-config.ts—handleSetSituacaoAlunocomEdgeRuntime.waitUntil.src/hooks/useGestaoResultados.ts— re-invalidate atrasado.docs/development/PROBLEM_SOLVING.md— Caso 20.mem://architecture/performance/post-mutation-recompute-standard.mem://architecture/edge-function-background-task-standard.