Guia de Indexação PostgreSQL: Erros Comuns e Como Consertar

novembro 10, 2025 | por dbsnoop

Guia de Indexação PostgreSQL: Erros Comuns e Como Consertar

Em todo o ecossistema de otimização de performance de bancos de dados, nenhuma outra operação oferece um retorno sobre o investimento tão massivo e imediato quanto a criação de um índice. Uma única e bem colocada instrução CREATE INDEX pode, literalmente, transformar uma query que leva minutos e satura a CPU em uma operação que executa em milissegundos com um custo de recursos insignificante. É a ferramenta mais poderosa no arsenal de um engenheiro para escalar um sistema. No entanto, essa mesma potência, quando mal aplicada, se torna uma das fontes mais insidiosas de débito técnico, degradando a performance de forma silenciosa e, por vezes, catastrófica.

A indexação reativa, feita sem uma análise profunda do workload, invariavelmente leva a um de dois cenários: a falta de índices cruciais que condena as leituras à ineficiência, ou a proliferação de índices inúteis que estrangula a performance de escrita. Em um ecossistema robusto como o do PostgreSQL, com sua variedade de tipos de índices e seu complexo planejador de consultas, os erros podem ser ainda mais sutis e custosos. Este guia definitivo oferece um mergulho técnico nos erros mais comuns e prejudiciais na estratégia de indexação em PostgreSQL e apresenta a abordagem correta, orientada por dados, para consertá-los.

A Fundação: Por Que a Indexação é uma Faca de Dois Gumes

Antes de analisar os erros, é crucial entender a troca fundamental (trade-off) que um índice representa. Um índice não é uma solução mágica e gratuita. É uma estrutura de dados redundante, uma cópia otimizada de uma porção dos seus dados, projetada para acelerar a busca.

  • O Benefício (Leitura/SELECT): Sem um índice, uma busca (WHERE col = ‘valor’) força o PostgreSQL a fazer uma varredura sequencial (Seq Scan), lendo a tabela bloco por bloco do início ao fim. Com um índice B-Tree, o banco pode navegar por uma estrutura de árvore balanceada para encontrar a localização exata do dado em tempo logarítmico, uma operação drasticamente mais rápida e barata em I/O e CPU.
  • O Custo (Escrita/INSERT, UPDATE, DELETE): Este é o lado que muitos esquecem. Quando você escreve na tabela, não está apenas escrevendo nos dados da tabela (conhecida como heap no PostgreSQL). Você precisa atualizar cada índice que existe naquela tabela. Se uma tabela users tem cinco índices, um simples INSERT resulta em seis operações de escrita no disco (uma na tabela, cinco nos índices). Um UPDATE que modifica uma coluna indexada resulta em uma exclusão e uma inserção na estrutura do índice. Portanto, cada índice que você adiciona acelera certas leituras ao custo de desacelerar todas as escritas.

Entender essa troca é o primeiro passo para uma indexação inteligente. O objetivo não é criar o máximo de índices possível, mas sim criar o mínimo de índices necessários para suportar eficientemente as queries críticas do seu workload.

Erro 1: Over-indexing (Proliferação de Índices) e custo dos Writes

O over-indexing é o resultado direto de uma indexação reativa e sem governança. A cada novo problema de performance de leitura, um novo índice é adicionado, muitas vezes sem verificar se um índice existente já poderia satisfazer a query, talvez com uma pequena modificação. Com o tempo, a tabela acumula dezenas de índices. Muitos deles são redundantes (índices em (A, B) e (A)), sobrepostos ou simplesmente nunca usados.

Esse “index sprawl” tem consequências graves:

  • Degradação da Performance de Escrita: Como vimos, cada índice adiciona uma sobrecarga a INSERTs, UPDATEs e DELETEs. Em um sistema transacional de alta frequência (um e-commerce, uma plataforma de pagamentos), essa sobrecarga pode se tornar o principal gargalo do sistema, causando longas esperas e contenção de locks.
  • Aumento do Custo de Armazenamento: Índices consomem espaço em disco significativo. Em tabelas muito grandes, o tamanho total dos índices pode facilmente exceder o tamanho da própria tabela, inflando os custos de armazenamento e backup.
  • Manutenção mais Lenta: Operações de manutenção como VACUUM e REINDEX demoram mais para serem concluídas em tabelas com muitos índices.

Como Consertar: Identificando e Removendo Índices Inúteis

Felizmente, o PostgreSQL fornece as ferramentas para identificar esses índices “peso morto”. A visão pg_stat_user_indexes contém contadores que rastreiam o uso de cada índice.

SELECT schemaname, relname AS table_name, indexrelname AS index_name, idx_scan FROM pg_stat_user_indexes WHERE idx_scan = 0;

Esta query lista todos os índices que, desde a última reinicialização das estatísticas, nunca foram usados em uma varredura de índice (Index Scan ou Index Only Scan). Se um índice aparece nesta lista consistentemente ao longo de semanas, é um forte candidato para remoção.

A abordagem manual, no entanto, é reativa. Uma plataforma de observabilidade como a dbsnOOp automatiza esse processo de forma contínua. Ela analisa o workload ao longo do tempo e consegue diferenciar um índice que é verdadeiramente inútil de um que é usado apenas para queries de fechamento de mês, por exemplo. Ao fornecer uma lista clara e baseada em dados históricos dos índices não utilizados, ela permite que a equipe de engenharia realize uma limpeza segura, recuperando a performance de escrita e reduzindo os custos de manutenção.

Erro 2: A Ordem das Colunas em Índices Compostos Importa

Um índice composto (ou multicoluna) é um índice criado sobre mais de uma coluna. Ex: CREATE INDEX ON users (last_name, first_name). A eficácia deste índice é ditada quase que inteiramente pela ordem em que as colunas são definidas.

A regra fundamental é baseada em seletividade e padrões de consulta. A coluna que filtra a maior quantidade de dados (a mais seletiva, com a maior cardinalidade) deve vir primeiro.

Um Exemplo Prático: Cardinalidade

Imagine uma tabela issues com as colunas project_id (alta cardinalidade, ex: 10.000 projetos distintos) e status (baixa cardinalidade, ex: ‘open’, ‘closed’, ‘in_progress’). Considere a query:

SELECT * FROM issues WHERE project_id = 42 AND status = 'open';
  • Índice Incorreto: CREATE INDEX idx_wrong ON issues(status, project_id);
    • Como o PostgreSQL o usa: Ele primeiro encontra todas as issues com status = ‘open’, que podem ser milhões. Dentro desse conjunto massivo, ele procura por project_id = 42. A maior parte do trabalho é feita na segunda etapa.
  • Índice Correto: CREATE INDEX idx_correct ON issues(project_id, status);
    • Como o PostgreSQL o usa: Ele primeiro encontra o conjunto pequeno e bem definido de issues para project_id = 42. Dentro deste resultado já filtrado, ele rapidamente encontra as que estão com status = ‘open’.

A diferença de performance entre os dois pode ser de ordens de magnitude. Além disso, o índice correto pode ser usado para queries que filtram apenas por project_id, mas o índice incorreto não pode ser usado de forma eficiente para queries que filtram apenas por project_id.

Como Consertar: Indexando Queries, Não Tabelas

A decisão sobre a ordem das colunas não deve ser uma suposição. Ela deve ser uma resposta direta às queries que seu sistema executa. É por isso que o princípio central da indexação é: “Você não indexa tabelas, você indexa queries”.

dbsnOOp resolve este problema em sua origem. Ao analisar as queries mais custosas do seu workload, suas recomendações de índices são geradas com base nas cláusulas WHERE e JOIN reais. Se a query sempre filtra por project_id e, opcionalmente, por status, a recomendação será para um índice em (project_id, status), garantindo a ordem ótima e eliminando o erro humano.

Erro 3: Ignorar Tipos de Índice Além do B-Tree Padrão

O PostgreSQL brilha por sua extensibilidade, e isso é especialmente verdade em seus tipos de índice. Usar apenas o B-Tree padrão é como ter uma caixa de ferramentas completa e usar apenas o martelo para tudo. Usar o tipo de índice correto para o workload pode resultar em ganhos de performance massivos e habilitar funcionalidades que seriam impossíveis de outra forma.

  • B-Tree: O padrão. Ideal para dados escalares com ordenação natural. Perfeito para _id, email, timestamp, etc. Suporta operadores =, >, <, BETWEEN, LIKE ‘prefixo%’, IN.
  • GIN (Generalized Inverted Index): A escolha para dados compostos. Em vez de indexar o item inteiro, ele indexa os elementos dentro do item. É a solução para:
    • JSONB: Para responder rapidamente a WHERE data ->> ‘key’ = ‘value’; ou WHERE data @> ‘{“key”: “value”}’. Um B-Tree não consegue olhar “dentro” do JSONB de forma eficiente.
    • Arrays: Para encontrar rapidamente linhas onde um array contenha um determinado valor (@>).
    • Full-Text Search: Para indexar os lexemas (tsvector) de um texto e permitir buscas textuais extremamente rápidas.
  • GiST (Generalized Search Tree): Um framework mais complexo, útil para indexar dados que podem se sobrepor. Seu caso de uso mais famoso é com a extensão PostGIS para dados geoespaciais. Um índice GiST em uma coluna de geometria permite responder a perguntas como “Encontre todos os restaurantes dentro deste raio” de forma extremamente eficiente.
  • BRIN (Block Range Indexes): Um tipo de índice mais leve, ideal para tabelas muito grandes cujos dados têm uma correlação natural com sua ordem física no disco (ex: uma tabela de logs ordenada por created_at). Ele armazena apenas o valor mínimo e máximo para um grande intervalo de blocos, resultando em um índice muito pequeno e de baixa sobrecarga.

Como Consertar: Alinhar o Índice ao Dado e à Consulta

A correção aqui é educacional e investigativa. Quando confrontado com uma query lenta, especialmente uma que opera em tipos de dados não tradicionais como JSONB ou geometrias, o primeiro passo é questionar se o B-Tree é a ferramenta certa. A documentação do PostgreSQL é excelente. Uma plataforma como a dbsnOOp ajuda a iniciar essa investigação: ao apontar com precisão que uma query em um campo JSONB é o seu maior gargalo, ela direciona o foco do engenheiro para pesquisar a solução correta, que neste caso seria a implementação de um índice GIN.

Erro 4: Negligenciar a Manutenção (Bloat e Estatísticas)

Criar um índice é apenas o começo da história. Índices são estruturas dinâmicas que se degradam com o tempo devido à forma como o PostgreSQL gerencia a concorrência (MVCC – Multi-Version Concurrency Control).

Index Bloat (Inchaço)

No PostgreSQL, UPDATEs e DELETEs não removem as versões antigas das linhas imediatamente. Eles as marcam como “mortas” (dead tuples). O processo de VACUUM é responsável por limpar esses tuplos mortos e tornar o espaço reutilizável. Se o VACUUM não consegue acompanhar a taxa de mudança, tanto a tabela quanto seus índices começam a inchar (bloat). O índice cresce em tamanho, não por conter mais dados vivos, mas por estar cheio de “lixo” e espaço vazio. Um índice inchado é menos eficiente: ele é maior, cabe menos dele em cache e requer mais I/O para ser lido.

  • Como Consertar: Um VACUUM FULL ou REINDEX pode reconstruir o índice do zero, eliminando o bloat. No entanto, essas operações exigem um lock exclusivo, causando downtime. Ferramentas como a extensão pg_repack são essenciais em produção, pois permitem reconstruir tabelas e índices online, sem bloqueios de longa duração.

Estatísticas Desatualizadas

Este é um dos problemas mais traiçoeiros. O planejador de consultas do PostgreSQL não executa a query; ele primeiro cria um “plano de execução”. Para decidir entre usar um índice ou fazer um Seq Scan, ele se baseia em estatísticas sobre os dados da tabela (guardadas na visão pg_stats). Se essas estatísticas estão desatualizadas, o planejador pode fazer uma estimativa de custo completamente errada e escolher um plano terrivelmente ineficiente, mesmo que o índice perfeito exista.

  • Como Consertar: A solução é executar a operação ANALYZE regularmente, que atualiza as estatísticas. O Autovacuum do PostgreSQL geralmente cuida disso, mas em tabelas com padrões de escrita incomuns, pode ser necessário um ajuste fino dos parâmetros do autovacuum ou a execução manual do ANALYZE.

dbsnOOp é um sistema de alerta precoce para esses problemas. Se a plataforma detecta que uma query, que sempre teve um plano de execução bom e rápido, de repente mudou para um plano ruim e lento sem nenhuma alteração no código, isso é um sinal clássico de que as estatísticas estão desatualizadas, permitindo que a equipe aja antes que o problema se torne uma crise.

De Adivinhação a Engenharia de Precisão

A indexação em PostgreSQL é uma disciplina de engenharia, não um ato de adivinhação. Cada erro — over-indexing, ordem errada, tipo errado ou falta de manutenção — deriva de uma falha em alinhar a estratégia de indexação com a realidade do workload de produção. A abordagem correta é inverter o processo: comece com os dados. Use uma plataforma de observabilidade para identificar as queries mais custosas, entender seus planos de execução e suas necessidades, e só então crie os índices precisos e cirúrgicos para atendê-las.

Ao mesmo tempo, use a mesma visibilidade para encontrar e remover o “peso morto” de índices não utilizados. Essa abordagem orientada por dados transforma a indexação de uma fonte de débito técnico em seu acelerador de performance mais confiável e potente.

Quer eliminar os erros da sua estratégia de indexação em PostgreSQL? Marque uma reunião com nosso especialista ou assista a uma demonstração na prática!

Para agendar uma conversa com um de nossos especialistas, acesse nosso site. Se preferir ver a ferramenta em ação, assista a uma demonstração gratuita. Mantenha-se atualizado com nossas dicas e novidades seguindo nosso canal no YouTube e nossa página no LinkedIn.

Agende uma demonstração aqui

Saiba mais sobre o dbsnOOp!

Visite nosso canal no youtube e aprenda sobre a plataforma e veja tutoriais

Aprenda sobre monitoramento de banco de dados com ferramentas avançadas aqui.

Leitura Recomendada

Compartilhar:

Leia mais

IMPULSIONE SUA OPERAÇÃO COM UM DBA AUTÔNOMO

SEM INSTALAÇÃO – 100% SAAS 

Complete o formulário abaixo para prosseguir

*Obrigatórias