Pedido ao time de dados — índices/mart de Emplacamento
Segue o mesmo formato de chassi-detalhamento-view.md
(precedente de pedido formal ao time de dados neste repo). Contexto completo em
perf-diagnostico-prod-emplacamento-kpi-dia.md.
dw_emplacamento_mart/dw_emplacamento não têm migration neste repo — schema populado por
pipeline externo (Pentaho). Os itens abaixo não foram implementados pela app; são pedidos
pro time de dados avaliar.
1. Índice não utilizado — candidato a remoção
ix_emplacamento_kpi_dia_fabricante (dw_emplacamento_mart.emplacamento_kpi_dia, coluna
fabricante_codigo) nunca foi usado, confirmado via get_advisors do Supabase. Não bate
com nenhum padrão de query real (todas as queries que filtram por fabricante já usam o índice
composto emplacamento_kpi_dia_data_fabricante_codigo_grupomodeloveic_key, que cobre data
antes de fabricante_codigo). Sugestão: avaliar remoção — reduz custo de manutenção em toda
carga do ETL, sem perda de performance de leitura.
2. emplacamento_kpi_dia — reavaliar depois da Frente 1 (materialização em app)
A app já resolveu as duas queries mais lentas (SELECT DISTINCT modalidade, array_agg de
área de abrangência) materializando os valores numa tabela própria
(public.emplacamento_filter_options, repovoada 1x/dia via pg_cron) — não precisam mais de
índice nessas colunas.
O que sobra: queries de dashboard-kpi-service.ts (queryFabricanteAgg, queryTotals,
queryDailyPulse, resolvePeriodRange) filtram por data BETWEEN ... / ano/mes — já
existem emplacamento_kpi_dia_data_idx (em data) e ix_emplacamento_kpi_dia_ano_mes. Não
temos evidência ainda de que essas queries estão lentas em prod (não apareceram nos logs
capturados até agora). Pedido: só revisitar se, depois da Frente 1 estar em produção, uma
nova checagem de get_logs/get_advisors mostrar essas queries acima de alguns segundos —
nesse caso, levantar o EXPLAIN ANALYZE real antes de propor índice novo (evitar duplicar
cobertura que já existe).
3. Por-território — não é problema de índice, é volume
Queries de por-território (fato_emplacamentos filtrado por local_sk = ANY(array),
GROUP BY local_sk, mes/ano) levam 10-12s em prod. O EXPLAIN mostra que já usam o índice
de local_sk das partições (fato_emplacamentos_2025_local_sk_idx,
fato_emplacamentos_2026_local_sk_idx) via Bitmap/Parallel Scan — não é falta de índice, é o
volume de linhas tocadas (centenas de milhares por partição, por request).
Pedido maior, não urgente: avaliar um mart agregado por local_sk (mesmo conceito de
emplacamento_kpi_dia, mas na granularidade de local em vez de diário por veículo), pra
por-território deixar de escanear fato_emplacamentos cru a cada request. É mudança de
modelagem, não um índice — não bloqueia o restante do trabalho de performance, fica registrado
aqui pra quando o time de dados tiver capacidade.
4. ix_emplacamento_kpi_dia_ano_mes_tipo_pessoa — aplicado direto em prod (2026-08-02)
Diferente dos itens acima (pedidos ao time de dados, não implementados pela app): este índice
foi criado direto pela app, via CREATE INDEX CONCURRENTLY (sem lock de escrita), com
aprovação explícita do usuário — achado durante teste de performance do novo filtro "Tipo de
Pessoa" (Por Modelo/Por Ranking/Por Marca).
Problema: com m.tipo_pessoa = ANY(...) no WHERE, o planner do Postgres passava a
escolher o índice único composto de 12 colunas (emplacamento_kpi_dia_data_fabricante_codigo_ grupomodeloveic_key) em vez do já existente ix_emplacamento_kpi_dia_ano_mes — como
tipo_pessoa é a última coluna desse índice composto, virava quase uma varredura completa.
ANALYZE na tabela (estatísticas nunca tinham rodado manual, só autoanalyze) não resolveu.
Query (EXPLAIN ANALYZE real, dados de prod) | Antes | Depois |
|---|---|---|
| Por Ranking (ano inteiro + tipo_pessoa) | 16,7s | 90ms |
| Por Modelo (mês a mês + tipo_pessoa) | 22,6–31,4s | 330ms |
CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_emplacamento_kpi_dia_ano_mes_tipo_pessoa
ON dw_emplacamento_mart.emplacamento_kpi_dia (ano, mes, tipo_pessoa);
Índice cobre exatamente o padrão WHERE ano = ... [AND mes ...] AND tipo_pessoa = ANY(...)
usado por Por Modelo e Por Ranking. Como o mart é repovoado por pipeline externo (Pentaho), o
time de dados precisa saber que esse índice existe agora — se algum processo de carga recriar
a tabela do zero (em vez de fazer INSERT/UPDATE incremental), o índice precisa ser
recriado junto (mesmo comando acima, idempotente por causa do IF NOT EXISTS).
Não é bloqueio pra nada em andamento
Os itens 2 e 3 são follow-up, não bloqueiam a Frente 1 (já em produção/pronta pra deploy) nem o uso normal dos relatórios. O item 1 é uma limpeza de baixo risco que pode ser feita a qualquer momento.