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:
| Tabela | Filtro 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 Scanem vez deIndex Scan/Index Only Scan.Rows Removed by Filter> 10× das linhas retornadas.Buffers: shared read(cache miss) repetido.Sortaparecendo quando aORDER BYdeveria ser servida pelo índice.
Gaps suspeitos (a validar)
resultados_alunoprovavelmente tem só PK + FKinscricao_id. Falta composto(olimpiada_id, fase_id, nivel_id)parahandleStatsAgregados.mensagens_enviadas— sem índice em(escola_id, created_at desc), a timeline do coord vai cair em seq scan crescente.logs_transacoes—admin-logsfiltra pormodulo+ 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) eSECURITY 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.
Links
- 02 — Pagination fetchAllRows
- 06 — Inventário tabelas escaláveis
mem://architecture/large-dataset-handling-standard