DEV Community

Cover image for De 6 horas para 40 segundos: como um índice de banco de dados salvou um job crítico de produção
Fernando Andrade for He4rt Developers

Posted on

De 6 horas para 40 segundos: como um índice de banco de dados salvou um job crítico de produção

"Às vezes, a solução mais elegante não está no código — está em ensinar o banco de dados a encontrar o que ele já tem."

Índice


O cenário: um job noturno que virou um problema diurno

Todo sistema que lida com monitoramento contínuo eventualmente enfrenta o mesmo desafio: quanto mais dados acumulam, mais lenta fica a análise. Foi exatamente isso que aconteceu em um projeto no qual trabalhei.

A arquitetura era simples na teoria: um job agendado rodava durante a madrugada, disparando N processos paralelos — cada um cadastrado individualmente pelo cliente. A lógica de cada processo era fazer uma comparação entre o resultado do dia atual (D+0) com o resultado do dia anterior (D-1), algo como:

"O que mudou desde ontem?"

Para isso, cada processo precisava buscar seu último resultado registrado, usando uma query com ORDER BY last_execution DESC filtrada pelo identificador do processo. Parece trivial. E durante um bom tempo, foi.


O problema cresceu junto com a tabela

Com o tempo, a tabela de resultados foi crescendo naturalmente — afinal, cada processo registra um novo resultado a cada execução. O que antes demorava milissegundos começou a demorar segundos. Depois, dezenas de segundos. Até que um dia percebemos:

A janela de execução do job, que deveria ser de até 4 horas, estava chegando a 12 horas.

Isso significava que um job que deveria terminar antes do horário comercial estava ainda em execução quando os usuários começavam a trabalhar de manhã — gerando inconsistências, bloqueios e reclamações.

A pergunta era: onde estava o gargalo?


O diagnóstico: o Azure Application Insights apontou o caminho

Ao analisar as métricas de performance no Azure Application Insights, ficou evidente que o problema estava concentrado em uma única operação: a query que buscava o último resultado de cada processo.

Internamente, a tabela de resultados tinha crescido o suficiente para que um ORDER BY last_execution DESC sem suporte de índice forçasse o banco a fazer um full scan — ou seja, varrer linha por linha até encontrar o registro mais recente. Multiplique isso por dezenas (ou centenas) de processos rodando em paralelo e você tem uma receita para o caos.

Antes da correção

1 linha(s) recuperada(s) — 1.754s, em 2025-08-11 às 09:19:01
Enter fullscreen mode Exit fullscreen mode

Quase 2 segundos por consulta. Para um único processo, tolerável. Para N processos simultâneos, catastrófico.


A solução: um índice bem posicionado

A correção foi aplicar um índice composto na tabela de resultados, cobrindo exatamente os campos usados na query crítica:

CREATE INDEX idx_job_result_process_date
  ON app_schema.job_results (fk_process_id, date_created DESC)
  INCLUDE (id, final_result, report_id, result_payload);
Enter fullscreen mode Exit fullscreen mode

Esse índice foi criado diretamente no ambiente de produção (pode ser gerado localmente também, dependendo da política da equipe) e o resultado foi imediato.

Depois da correção

1 linha(s) recuperada(s) — 0.003s, em 2025-08-11 às 09:32:00
Enter fullscreen mode Exit fullscreen mode

De 1,754 segundos para 0,003 segundos por consulta. Uma redução de 99,8% no tempo de resposta.


O impacto real: economia de tempo total por dia

Cenário Tempo acumulado de processamento/dia
❌ Antes do índice ~6 horas e 18 minutos
✅ Depois do índice ~40 segundos

O job voltou a terminar bem antes do horário comercial. Os processos diurnos pararam de ser impactados. E tudo isso sem reescrever uma linha de código de negócio.


Mas afinal: o que é um índice de banco de dados?

Se você chegou até aqui e nunca parou para entender o que um índice faz de verdade, esse é o momento.

A analogia do livro

Imagine que você tem um livro enciclopédico com 10.000 páginas e precisa encontrar tudo que fala sobre "fotossíntese". Você tem duas opções:

  1. Sem índice: Ler página por página do início ao fim. Funciona, mas demora.
  2. Com índice: Ir até o índice remissivo no final do livro, localizar "fotossíntese" em segundos e ir direto às páginas certas.

Um índice de banco de dados funciona exatamente assim. Ele é uma estrutura de dados separada (geralmente uma B-Tree) que mantém uma cópia ordenada de uma ou mais colunas, com ponteiros para as linhas reais da tabela.

O que um índice resolve?

  • Buscas por igualdade: WHERE id = 42 → o índice encontra o valor direto, sem varrer a tabela
  • Buscas por intervalo: WHERE date_created BETWEEN '2025-01-01' AND '2025-12-31'
  • Ordenação: ORDER BY last_execution DESC → se o índice já estiver ordenado nessa direção, o banco nem precisa ordenar
  • Queries cobertas (covering index): Com a cláusula INCLUDE, o banco pode responder à query inteira só pelo índice, sem nem tocar na tabela original

O que um índice não é (e quando ele atrapalha)

Índice não é gratuito. Ele tem custos:

  • Espaço em disco: o índice ocupa armazenamento adicional
  • Custo de escrita: toda vez que um INSERT, UPDATE ou DELETE acontece, os índices afetados também precisam ser atualizados
  • Manutenção: índices fragmentados precisam ser reorganizados periodicamente

Por isso, criar índices sem critério pode ser tão prejudicial quanto não tê-los. A regra de ouro é:

Crie índices nas colunas que aparecem frequentemente em cláusulas WHERE, JOIN, ORDER BY e GROUP BY de queries lentas — especialmente em tabelas grandes.


Como identificar quando você precisa de um índice

Alguns sinais de alerta:

  • Queries que demoram mais conforme a tabela cresce (como o nosso caso)
  • Full table scans aparecendo nos planos de execução (EXPLAIN / Query Execution Plan)
  • Timeouts em operações que antes eram rápidas
  • CPU do banco de dados consistentemente alta durante períodos de consulta

Ferramentas como o Azure Application Insights, pg_stat_statements (PostgreSQL), slow query log (MySQL) e o Query Store (SQL Server) são aliadas valiosas nesse diagnóstico.


Anatomia do índice que resolveu o problema

Voltando ao índice criado:

CREATE INDEX idx_job_result_process_date
  ON app_schema.job_results (fk_process_id, date_created DESC)
  INCLUDE (id, final_result, report_id, result_payload);
Enter fullscreen mode Exit fullscreen mode

Por que esse design?

Componente Motivo
fk_process_id Filtro principal da query (cada processo tem seu identificador)
date_created DESC A query precisa do resultado mais recente primeiro
INCLUDE (...) Colunas retornadas pela query — incluí-las evita um segundo acesso à tabela

O resultado é um covering index: o banco responde à query inteira consultando apenas o índice, sem precisar buscar dados na tabela principal. É a forma mais eficiente de otimização de leitura possível.


Conclusão

Performance não é só sobre algoritmos ou arquitetura de microsserviços. Às vezes, o gargalo está numa operação aparentemente simples que o banco de dados precisa executar milhares de vezes por dia — e que ninguém percebe até que o custo acumulado se torne um problema real.

Nesse caso, um único índice bem pensado transformou 6 horas de processamento em 40 segundos. Sem refatoração. Sem mudança de arquitetura. Sem downtime.

Se você ainda não tem o hábito de revisar os planos de execução das suas queries críticas, comece agora. O banco de dados tem muito a te contar — basta saber ouvir.


Sugestões de leitura para se aprofundar: B-Tree indexes, covering indexes, query execution plans, índices compostos e ferramentas de profiling como EXPLAIN ANALYZE.

Top comments (0)