Você passou horas tunando uma query, criou o índice perfeito e, nos testes, ela rodou em menos de 2 milissegundos. Mas algumas semanas depois, com o sistema em produção, aquela mesma consulta começa a demorar segundos, arrastando a performance da aplicação inteira. Você checa o índice, e ele continua lá. O que mudou?
O que aconteceu é que o Planner (o planejador de consultas do Postgres) ficou cego.
Nos posts anteriores da nossa série Guia de Sobrevivência: Backup e Manutenção no Postgres, focamos muito na parte física: arquivos, espaço em disco e limpeza de linhas mortas. Hoje, vamos entrar na mente do Postgres. Vamos entender como ele decide usar ou ignorar um índice baseado em estatísticas e como o comando vacuumdb -z (ou simplesmente ANALYZE) é o colírio que o seu banco precisa.
Como o Postgres decide ler os dados?
Sempre que você envia uma query para o Postgres, ela passa pelo Planner. O papel dele é olhar para o seu comando e decidir o caminho mais rápido e barato para buscar as linhas no disco. Ele basicamente escolhe entre duas estratégias principais:
- Sequential Scan (Escanear a tabela toda): O Postgres lê o arquivo da tabela do início ao fim, linha por linha. É ótimo para tabelas pequenas, mas um desastre para tabelas gigantes.
- Index Scan (Busca por Índice): O Postgres vai direto ao ponto usando a estrutura do índice (como o sumário de um livro) e busca apenas as páginas exatas que contêm o dado.
O mistério do custo
O Planner toma essa decisão baseando-se em estatísticas matemáticas acumuladas sobre o conteúdo das tabelas (quantas linhas ela tem, qual a distribuição dos valores, quais dados são mais frequentes).
Se você faz uma carga massiva de dados (insere 10 milhões de linhas de uma vez) ou deleta metade do banco, a tabela real muda drasticamente, mas a tabela de estatísticas internas continua desatualizada. Para o Planner, a tabela ainda parece pequena ou vazia. É aí que ele toma a decisão errada e escolhe um Sequential Scan onde deveria usar um Índice.
O Gancho: O Antes e Depois com EXPLAIN ANALYZE
Vamos ver o perigo da “cegueira” do Planner em um cenário real através do comando EXPLAIN ANALYZE, que nos mostra o plano de execução real do banco.
O Cenário:
Imagine uma tabela chamada clientes que tinha apenas 1.000 registros. De madrugada, um script de migração inseriu 5 milhões de novos clientes. O desenvolvedor tenta buscar um cliente específico pelo código:
SQL
EXPLAIN ANALYZE SELECT * FROM clientes WHERE codigo_interno = 'ABC-9988';
O “Antes” (Estatísticas Cegas):
Plaintext
Seq Scan on clientes (cost=0.00..98450.00 rows=1 width=244) (actual time=845.120..1202.340 rows=1 loops=1)
Filter: (codigo_interno = 'ABC-9988'::text)
Rows Removed by Filter: 4999999
Planning Time: 0.115 ms
Execution Time: 1202.510 ms
O Desastre: O Postgres ignorou o índice existente na coluna codigo_interno. Como as estatísticas antigas diziam que a tabela era minúscula, ele achou que seria mais rápido ler tudo sequencialmente. O resultado? 1.2 segundos para achar um único registro.
O Salvador: Atualizando com o ANALYZE
Agora, nós rodamos a ferramenta de linha de comando para recalcular as estatísticas do banco:
Bash
vacuumdb -U postgres -h localhost -d meu_banco -z -v
(Nota: A flag -z ou --analyze-only diz ao utilitário para focar puramente em recalcular as estatísticas, sem gastar tempo limpando linhas mortas).
O “Depois” (Estatísticas Atualizadas):
Rodamos exatamente a mesma query novamente:
Plaintext
Index Scan using idx_clientes_codigo on clientes (cost=0.43..8.45 rows=1 width=244) (actual time=0.042..0.045 rows=1 loops=1)
Index Cond: (codigo_interno = 'ABC-9988'::text)
Planning Time: 0.180 ms
Execution Time: 0.065 ms
A Vitória: Agora que o Planner sabe o tamanho real da tabela (5 milhões de linhas), ele percebeu instantaneamente que o Sequential Scan seria uma loucura. Ele optou pelo Index Scan usando o índice idx_clientes_codigo. A query caiu de 1202ms para 0.06ms.
O papel do utilitário vacuumdb -z
Assim como o processo de faxina do espaço em disco, o Postgres possui o Autovacuum que também roda o analyze automaticamente. No entanto, em janelas pós-migração, cargas de dados via pipelines ou atualizações de sistemas (onde estruturas mudam em lote), você não deve esperar o banco decidir atualizar. Você deve forçar a atualização.
O comando vacuumdb facilita isso via CLI:
1. Atualizar apenas estatísticas (Ultra veloz)
Diferente do VACUUM que lê a tabela toda linha por linha, o ANALYZE tira uma amostragem estatística, sendo um comando que roda em poucos segundos, mesmo em bancos grandes.
Bash
vacuumdb -U postgres -h localhost -d meu_banco --analyze-only -v
2. O Combo de Produção (Faxina + Inteligência)
Se você quer limpar o banco e atualizar as estatísticas na mesma rotina da madrugada de forma paralela (ótimo para scripts automáticos), junte as flags:
Bash
vacuumdb -U postgres -h localhost -d meu_banco -j 4 -z -v
-j 4: Roda em 4 workers paralelos.-z: Executa oANALYZElogo após o término doVACUUMde cada tabela.
Conclusão
Uma query rápida depende diretamente de um Planner bem informado. Se você faz grandes movimentações de dados e não avisa o Postgres rodando um ANALYZE, o banco tomará decisões ruins usando dados desatualizados.
Sempre que terminar uma carga massiva de dados, lembre-se: o seu último passo no script deve ser disparar um vacuumdb -z.
Com este post, encerramos a análise de performance e saúde interna. Agora, vamos entrar no último bloco da nossa série: Automação e Cenários Reais. No próximo post, vamos colocar o chapéu de DevOps e construir um script robusto (em Shell ou Python) para automatizar todo o processo de backup e segurança que aprendemos até aqui. Até lá!
Sua query já ficou lenta do nada e voltou a voar depois de um tempo misteriosamente? Provavelmente foi o analyze rodando em background! Deixe suas dúvidas ou comentários aqui embaixo.