TL;DR — Resumo Rápido

Domine o ajuste de VACUUM e autovacuum no PostgreSQL para eliminar bloat, prevenir o wraparound de XID e maximizar o desempenho do seu banco de dados.

O VACUUM no PostgreSQL é uma das operações de manutenção mais incompreendidas e ao mesmo tempo mais críticas em qualquer implantação PostgreSQL. Sem ajuste adequado, as tabelas acumulam silenciosamente tuplas mortas que infam o armazenamento, degradam o desempenho das consultas e, no pior caso, desencadeiam um wraparound catastrófico do ID de transação que coloca todo o banco de dados offline. Este guia cobre tudo que você precisa para configurar o autovacuum corretamente e manter bancos de dados em produção saudáveis.

Pré-requisitos

  • PostgreSQL 13 ou superior (a maioria dos exemplos se aplica a partir do PostgreSQL 10+)
  • psql ou um cliente SQL com acesso em nível DBA
  • Capacidade de editar postgresql.conf ou usar ALTER SYSTEM
  • Familiaridade básica com conceitos de armazenamento do PostgreSQL

Por que o VACUUM existe: MVCC e tuplas mortas

O PostgreSQL usa Controle de Concorrência Multiversão (MVCC) para lidar com leituras e escritas concorrentes sem bloqueios. Cada UPDATE ou DELETE não modifica dados in-place — em vez disso, marca a versão antiga da linha como morta e escreve uma nova versão. Isso faz com que as leituras não bloqueiem, mas deixa para trás tuplas mortas: versões de linhas que não são mais visíveis para nenhuma transação, mas ainda ocupam espaço em disco.

Com o tempo, as tuplas mortas causam três problemas graves:

  1. Bloat de tabela: O arquivo físico da tabela cresce mesmo que a contagem lógica de linhas permaneça constante. Uma tabela com 10 milhões de linhas ativas pode ocupar o espaço de 50 milhões em disco.
  2. Bloat de índice: Índices B-tree acumulam ponteiros para tuplas mortas, aumentando seu tamanho e custo de varredura.
  3. Wraparound de ID de transação: O PostgreSQL usa IDs de transação de 32 bits. Após aproximadamente 2 bilhões de transações, os IDs dão a volta. O PostgreSQL força uma parada de emergência em todo o banco de dados se o VACUUM não conseguir congelar transações antigas a tempo.

O VACUUM resolve os três problemas ao varrer páginas de tabelas, marcando tuplas mortas como espaço reutilizável e avançando o horizonte de congelamento para transações antigas.

Tipos de VACUUM comparados

ComandoBloqueioRecuperação de espaçoCaso de uso
VACUUMNenhum (compartilha com leituras/escritas)Marca espaço para reutilização, não encolhe o arquivoManutenção rotineira, autovacuum
VACUUM ANALYZENenhumIgual + atualiza estatísticas do planejadorApós inserções/deleções em massa
VACUUM FULLExclusivo (tabela bloqueada)Reescreve a tabela, devolve espaço ao SORecuperação pontual de bloat severo
VACUUM FREEZENenhumForça congelamento de todos os XIDs antigosPrevenção de wraparound

Use VACUUM FULL apenas como etapa de recuperação pontual em tabelas muito infladas — ele mantém um bloqueio exclusivo que bloqueia todas as leituras e escritas durante sua execução. Para reescritas de tabelas online, use pg_repack.

O daemon autovacuum: parâmetros principais

O autovacuum é executado como um daemon em background que lança processos worker para vacuumar tabelas automaticamente. Os parâmetros-chave no postgresql.conf:

# Com que frequência o launcher verifica se há trabalho
autovacuum_naptime = 1min

# Máximo de workers concorrentes de autovacuum
autovacuum_max_workers = 3

# Ativa vacuum quando tuplas mortas > threshold + scale_factor * n_live_tup
autovacuum_vacuum_threshold = 50
autovacuum_vacuum_scale_factor = 0.2       # 20% da tabela

# Ativa ANALYZE quando tuplas modificadas excedem isso
autovacuum_analyze_threshold = 50
autovacuum_analyze_scale_factor = 0.1      # 10% da tabela

# Congela linhas com esta antiguidade de transação
autovacuum_freeze_max_age = 200000000

O ponto crítico de ajuste é autovacuum_vacuum_scale_factor. O padrão de 0.2 significa que o autovacuum dispara quando 20% das linhas de uma tabela estão mortas. Para uma tabela de 100 linhas, isso é aceitável. Para uma tabela de 50 milhões de linhas, o autovacuum não disparará até que 10 milhões de tuplas mortas tenham se acumulado.

Valores globais recomendados para sistemas de alta escrita:

autovacuum_vacuum_scale_factor = 0.05
autovacuum_analyze_scale_factor = 0.02
autovacuum_max_workers = 5
autovacuum_naptime = 30s

Configurações de autovacuum por tabela

A técnica de ajuste mais poderosa é definir parâmetros de autovacuum por tabela, substituindo os padrões globais apenas onde necessário:

-- Para uma tabela de muito alta escrita (ex.: log de eventos/auditoria)
ALTER TABLE eventos SET (
    autovacuum_vacuum_scale_factor = 0.01,
    autovacuum_vacuum_threshold = 500,
    autovacuum_analyze_scale_factor = 0.005,
    autovacuum_vacuum_cost_delay = 2
);

-- Para uma tabela de consulta quase estática (vacuum menos agressivo)
ALTER TABLE codigos_pais SET (
    autovacuum_vacuum_scale_factor = 0.5,
    autovacuum_vacuum_threshold = 1000
);

Monitoramento do bloat de tabelas

Usando pg_stat_user_tables

-- Top 20 tabelas por contagem de tuplas mortas
SELECT
    schemaname,
    relname AS nome_tabela,
    n_live_tup,
    n_dead_tup,
    round(n_dead_tup::numeric / NULLIF(n_live_tup + n_dead_tup, 0) * 100, 1) AS pct_mortas,
    last_vacuum,
    last_autovacuum,
    last_analyze,
    last_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 20;

Detectar tabelas próximas ao wraparound de XID

SELECT
    schemaname,
    relname,
    age(relfrozenxid) AS idade_xid,
    pg_size_pretty(pg_total_relation_size(oid)) AS tamanho_tabela
FROM pg_class
JOIN pg_namespace ON pg_namespace.oid = relnamespace
WHERE relkind = 'r'
ORDER BY age(relfrozenxid) DESC
LIMIT 20;

Qualquer tabela com idade_xid acima de 1,5 bilhão precisa de atenção imediata.

Medir o bloat real com pgstattuple

CREATE EXTENSION IF NOT EXISTS pgstattuple;

SELECT
    dead_tuple_count,
    round(dead_tuple_percent, 1) AS pct_mortas,
    free_space,
    round(free_percent, 1) AS pct_livre
FROM pgstattuple('public.pedidos');

Prevenção do wraparound de XID

Monitore a idade no nível do banco de dados:

SELECT
    datname,
    age(datfrozenxid) AS idade_xid,
    pg_size_pretty(pg_database_size(datname)) AS tamanho_bd
FROM pg_database
ORDER BY age(datfrozenxid) DESC;

Recuperação de emergência se um banco estiver próximo do wraparound:

vacuumdb --all --freeze --verbose --analyze

VACUUM FULL vs pg_repack

Para sistemas de produção que não podem tolerar tempo de inatividade, use pg_repack:

sudo apt install postgresql-16-repack
pg_repack -h localhost -U postgres -d meubd -t pedidos
pg_repack -h localhost -U postgres -d meubd --only-indexes -t pedidos

Problemas comuns e resolução

Autovacuum eliminado por transações de longa duração: Busque transações obsoletas:

SELECT pid, now() - xact_start AS duracao, query, state
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY duracao DESC
LIMIT 10;

“Autovacuum: preventing wraparound” nos logs: Significa que o autovacuum entrou em modo de emergência. Não bloqueie nem encerre esses processos de vacuum.

Referência de ajuste por tamanho do banco

Tamanho do BDautovacuum_max_workersautovacuum_vacuum_scale_factorautovacuum_vacuum_cost_limit
< 10 GB3 (padrão)0.05200 (padrão)
10–100 GB4–50.02400
100 GB – 1 TB6–80.01600
> 1 TB8–10 + por tabela0.005 em tabelas ativas800

Comparativo entre sistemas de banco de dados

RecursoPostgreSQL VACUUMMySQL OPTIMIZE TABLESQL Server Index RebuildOracle Segment Shrink
Bloqueios necessáriosNenhum (regular), Exclusivo (FULL)ExclusivoOnline (Enterprise)Exclusivo de linha
Ferramenta de reescrita onlinepg_repackpt-online-schema-changeRebuild onlineSHRINK SPACE COMPACT
AutomáticoDaemon autovacuumManual ou event schedulerManutenção automática de índicesNão automático
Risco de wraparoundSim (XID)NãoNãoNão
Visibilidade do bloatpg_stat_user_tables, pgstattupleinformation_schemasys.dm_db_index_physical_statsDBA_SEGMENTS

Resumo

  • VACUUM recupera espaço de tuplas mortas de atualizações e deleções MVCC; sem ele, as tabelas incham e o wraparound de XID eventualmente para o banco.
  • Autovacuum é a solução automática mas requer ajuste — o padrão autovacuum_vacuum_scale_factor = 0.2 é alto demais para tabelas grandes.
  • Use configuração por tabela com ALTER TABLE SET para tabelas de alta escrita.
  • Monitore pg_stat_user_tables e age(datfrozenxid) em pg_database.
  • Use pg_repack em vez de VACUUM FULL em tabelas de produção para evitar downtime.
  • Mantenha age(datfrozenxid) bem abaixo de 1,5 bilhão em todos os bancos.

Artigos Relacionados