⌘K

08 – O Planejador de Consultas cego: Como o ANALYZE salva suas queries da lentidão

Last updated

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 o ANALYZE logo após o término do VACUUM de 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.

Still stuck? How can we help? Get Help