Maintenance

Como executar VACUUM FULL sem espaço no disco atual do PostgreSQL

PG Monitoring Team August 01, 2026 12 min de leitura

O VACUUM FULL resolve um problema de armazenamento criando temporariamente outro: o PostgreSQL reescreve a relação em um novo arquivo e mantém o antigo até a operação terminar com sucesso. Quando o volume da tabela está quase cheio, o comando pode falhar antes de devolver um único gigabyte ao sistema operacional. A saída prática é registrar um volume auxiliar como tablespace temporário.

Um agrado da nossa equipe: transformamos uma técnica útil de tablespace em um runbook de produção, incluindo os pontos fáceis de esquecer: índices, TOAST, crescimento de WAL, locks, partições e a volta para o volume original.

O que “sem espaço em disco” realmente significa

Este procedimento não faz uma reescrita acontecer sem armazenamento. Ele significa “sem espaço livre suficiente no volume onde a tabela está agora”. Você ainda precisa de um disco anexado, volume SAN/NFS com durabilidade e latência adequadas ou outro filesystem local com capacidade temporária. A documentação do PostgreSQL confirma que o VACUUM FULL obtém lock ACCESS EXCLUSIVE e usa espaço adicional porque grava uma nova cópia antes de liberar a antiga.

O VACUUM (ANALYZE) comum deve continuar sendo a manutenção padrão: ele recupera o espaço das dead tuples para reutilização dentro da mesma tabela e normalmente permite leituras e escritas concorrentes. Use FULL apenas quando for necessário devolver espaço relevante ao sistema operacional e houver janela de manutenção.

Pré-flight: meça antes de mover

Troque public.orders pela relação que será reescrita. Esta consulta separa o tamanho de heap/TOAST dos índices e mostra o tablespace atual:

SELECT
  c.oid::regclass AS relation,
  COALESCE(t.spcname, 'pg_default') AS tablespace,
  pg_size_pretty(pg_table_size(c.oid)) AS table_and_toast,
  pg_size_pretty(pg_indexes_size(c.oid)) AS indexes,
  pg_size_pretty(pg_total_relation_size(c.oid)) AS total
FROM pg_class c
LEFT JOIN pg_tablespace t ON t.oid = c.reltablespace
WHERE c.oid = 'public.orders'::regclass;

Para um orçamento conservador, reserve no volume auxiliar pelo menos 2 × pg_total_relation_size mais 20%. A primeira cópia nasce quando os objetos migram para o tablespace; uma segunda pode coexistir enquanto o VACUUM FULL reescreve a tabela e recria seus índices. Tabelas com muito bloat podem exigir menos, mas um runbook de produção não deve depender do melhor caso.

  • Mantenha espaço livre no volume original do PostgreSQL para o pg_wal. Movimentações e reescritas podem gerar bastante WAL.
  • Confirme que réplicas e archive conseguem consumir o pico; um replication slot inativo pode reter WAL e lotar o disco original.
  • Estime a janela e ensaie a sequência completa em uma restauração antes da produção.
  • Tenha backup recuperável e verificado. Mover para tablespace é manutenção, não estratégia de backup.

Confira dead tuples e bloqueadores

SELECT schemaname, relname, n_live_tup, n_dead_tup,
       last_autovacuum, last_vacuum
FROM pg_stat_user_tables
WHERE relid = 'public.orders'::regclass;

SELECT pid, usename, state, xact_start, wait_event_type, wait_event,
       left(query, 120) AS query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY xact_start;

Tanto ALTER TABLE ... SET TABLESPACE quanto VACUUM FULL precisam de locks fortes. Um lock_timeout curto impede a operação de esperar escondida atrás do tráfego, mas a correção real é uma janela controlada, drenando novas sessões da aplicação.

1. Prepare o filesystem auxiliar

Como root, monte o volume extra e crie uma pasta vazia pertencente à conta de sistema que executa o PostgreSQL:

install -d -o postgres -g postgres -m 700 /mnt/pg_vacuum_tmp/ts_vacuum
df -hT /mnt/pg_vacuum_tmp
findmnt /mnt/pg_vacuum_tmp

Em distribuições com SELinux, rotule o volume antes de o PostgreSQL acessá-lo:

semanage fcontext -a -t postgresql_db_t "/mnt/pg_vacuum_tmp(/.*)?"
restorecon -Rv /mnt/pg_vacuum_tmp

Não crie um tablespace dentro do PGDATA e não aponte dois clusters PostgreSQL para a mesma pasta. O caminho deve ser absoluto, vazio, durável durante toda a operação e pertencer ao usuário de sistema do PostgreSQL.

2. Registre o tablespace temporário

Execute como superusuário do PostgreSQL. CREATE TABLESPACE não pode rodar dentro de bloco de transação:

CREATE TABLESPACE ts_vacuum_tmp
  LOCATION '/mnt/pg_vacuum_tmp/ts_vacuum';

SELECT spcname, pg_tablespace_location(oid)
FROM pg_tablespace
WHERE spcname = 'ts_vacuum_tmp';

3. Leve os índices e a tabela para o disco auxiliar

A movimentação da tabela não leva seus índices. No psql, a sequência abaixo move primeiro todos os índices válidos e depois heap e TOAST. Execute no banco que contém a tabela:

 et ON_ERROR_STOP on
SET lock_timeout = '10s';

SELECT format(
         'ALTER INDEX %s SET TABLESPACE ts_vacuum_tmp;',
         indexrelid::regclass
       )
FROM pg_index
WHERE indrelid = 'public.orders'::regclass
  AND indisvalid
gexec

ALTER TABLE public.orders SET TABLESPACE ts_vacuum_tmp;

Em tabela particionada, mover a tabela pai não move as partições. Inventarie e mova explicitamente cada partição folha. Verifique também views materializadas ou índices administrados separadamente da relação alvo.

4. Rode o VACUUM FULL no disco auxiliar

Execute fora de uma transação explícita. O vacuumdb deixa banco e tabela bem claros:

vacuumdb --dbname=app_production   --table='public.orders'   --full --analyze --verbose

Monitore três destinos: o mount auxiliar, o volume original de dados/WAL e o archive. Em outra sessão, acompanhe o progresso nas versões suportadas:

SELECT pid, datname, relid::regclass AS relation,
       phase, heap_blks_scanned, heap_blks_total
FROM pg_stat_progress_cluster;

O VACUUM FULL usa a mesma view de progresso de reescrita do CLUSTER. Resultado vazio depois do término é normal.

5. Valide antes de voltar

SELECT
  c.oid::regclass AS relation,
  COALESCE(t.spcname, 'pg_default') AS tablespace,
  pg_size_pretty(pg_table_size(c.oid)) AS table_and_toast,
  pg_size_pretty(pg_indexes_size(c.oid)) AS indexes,
  pg_size_pretty(pg_total_relation_size(c.oid)) AS total
FROM pg_class c
LEFT JOIN pg_tablespace t ON t.oid = c.reltablespace
WHERE c.oid = 'public.orders'::regclass;

SELECT count(*) FROM public.orders;

Rode smoke tests da aplicação e confirme que as réplicas alcançaram o primário. Se a relação compactada ainda não couber com margem no volume original, pare aqui e mantenha-a em um tablespace permanente e monitorado, em vez de forçar o retorno.

6. Devolva os objetos compactados e limpe

 et ON_ERROR_STOP on
SET lock_timeout = '10s';

ALTER TABLE public.orders SET TABLESPACE pg_default;

SELECT format(
         'ALTER INDEX %s SET TABLESPACE pg_default;',
         indexrelid::regclass
       )
FROM pg_index
WHERE indrelid = 'public.orders'::regclass
  AND indisvalid
gexec

DROP TABLESPACE ts_vacuum_tmp;

O DROP TABLESPACE só termina se nenhum objeto restar ali. Depois do sucesso, desmonte e desanexe o volume temporário pelo procedimento do sistema operacional ou da nuvem. Nunca apague a pasta enquanto o PostgreSQL ainda tiver o tablespace registrado.

Plano de falha e alternativas mais seguras

SituaçãoMelhor resposta
Não existe janela de manutençãoAvalie pg_repack; ele reduz a fase de lock exclusivo, mas ainda precisa de disco extra e testes cuidadosos.
O espaço só precisa ser reutilizado pela tabelaUse VACUUM (ANALYZE) comum; não pague o custo de uma reescrita completa.
A tabela é naturalmente temporalParticione, arquive partições antigas e remova-as, em vez de reescrever repetidamente um heap gigante.
O volume de WAL já está críticoCorrija archive/slots ou aumente a margem de WAL antes de qualquer reescrita.
A volta falhaMantenha a relação no tablespace auxiliar, restaure o serviço e investigue a capacidade. Não remova o tablespace.

Monitore a causa, não apenas a limpeza

Uma reescrita bem-sucedida trata o bloat acumulado; ela não explica por que ele apareceu. O PG Monitoring acompanha crescimento de dead tuples, cadência do autovacuum, transações longas, tamanho das tabelas, retenção por replication slots e previsão de disco continuamente. Essa evidência mostra se a correção permanente é tuning de autovacuum, higiene de transações, particionamento ou capacidade — e evita outro VACUUM FULL emergencial.

Referências: documentação do VACUUM, CREATE TABLESPACE, ALTER TABLE e a técnica original de tablespace que inspirou este runbook ampliado.

Related Articles

Ready to experience better PostgreSQL monitoring?

Join thousands of teams who switched from traditional tools to PG Monitoring's AI-powered platform.

Fale conosco