Vacuum & Bloat Demystified
O PostgreSQL nunca sobrescreve uma linha no lugar quando você a UPDATE ou DELETE.
Em vez disso, ele deixa a versão antiga para trás e escreve uma nova, que é a base de como o banco de dados fornece a cada transação um snapshot consistente dos dados.
Essa versão antiga se torna uma tupla morta, e tuplas mortas são a matéria-prima do que esta seção chama de bloat.
Vacuum e bloat parecem dois tópicos separados, mas são realmente um único mecanismo visto de extremidades opostas: vacuum é o processo de limpeza, e bloat é o que se acumula quando a limpeza fica para trás.
Esta página constrói o modelo mental que o restante da seção vacuum-bloat-storage assume que você já possui.
Resumo
- O design MVCC do PostgreSQL deixa versões de linha mortas para trás em cada atualização e exclusão, e o vacuum é o processo em segundo plano que recupera esse espaço e protege o cluster contra exaustão de ID de transação.
- Por que Importa: Pular ou ajustar incorretamente o vacuum leva a tabelas inchadas (bloated), varreduras de índice mais lentas, disco desperdiçado e, no pior caso, um desligamento de emergência para prevenir o wraparound do ID de transação.
- Conceitos Chave: tupla morta, MVCC, mapa de visibilidade, autovacuum, horizonte de congelamento, bloat.
- Quando Usar: Toda tabela de produção precisa deste modelo mental - ele explica por que as tabelas crescem após exclusões, por que varreduras apenas por índice às vezes param de funcionar e por que desabilitar o autovacuum é quase sempre a decisão errada.
- Limitações / Trade-offs: O vacuum recupera espaço para reutilização dentro do arquivo existente, mas não encolhe o arquivo no disco no caso comum, e configurações agressivas de vacuum trocam I/O e CPU por limpeza.
- Tópicos Relacionados: Visibilidade MVCC, ajuste de autovacuum, wraparound de ID de transação, bloat de tabela e índice.
Fundamentos
MVCC, abreviação de controle de concorrência multi-versão, é a razão pela qual o PostgreSQL pode permitir que uma transação leia uma tabela enquanto outra a atualiza sem que nenhuma delas bloqueie a outra. Cada versão de linha carrega metadados ocultos registrando qual transação a criou e, se foi substituída, qual transação a excluiu.
Quando uma transação lê uma tabela, o PostgreSQL entrega a ela as versões de linha que estavam visíveis no momento em que seu snapshot foi tirado, ignorando versões criadas depois e versões já excluídas antes.
Isso é poderoso para concorrência, mas significa que um DELETE não apaga realmente nada no instante em que é executado.
A versão antiga da linha permanece fisicamente presente no arquivo da tabela até que nada mais possa precisar dela, momento em que se torna uma tupla morta elegível para limpeza. Vacuum é o processo que percorre as páginas de uma tabela, identifica tuplas mortas que nenhuma transação aberta pode mais ver e marca esse espaço como reutilizável para futuras inserções e atualizações. Uma analogia simples é um hotel que nunca demole um quarto de hóspede que fez check-out: ele apenas marca o quarto como "vago" para que o próximo hóspede possa entrar, e o vacuum é a passagem de arrumação que muda o status de um quarto de ocupado para vago.
Crucialmente, o trabalho do vacuum é criar espaço reutilizável dentro do edifício existente, não demolir e reconstruir o edifício menor.
Essa única distinção explica quase todas as surpresas que os iniciantes encontram com vacuum e bloat.
Mecânicas e Interações
O trabalho mais visível do vacuum é recuperar o espaço de tuplas mortas, mas seu trabalho mais crítico para a segurança é prevenir o wraparound do ID de transação, e ambas as responsabilidades passam pela mesma varredura.
Os IDs de transação do PostgreSQL são um contador finito e que se repete (wraps), e os metadados de visibilidade de cada linha são marcados com o ID da transação que a criou.
Se o contador pudesse se repetir enquanto linhas antigas ainda referenciam um ID de transação não congelado, essas linhas poderiam subitamente parecer vir do futuro e desaparecer da vista.
O vacuum previne isso congelando periodicamente versões antigas de linhas, marcando-as como permanentemente visíveis para que seu ID de transação original não importe mais.
O autovacuum decide quando agir em uma tabela usando uma fórmula de limite simples: ele dispara assim que as tuplas mortas excedem um limite base mais um fator de escala vezes a contagem de linhas vivas da tabela.
Essa fórmula é o motivo pelo qual autovacuum_vacuum_scale_factor importa mais à medida que uma tabela cresce - o limite padrão é trivial em uma tabela de 10 linhas, mas em uma tabela de 50 milhões de linhas, ele permite que dezenas de milhões de linhas mortas se acumulem antes que o autovacuum sequer considere agir.
-- Verificação conceitual do gatilho que o autovacuum realiza por tabela:
-- executa quando n_dead_tup >= vacuum_threshold + vacuum_scale_factor * n_live_tup
SELECT relname, n_live_tup, n_dead_tup, last_autovacuum
FROM pg_stat_user_tables
WHERE relname = 'orders';O mapa de visibilidade é a outra metade da história das mecânicas, rastreando quais páginas contêm apenas tuplas visíveis para todas as transações.
Uma página marcada como totalmente visível nesse mapa permite que uma varredura apenas por índice responda a uma consulta apenas do índice, pulando completamente a ida ao heap.
O vacuum é o que atualiza o mapa de visibilidade, então uma tabela que fica muito tempo sem vacuum perde a elegibilidade de varredura apenas por índice, mesmo quando seus índices estão saudáveis.
Transações de longa duração interagem mal com tudo isso, pois o vacuum não pode remover uma tupla morta que o snapshot de uma transação mais antiga ainda possa precisar ver.
Uma única sessão ociosa em transação deixada aberta por horas pode reter o horizonte de congelamento de uma tabela inteira, e às vezes um banco de dados inteiro, não importa quão agressivamente o autovacuum seja ajustado.
O bloat em si é simplesmente o acúmulo de tuplas mortas e fragmentação de página que supera a capacidade do vacuum de limpá-lo, e ele aparece de forma diferente em tabelas do que em índices.
Considerações Avançadas e Aplicações
Bloat de tabela e bloat de índice não são a mesma falha, e raramente são corrigidos pela mesma ferramenta.
Bloat de tabela significa que as páginas carregam tuplas mortas que o vacuum pode marcar como reutilizáveis no lugar, o que mantém o tamanho do arquivo estável assim que o vacuum alcança.
Bloat de índice é estrutural: uma página B-tree que foi dividida e parcialmente esvaziada por exclusões não se funde automaticamente de volta em uma árvore compacta, então um índice pode permanecer inchado mesmo depois que a tabela subjacente está limpa.
Essa assimetria é o motivo pelo qual o tamanho de uma tabela no disco pode parecer bom enquanto seus índices silenciosamente incham para várias vezes o tamanho necessário. O armazenamento TOAST adiciona outra complicação, pois valores de coluna grandes são movidos da tabela principal para uma tabela TOAST separada com seu próprio ciclo de vida independente de vacuum e bloat. Em escala, a questão prática muda de "o vacuum está rodando" para "o vacuum está acompanhando o ritmo", e isso é um problema de capacidade tanto quanto um problema de configuração.
Uma tabela sob forte rotatividade de atualizações ou exclusões precisa de um fator de escala menor e um limite de custo maior do que os padrões globais do cluster, pois esses padrões são ajustados para a tabela mediana, não para a mais ocupada.
O PostgreSQL 18.4 ainda expõe pg_stat_progress_vacuum para observar a fase e o progresso de um vacuum em andamento, o que importa ao decidir se deve esperar ou intervir.
Quando o bloat já ocorreu, as opções de remediação trocam disponibilidade por abrangência de maneiras diferentes.
| Abordagem | Força | Fraqueza | Melhor Ajuste |
|---|---|---|---|
VACUUM Simples | Online, sem bloqueio exclusivo, seguro para executar a qualquer momento | Não encolhe o arquivo no disco | Limpeza de tuplas mortas rotineira |
VACUUM FULL | Compacta totalmente a tabela ao seu tamanho mínimo | Requer um bloqueio de acesso exclusivo, bloqueando todo o acesso | Encolhimento raro, apenas em janela de manutenção |
pg_repack | Reconstrói a tabela online com bloqueio mínimo | Necessita de espaço extra temporário em disco durante a reconstrução | Encolhimento em produção sem downtime |
REINDEX CONCURRENTLY | Reconstrói um índice inchado sem bloquear leituras/escritas | Corrige apenas o bloat de índice, não o bloat de tabela | Índices inchados em tabelas ativas |
| Particionamento + Desanexar | Descarta dados antigos instantaneamente sem necessidade de vacuum nas linhas removidas | Requer uma chave de particionamento que corresponda ao padrão de retenção | Tabelas de séries temporais ou com muitas inserções |
Desabilitar o autovacuum em uma tabela ocupada para "reduzir a carga" é um dos erros mais caros que uma equipe pode cometer, pois tuplas mortas e linhas não congeladas continuam a se acumular invisivelmente até que o desempenho degrade acentuadamente ou a salvaguarda de wraparound force o PostgreSQL a entrar em modo somente leitura.
Provedores de nuvem gerenciados geralmente deixam o autovacuum ativado por padrão e expõem os mesmos GUCs por tabela, então este modelo mental se transfere diretamente, quer o cluster seja auto-hospedado ou gerenciado.
Equívocos Comuns
- "DELETE libera espaço em disco imediatamente." A linha se torna uma tupla morta primeiro, e apenas o vacuum torna esse espaço reutilizável, e mesmo assim o arquivo raramente encolhe - um equívoco que vem de como a maioria dos outros softwares trata a exclusão como imediata e final.
- "VACUUM FULL é apenas um VACUUM mais completo." É uma operação fundamentalmente diferente que reescreve a tabela inteira sob um bloqueio exclusivo, não uma versão mais forte do vacuum rotineiro, razão pela qual nunca deve ser uma resposta padrão ao bloat.
- "Autovacuum rodando significa que o bloat é impossível." O autovacuum rodando não garante que ele esteja acompanhando o ritmo da taxa de escrita, e uma tabela ainda pode inchar constantemente se o crescimento de tuplas mortas superar a frequência do vacuum.
- "Desligar o autovacuum melhora o desempenho." Ele remove I/O visível no curto prazo enquanto acumula silenciosamente tuplas mortas e risco de wraparound que custam muito mais para corrigir depois do que a sobrecarga do vacuum jamais custou.
- "Vacuum é apenas sobre recuperar espaço." Congelar linhas para prevenir o wraparound do ID de transação é pelo menos tão importante quanto a recuperação de espaço, e o PostgreSQL força um vacuum agressivo por essa razão, mesmo em uma tabela sem problema de bloat.
FAQs
Por que DELETE não encolhe minha tabela no disco?
Porque o design MVCC do PostgreSQL deixa a versão antiga da linha no lugar como uma tupla morta; o vacuum posteriormente marca esse espaço como reutilizável para novas linhas, mas reutilizar espaço dentro do arquivo não é o mesmo que retornar bytes para o sistema operacional.
Qual é a diferença entre VACUUM e VACUUM FULL?
VACUUMSimples recupera espaço de tuplas mortas para reutilização sem um bloqueio exclusivo, então leituras e escritas normais continuam.VACUUM FULLreescreve a tabela inteira em um novo arquivo compacto sob um bloqueio de acesso exclusivo que bloqueia todo o acesso até que termine.
Como o autovacuum decide quando executar em uma tabela?
Ele compara a contagem de tuplas mortas da tabela com uma fórmula de limite, um limite base mais um fator de escala multiplicado pela contagem de linhas vivas, e dispara um vacuum assim que as tuplas mortas cruzam essa linha.
Por que a varredura apenas por índice para de funcionar em uma tabela que eu achava que estava bem?
Varreduras apenas por índice dependem do mapa de visibilidade mostrando uma página como totalmente visível para todas as transações, e apenas o vacuum atualiza esse mapa, então uma tabela que ficou muito tempo sem vacuum perde essa elegibilidade mesmo que o índice em si esteja saudável.
Bloat de tabela é o mesmo problema que bloat de índice?
Não - bloat de tabela é espaço de tupla morta que o vacuum pode recuperar no lugar, enquanto bloat de índice é fragmentação estrutural de divisões de página B-tree que o vacuum sozinho tipicamente não consegue desfazer, razão pela qual índices inchados frequentemente precisam de REINDEX CONCURRENTLY separadamente.
O que é wraparound de ID de transação e por que o vacuum o previne?
O contador de ID de transação do PostgreSQL é finito e se repete (wraps), então o vacuum periodicamente congela versões antigas de linhas para tornar sua visibilidade independente desse contador, evitando um cenário onde linhas antigas pareceriam vir do futuro assim que o contador se repetisse.
Uma transação de longa duração pode realmente bloquear o vacuum em uma tabela inteira?
Sim - o vacuum não pode remover uma tupla morta que o snapshot de uma transação mais antiga ainda possa precisar, então uma única sessão ociosa em transação deixada aberta por horas pode reter a limpeza e o congelamento em toda a tabela que ela tocou.
Devo desabilitar o autovacuum em uma tabela?
Quase nunca para a tabela inteira - desabilitá-lo remove a salvaguarda contra bloat e wraparound, e a abordagem padrão é ajustar configurações por tabela como fator de escala e limite de custo em vez de desativá-lo.
O que o TOAST tem a ver com bloat?
Valores de coluna grandes são movidos da tabela principal para uma tabela TOAST separada com seu próprio ciclo de vida independente de vacuum e bloat, então o heap principal de uma tabela pode parecer limpo enquanto seu armazenamento TOAST está inchado, ou vice-versa.
Como sei se o vacuum está realmente acompanhando minha carga de escrita?
Observe n_dead_tup em relação a n_live_tup e last_autovacuum em pg_stat_user_tables ao longo do tempo - uma proporção que continua subindo ou um last_autovacuum que continua caindo cada vez mais para trás, ambos sinalizam que o vacuum está perdendo terreno.
Qual é a maneira menos disruptiva de corrigir uma tabela de produção inchada?
pg_repack é geralmente a escolha mais segura, pois reconstrói a tabela online com muito menos bloqueio do que VACUUM FULL, ao custo de precisar de espaço extra temporário em disco durante a reconstrução.
Este modelo de vacuum se aplica da mesma forma no PostgreSQL gerenciado na nuvem?
Sim - provedores gerenciados executam a mesma maquinaria de vacuum e autovacuum do PostgreSQL por baixo dos panos e expõem os mesmos GUCs por tabela, então o modelo mental e as alavancas de ajuste descritos aqui se transferem diretamente.
Relacionados
- Noções Básicas de Vacuum - comandos práticos e consultas de monitoramento para trabalho diário com vacuum
- Ajuste de Autovacuum - fatores de escala e limites de custo para manter o vacuum à frente da carga de escrita
- Wraparound de ID de Transação - o mecanismo de congelamento e a resposta de emergência a wraparound
- Bloat de Tabela e Índice - medindo bloat e escolhendo um caminho de remediação
- Visibilidade MVCC - o mecanismo de versão de linha que torna as tuplas mortas inevitáveis
Versões de Stack: Esta página foi escrita para PostgreSQL 18.4 (estável 18, manutenção 17).