Skip to content

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:

  1. upsert da linha alvo (rápido — 1 row);
  2. recomputeFaseForOlimpiadaN UPDATEs individuais para reordenar ranks de todos os alunos da fase;
  3. recomputeSnapshotsIfPublished — releitura do universo e regravação de mural_dados_publicados;
  4. registrarLog — INSERT em logs.

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:

  1. Síncrono no Response: validações + persistência da linha alvo.
  2. EdgeRuntime.waitUntil envolve todo trabalho derivado: recompute global, snapshot, log.
  3. Recompute em si usa diff em memória: compara estado calculado com estado lido e escreve APENAS linhas que mudaram (changed.length tipicamente 1 em mutações singulares).
  4. 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 devolve taskId; 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.ts para 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 recomputeFaseForOlimpiada lança, erro vai para function_edge_logs mas 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_manual no 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 === 0 antes 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.tshandleSetSituacaoAluno com EdgeRuntime.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.