Skip to content

09 — Índices DB e baseline de EXPLAIN para tabelas escaláveis

Pergunta: os queries hot estão batendo índice ou seq scan? Onde falta índice composto que justifica o custo elevado mesmo após paginação servidor?

Tabelas no perímetro

Cruzar com 06 — Inventário. Foco aqui são as 5 com maior risco de seq scan custoso:

TabelaFiltro hotÍndice ideal
inscricoes_olimpiada(olimpiada_id, fase_id, nivel_id, escola_id)composto multi-coluna, terminando em escola_id
resultados_aluno(inscricao_id) + (olimpiada_id, fase_id)duas variações
alunos(escola_id, ativo) + (escola_id, turma_id)composto
mensagens_enviadas(escola_id, created_at desc)para timeline
logs_transacoes(escola_id, created_at desc) + (modulo, created_at)para admin-logs e filtros

Método de constatação (a executar)

sql
-- 1. Listar índices existentes da tabela
select indexname, indexdef
from pg_indexes
where tablename = 'inscricoes_olimpiada'
order by indexname;

-- 2. EXPLAIN ANALYZE da query hot
explain (analyze, buffers, format text)
select id, aluno_id, fase_id, nivel_id, status
from public.inscricoes_olimpiada
where olimpiada_id = '<uuid>'
  and fase_id = '<uuid>'
  and escola_id = '<uuid>'
order by created_at desc
limit 50 offset 0;

Sinais de alerta:

  • Seq Scan em vez de Index Scan/Index Only Scan.
  • Rows Removed by Filter > 10× das linhas retornadas.
  • Buffers: shared read (cache miss) repetido.
  • Sort aparecendo quando a ORDER BY deveria ser servida pelo índice.

Gaps suspeitos (a validar)

  • resultados_aluno provavelmente tem só PK + FK inscricao_id. Falta composto (olimpiada_id, fase_id, nivel_id) para handleStatsAgregados.
  • mensagens_enviadas — sem índice em (escola_id, created_at desc), a timeline do coord vai cair em seq scan crescente.
  • logs_transacoesadmin-logs filtra por modulo + intervalo de data; sem índice composto vira full scan.

RLS overhead

Toda policy com função (public.has_role(...), pertence_escola(...)) é avaliada por linha. Em tabelas de 50k+:

  • Confirmar se as funções estão STABLE (caching por statement) e SECURITY DEFINER.
  • Verificar que o filtro de coluna indexada vem ANTES (no WHERE explícito), para reduzir o set antes da policy ser avaliada.

Próximas etapas (auditoria, não fix)

  • Rodar as 5 EXPLAINs do quadro acima em prod via supabase--read_query.
  • Documentar índices faltantes com migration de criação CONCURRENTLY (não bloqueante).
  • Cruzar com SSOT do arquivo 99.