Pular para o conteúdo principal

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)AntesDepois
Por Ranking (ano inteiro + tipo_pessoa)16,7s90ms
Por Modelo (mês a mês + tipo_pessoa)22,6–31,4s330ms
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.