24.1. Limpeza de rotina #

24.1.1. Noções básicas de limpeza
24.1.2. Recuperação de espaço em disco
24.1.3. Atualização das estatísticas do planejador
24.1.4. Atualização do mapa de visibilidade
24.1.5. Prevenção de falhas de reinício de ID de transação
24.1.6. Processo de limpeza automática

Os bancos de dados do PostgreSQL requerem uma manutenção de limpeza periódica, conhecida como VACUUM. Para muitas instalações, é suficiente deixar a limpeza ser feita pelo daemon autovacuum, descrito em Processo de limpeza automática. Pode ser necessário ajustar os parâmetros de limpeza automática descritos nesta seção para obter os melhores resultados. Alguns administradores de banco de dados desejarão complementar ou substituir as atividades do autovacuum por comandos VACUUM gerenciados manualmente, que são normalmente executados segundo uma programação de comandos especificada no cron, ou no Gerenciador de tarefas do Windows. Para configurar adequadamente a limpeza gerenciada manualmente, é essencial entender os problemas discutidos nas próximas subseções. Os administradores que dependem do daemon autovacuum podem ler este material para ajudá-los a entender e ajustar o autovacuum.

24.1.1. Noções básicas de limpeza #

O comando VACUUM do PostgreSQL precisa processar cada tabela regularmente por vários motivos:

  1. Para recuperar ou reutilizar o espaço em disco ocupado por linhas atualizadas ou excluídas.
  2. Para atualizar os dados das estatísticas usadas pelo planejador de consultas do PostgreSQL.
  3. Para atualizar o mapa de visibilidade, que acelera as varreduras somente de índice.
  4. Para proteger contra perda de dados muito antigos devido ao reinício do ID de transação, ou reinício do ID multixact.

Cada um desses motivos determina a execução de operações de VACUUM de frequência e escopo variados, conforme explicado nas subseções a seguir.

Existem duas variantes do comando VACUUM: VACUUM padrão e VACUUM FULL. O comando VACUUM FULL pode recuperar mais espaço em disco, mas a execução é muito mais lenta. Além disso, a forma padrão do comando VACUUM pode ser executada em paralelo com as operações do banco de dados em produção. (Comandos como SELECT, INSERT, UPDATE e DELETE continuarão a funcionar normalmente, embora não seja possível modificar a definição de uma tabela com comandos como ALTER TABLE enquanto esta estiver sendo limpa.) O comando VACUUM FULL requer o bloqueio ACCESS EXCLUSIVE na tabela em que está trabalhando, portanto não pode ser usado em paralelo com outro uso da tabela. Geralmente, portanto, os administradores devem se esforçar para usar o comando VACUUM padrão, e evitar o uso do comando VACUUM FULL.

O comando VACUUM cria uma quantidade substancial de tráfego de E/S, o que pode causar baixo desempenho nas outras sessões ativas. Existem parâmetros de configuração que podem ser ajustados para reduzir o impacto no desempenho devido à limpeza em segundo plano — veja Atraso do VACUUM baseado em custos.

24.1.2. Recuperação de espaço em disco #

No PostgreSQL, o UPDATE ou DELETE de uma linha não remove imediatamente a versão antiga da linha. Esta abordagem é necessária para obter os benefícios do controle de concorrência multiversão (MVCC, veja Controle de concorrência): a versão da linha não deve ser removida enquanto ainda estiver potencialmente visível para outras transações. Mas, por fim, uma versão de linha desatualizada ou excluída não é mais interessante para nenhuma transação. O espaço que ocupa deve então ser recuperado para reutilização por novas linhas, para evitar o crescimento ilimitado dos requisitos de espaço em disco. Isto é feito executando o comando VACUUM.

A forma padrão do comando VACUUM remove versões de linhas mortas em tabelas e índices, e marca o espaço disponível para reutilização futura. Entretanto, não devolverá o espaço ao sistema operacional, exceto no caso especial em que uma ou mais páginas no final da tabela fiquem inteiramente livres, e o bloqueio exclusivo da tabela pode ser facilmente obtido. Em contraste, o comando VACUUM FULL compacta as tabelas ativamente, escrevendo uma nova versão completa do arquivo da tabela sem espaço morto. Isto minimiza o tamanho da tabela, mas pode levar muito tempo. Também requer espaço em disco extra para a nova cópia da tabela, até que a operação seja concluída.

O objetivo usual da limpeza de rotina é executar o comando VACUUM padrão com frequência suficiente para evitar a necessidade do VACUUM FULL. O daemon autovacuum tenta trabalhar dessa forma e, na verdade, nunca executará o VACUUM FULL. Nesta abordagem, a ideia não é manter as tabelas em seu tamanho mínimo, mas manter o uso do espaço em disco estável: cada tabela ocupa um espaço equivalente ao seu tamanho mínimo, mais quanto espaço for usado entre execuções de limpeza. Embora o comando VACUUM FULL possa ser usado para reduzir uma tabela de volta ao seu tamanho mínimo e devolver o espaço em disco ao sistema operacional, não haverá muito sentido nisso se a tabela simplesmente crescer novamente no futuro. Portanto, para manter as tabelas atualizadas, execuções moderadamente frequentes do comando VACUUM padrão é uma abordagem melhor do que execuções pouco frequentes do comando VACUUM FULL.

Alguns administradores preferem agendar a limpeza por conta própria como, por exemplo, fazendo todo o trabalho à noite, quando a carga estiver baixa. A dificuldade em fazer a limpeza segundo um cronograma fixo é que, se a tabela tiver um pico inesperado na atividade de atualização, ela poderá crescer a ponto de ser realmente necessário o uso do comando VACUUM FULL para recuperar o espaço. O uso do daemon autovacuum alivia este problema, porque o daemon autovacuum agenda a limpeza dinamicamente em resposta à atividade de atualização. Não é aconselhável desativar o daemon autovacuum inteiramente, a menos que se tenha uma carga de trabalho extremamente previsível. Um compromisso possível é definir os parâmetros do processo para reagir apenas a atividades de atualização inusitadamente pesadas, evitando assim que a situação saia de controle, enquanto comandos VACUUM programados devem fazer a maioria do trabalho quando a carga for típica.

Para aqueles que não usam o autovacuum, uma abordagem típica é agendar o comando VACUUM para cada banco de dados uma vez por dia durante um período de baixo uso, complementado por uma limpeza mais frequente de tabelas fortemente atualizadas conforme necessário. (Algumas instalações com taxas de atualização extremamente altas limpam suas tabelas mais ativas tão frequentemente quanto uma vez a cada poucos minutos.) Havendo vários bancos de dados em uma instância, não se deve esquecer de executar o comando VACUUM em cada um deles; o utilitário vacuumdb pode ser útil.

Dica

O comando VACUUM simples pode não ser satisfatório quando a tabela contém muitas versões de linhas mortas, como resultado de atividade de atualização ou de exclusão acima do normal. Se existir uma tabela assim, e for preciso recuperar o excesso de espaço em disco que ela ocupa, será necessário usar o comando VACUUM FULL, ou, como alternativa, o comando CLUSTER, ou uma das variantes de reescrita de tabela do comando ALTER TABLE. Estes comandos reescrevem uma nova cópia inteira da tabela, e criam novos índices para ela. Todas estas opções requerem o bloqueio ACCESS EXCLUSIVE. Note que estes comandos também usam temporariamente um espaço extra em disco aproximadamente igual ao tamanho da tabela, porque as cópias antigas da tabela e dos índices não podem ser liberadas até que as novas sejam concluídas.

Dica

Se houver uma tabela cujo conteúdo inteiro é excluído periodicamente, deve-se considerar fazê-lo usando o comando TRUNCATE, em vez de usar o comando DELETE seguido pelo comando VACUUM. O comando TRUNCATE remove todo o conteúdo da tabela imediatamente, sem exigir um comando VACUUM ou VACUUM FULL subsequente para recuperar o espaço em disco não mais utilizado. A desvantagem é que é violada a semântica estrita do MVCC.

24.1.3. Atualização das estatísticas do planejador #

O planejador de consultas do PostgreSQL conta com informações estatísticas sobre o conteúdo das tabelas para gerar bons planos de consultas. Estas estatísticas são reunidas pelo comando ANALYZE, que pode ser chamado sozinho, ou como uma etapa opcional do comando VACUUM. É importante ter estatísticas razoavelmente precisas, caso contrário escolhas ruins de planos podem degradar o desempenho do servidor de banco de dados.

O daemon autovacuum, se ativo, executa automaticamente comandos ANALYZE sempre que o conteúdo da tabela for alterado o suficiente. Entretanto, os administradores podem preferir confiar em operações de ANALYZE agendadas manualmente, especialmente se for conhecido que a atividade de atualização da tabela não afetará as estatísticas de colunas que interessam. O daemon agenda o comando ANALYZE estritamente em função do número de linhas inseridas ou atualizadas; não tem conhecimento se isto levará a mudanças estatísticas significativas.

As tuplas alteradas em partições e tabelas filhas por herança não acionam a análise na tabela mãe. Se a tabela mãe estiver vazia, ou for raramente alterada, ela nunca poderá ser processada pelo autovacuum, e as estatísticas da árvore de herança em seu todo não serão coletadas. É necessário executar o comando ANALYZE na tabela mãe manualmente para manter as estatísticas atualizadas.

Assim como na limpeza para recuperação de espaço, as atualizações frequentes de estatísticas são mais úteis para as tabelas muito atualizadas do que para as tabelas raramente atualizadas. Mas mesmo para uma tabela bastante atualizada, poderá não haver necessidade de atualizações de estatísticas, se a distribuição estatística dos dados não estiver mudando muito. Uma regra simples é pensar em quanto os valores mínimo e máximo das colunas na tabela mudam. Por exemplo, uma coluna do tipo de dados timestamp contendo a hora da atualização da linha, terá um valor máximo que aumenta constantemente à medida que as linhas são adicionadas e atualizadas; esta coluna precisará provavelmente de atualizações de estatísticas mais frequentes do que, digamos, uma coluna contendo URLs para páginas acessadas em um site. A coluna de URL poderá receber alterações com a mesma frequência, mas a distribuição estatística de seus valores mudará provavelmente de forma relativamente lenta.

É possível executar o comando ANALYZE em tabelas específicas, e até mesmo em colunas específicas de uma tabela, portanto existe a flexibilidade de atualizar algumas estatísticas com mais frequência do que outras, se a aplicação exigir. Na prática, porém, é geralmente melhor apenas analisar todo o banco de dados, porque é uma operação rápida. O comando ANALYZE usa uma amostragem estatisticamente aleatória das linhas da tabela, em vez de ler cada linha.

Dica

Embora o ajuste da frequência por coluna para o comando ANALYZE possa não ser muito produtivo, pode-se achar que vale a pena fazer o ajuste por coluna no nível de detalhe das estatísticas coletadas pelo comando ANALYZE. As colunas muito usadas em cláusulas WHERE, e com distribuições de dados muito irregulares, podem exigir um histograma de dados mais refinado do que outras colunas. Veja o comando ALTER TABLE SET STATISTICS, ou altere o valor padrão para todo o banco de dados usando o parâmetro de configuração default_statistics_target.

Além disso, por padrão, há informações limitadas disponíveis sobre a seletividade das funções. Entretanto, se for criado um objeto de estatísticas ou um índice de expressão que usa uma chamada de função, serão coletadas estatísticas úteis sobre a função, o que poderá melhorar muito os planos de consulta que usam o índice de expressão.

Dica

O daemon autovacuum não executa comandos ANALYZE para tabelas estrangeiras, porque não tem meios de determinar com que frequência isto pode ser útil. Se as consultas exigirem estatísticas em tabelas estrangeiras para um planejamento adequado, é uma boa ideia executar comandos ANALYZE gerenciados manualmente nessas tabelas em um cronograma adequado.

Dica

O daemon autovacuum não executa comandos ANALYZE para tabelas particionadas. As mães da herança só serão analisadas se a própria mãe for alterada – as alterações nas tabelas filhas não acionam a análise automática na tabela mãe. Se as consultas exigirem estatísticas nas tabelas mãe para um planejamento adequado, será necessário executar periodicamente o comando ANALYZE manualmente nessas tabelas para manter as estatísticas atualizadas.

24.1.4. Atualização do mapa de visibilidade #

O comando VACUUM mantém um mapa de visibilidade para cada tabela, para manter o controle de quais páginas contêm apenas tuplas conhecidas por serem visíveis para todas as transações ativas (e todas as transações futuras, até que a página seja novamente modificada). Isto tem duas finalidades. Em primeiro lugar, o próprio VACUUM poderá saltar estas páginas na próxima execução, porque não há nada para limpar.

Em segundo lugar, permite que o PostgreSQL responda algumas consultas usando apenas o índice, sem referência à tabela subjacente. Como os índices do PostgreSQL não contêm informações de visibilidade da linha, uma varredura de índice normal busca a linha da tabela para cada entrada de índice correspondente, para verificar se ela está visível pela transação corrente. Uma varredura de índice-apenas, por outro lado, verifica primeiro o mapa de visibilidade. Se for conhecido que todas as linhas na página estão visíveis, a busca da linha poderá ser evitada. Isto é mais útil em grandes conjuntos de dados em que o mapa de visibilidade pode evitar acessos ao disco. O mapa de visibilidade é muito menor do que a tabela, portanto pode ser facilmente armazenado em cache, mesmo quando a tabela for muito grande.

24.1.5. Prevenção de falhas de reinício de ID de transação #

A semântica de transações do controle de concorrência multiversão (MVCC) do PostgreSQL depende da capacidade de comparar números de ID de transação (XID): uma versão de linha com XID de inserção maior que o XID da transação corrente está no futuro, não devendo estar visível para a transação corrente. Mas como os IDs de transação têm tamanho limitado (32 bits), uma instância ativa há muito tempo (mais de 4 bilhões de transações) poderá sofrer um reinício do identificador de transação: o contador XID volta a zero e, de repente, as transações que estavam no passado parecem estar no futuro — significando que suas saídas se tornam invisíveis. Em resumo, uma perda de dados terrível. (Na verdade, os dados continuam lá, mas isto será de pouca serventia se não for possível obtê-los.) Para evitar esta situação, é necessário limpar todas as tabelas em todos os bancos de dados pelo menos uma vez a cada dois bilhões de transações.

A razão pela qual a limpeza periódica resolve este problema é que o comando VACUUM irá marcar as linhas como congeladas (frozen), indicando que foram inseridas por uma transação suficientemente antiga para que os efeitos dessa transação ainda possam ser visíveis pelas transações correntes e futuras. Os XIDs normais são comparados usando a aritmética de módulo-232. Isto significa que para cada XID normal, existem dois bilhões de XIDs que são mais antigos, e dois bilhões que são mais novos; outra maneira de dizer isto é que o espaço de XID normal é circular sem extremidade. Portanto, uma vez que uma versão de linha foi criada com um XID normal específico, a versão da linha parecerá estar no passado para os próximos dois bilhões de transações, independentemente de qual XID normal está se falando. Se a versão da linha ainda existir após mais de dois bilhões de transações, de repente parecerá estar no futuro. Para evitar esta situação, o PostgreSQL reserva um XID especial, o FrozenTransactionId, que não segue as regras normais de comparação de XID, sendo sempre considerado mais antigo que todo XID normal. As versões de linhas congeladas são tratadas como se o XID de inserção fosse FrozenTransactionId, para parecerem estar no passado para todas as transações normais, independentemente de problemas de reinício e, portanto, estas versões de linha serão válidas até serem excluídas, não importando quanto tempo isto demore.

Nota

Nas versões do PostgreSQL anteriores a 9.4, o congelamento era implementado substituindo o XID de inserção da linha por FrozenTransactionId, que era visível na coluna do sistema xmin da linha. As versões mais recentes apenas definem um bit sinalizador, preservando o xmin original da linha para possível uso forense. Entretanto, linhas com xmin igual a FrozenTransactionId (2) ainda podem ser encontradas em bancos de dados com pg_upgrade de versões anteriores à 9.4.

Além disso, os catálogos do sistema podem conter linhas com xmin igual a BootstrapTransactionId (1), indicando que foram inseridas durante a primeira fase do initdb. Assim como FrozenTransactionId, este XID especial é tratado como mais antigo que qualquer XID normal.

O parâmetro vacuum_freeze_min_age controla quão antigo um valor XID deve ser antes que as linhas com este XID sejam congeladas. Aumentar esta configuração poderá evitar trabalho desnecessário, se as linhas que de outra forma seriam congeladas forem modificadas novamente em breve, mas diminuir esta configuração aumenta o número de transações que podem ocorrer antes que a tabela seja limpa novamente.

O comando VACUUM usa o mapa de visibilidade para determinar quais páginas de uma tabela devem ser verificadas. Normalmente, o sistema ignora páginas que não possuem versões de linhas desatualizadas, mesmo que estas páginas ainda possam ter versões de linhas com valores de XID antigos. Portanto, comandos VACUUM normais nem sempre congelam todas as versões antigas das linhas na tabela. Quando isto acontece, o comando VACUUM eventualmente precisará executar um VACUUM agressivo, que congelará todos os valores de XID e MXID elegíveis que ainda não foram congelados, incluindo aqueles de todas as páginas visíveis, mas não inteiramente congeladas.

Se uma tabela estiver acumulando um número excessivo de páginas visíveis, mas não congeladas, uma operação de limpeza normal poderá optar por examinar páginas que podem ser ignoradas, na tentativa de congelá-las. Fazendo isto, diminui-se o número de páginas que a próxima limpeza agressiva terá que varrer. Estas são referidas como páginas varridas com avidez. A varredura com avidez pode ser ajustada para tentar congelar mais páginas inteiramente visíveis, aumentando vacuum_max_eager_freeze_failure_rate. Mesmo que a varredura com avidez tenha mantido o número de páginas visíveis mas não congeladas no mínimo, a maioria das tabelas ainda requer uma limpeza agressiva periódica. Entretanto, qualquer página congelada com sucesso por congelamento rápido pode ser ignorada durante um processo de limpeza agressiva, portanto o congelamento rápido pode minimizar a sobrecarga dos processos de limpeza agressiva.

vacuum_freeze_table_age controla quando uma tabela é limpa de forma agressiva. Todas as páginas visíveis, mas não inteiramente congeladas, serão varridas se o número de transações que ocorreram desde a última varredura for maior que vacuum_freeze_table_age menos vacuum_freeze_min_age. Definir vacuum_freeze_table_age como 0 força o VACUUM a sempre usar sua estratégia agressiva.

O tempo máximo que uma tabela pode ficar sem limpeza é de dois bilhões de transações menos o valor vacuum_freeze_min_age no momento da última limpeza agressiva. Se não for limpa por mais tempo do que isto, poderá ocorrer perda de dados. Para garantir que isto não aconteça, o daemon autovacuum é chamado em qualquer tabela que possa conter linhas não congeladas com XIDs anteriores à idade especificada pelo parâmetro de configuração autovacuum_freeze_max_age. (Isto acontecerá, mesmo se o autovacuum estiver inativo.)

Isto implica que, se uma tabela não for limpa de outra forma, o autovacuum será chamado para ela aproximadamente uma vez a cada transação autovacuum_freeze_max_age menos vacuum_freeze_min_age. Para as tabelas limpas regularmente para fins de recuperação de espaço, isto é de pouca importância. Entretanto, para as tabelas estáticas (incluindo tabelas que recebem inserções, mas não atualizações ou exclusões), não há necessidade de limpar para recuperação de espaço, portanto pode ser útil tentar maximizar o intervalo entre os autovacuum forçados em tabelas estáticas muito grandes. Obviamente, pode-se fazer isto aumentando autovacuum_freeze_max_age, ou diminuindo vacuum_freeze_min_age.

O valor máximo efetivo para vacuum_freeze_table_age é de 0,95 * autovacuum_freeze_max_age; uma configuração maior do que esta será limitada ao máximo. Um valor maior que autovacuum_freeze_max_age não faria sentido, porque o autovacuum anti-reinício seria acionado naquele ponto de qualquer maneira, e o multiplicador de 0,95 deixa algum espaço para respirar para executar o comando VACUUM manualmente antes que isto aconteça. Como regra geral, vacuum_freeze_table_age deve ser definido como um valor um pouco abaixo de autovacuum_freeze_max_age, deixando espaço suficiente para que o comando VACUUM agendado regularmente, ou o autovacuum disparado pela atividade normal de exclusão e atualização, seja executado neste intervalo. Defini-lo muito próximo pode levar a limpezas automáticas anti-reinício, mesmo que a tabela tenha sido limpa recentemente para recuperar espaço, enquanto valores mais baixos levam a uma limpeza agressiva mais frequente.

A única desvantagem de aumentar autovacuum_freeze_max_age (e vacuum_freeze_table_age ao mesmo tempo) é que os subdiretórios pg_xact e pg_commit_ts da instância ocuparão mais espaço, porque devem armazenar o status de efetivação, e (se track_commit_timestamp estiver ativo) o carimbo de data/hora de todas as transações, desde autovacuum_freeze_max_age. O status de efetivação usa dois bits por transação, portanto se autovacuum_freeze_max_age for definido com o seu valor máximo permitido de dois bilhões, então pg_xact poderá crescer para cerca de meio gigabyte, e pg_commit_ts para cerca de 20 GB. Se estes valores forem triviais em comparação com o tamanho total do banco de dados, é recomendável definir autovacuum_freeze_max_age com seu valor máximo permitido. Caso contrário, dever ser definido dependendo do que se deseja permitir para o armazenamento de pg_xact e de pg_commit_ts. (O valor padrão, 200 milhões de transações, se traduz em cerca de 50 MB de armazenamento para pg_xact, e cerca de 2 GB de armazenamento para pg_commit_ts.)

Uma desvantagem de diminuir vacuum_freeze_min_age é que poderá fazer com que o comando VACUUM faça um trabalho inútil: congelar uma versão de linha será perda de tempo se a linha for modificada logo em seguida (fazendo com que ela obtenha um novo XID). Portanto, a configuração deve ser grande o suficiente para que as linhas não sejam congeladas até que seja improvável que mudem mais.

Para rastrear a idade dos XIDs descongelados mais antigos em um banco de dados, o comando VACUUM armazena estatísticas de XID nas tabelas do sistema pg_class e pg_database. Em particular, a coluna relfrozenxid da linha pg_class de uma tabela contém o XID final do congelamento que foi usado pelo último comando VACUUM agressivo para esta tabela. Da mesma forma, a coluna datfrozenxid da linha pg_database de um banco de dados é um limite inferior para os XIDs não congelados que aparecem neste banco de dados — é simplesmente o valor mínimo entre os valores de relfrozenxid por tabela no banco de dados. Uma maneira prática de examinar estas informações é executar consultas como:

SELECT c.oid::regclass as nome_da_tabela,
       greatest(age(c.relfrozenxid),age(t.relfrozenxid)) as idade
FROM pg_class c
LEFT JOIN pg_class t ON c.reltoastrelid = t.oid
WHERE c.relkind IN ('r', 'm');

SELECT datname, age(datfrozenxid) FROM pg_database;

A coluna age mede o número de transações desde o XID de corte até o XID da transação corrente.

Dica

Quando é especificado o parâmetro VERBOSE do comando VACUUM, o comando VACUUM mostra várias estatísticas sobre a tabela. Isto inclui informações sobre como relfrozenxid e relminmxid avançaram, e o número de páginas recém-congeladas. Os mesmos detalhes aparecem no registro do servidor quando o registro do autovacuum (controlado por log_autovacuum_min_duration) relata uma operação de VACUUM executada pelo autovacuum.

Embora o comando VACUUM examine principalmente páginas que foram modificadas desde a última limpeza, também poderá varrer agressivamente algumas páginas inteiramente visíveis, mas não inteiramente congeladas, numa tentativa de congelá-las, mas o campo relfrozenxid só será avançado quando todas as páginas da tabela que possam conter XIDs descongelados forem verificadas. Isto ocorre quando o campo relfrozenxid é mais antigo que vacuum_freeze_table_age transações, quando é usada a opção FREEZE do comando VACUUM, ou quando todas as páginas que ainda não estão inteiramente congeladas precisam ser limpas para remover versões com linhas mortas. Quando o comando VACUUM examina todas as páginas da tabela que ainda não estão inteiramente congeladas, deve definir age(relfrozenxid) para um valor ligeiramente superior à configuração vacuum_freeze_min_age que foi utilizada (mais pelo número de transações iniciadas desde que o comando VACUUM foi iniciado). O comando VACUUM irá definir relfrozenxid para o XID mais antigo que permanecer na tabela, portanto é possível que o valor final seja muito mais recente do que o estritamente necessário. Se nenhum comando VACUUM com avanço de relfrozenxid for emitido na tabela até que autovacuum_freeze_max_age seja atingido, será forçado em breve para a tabela um autovacuum.

Se por algum motivo o autovacuum falhar ao limpar os XIDs antigos de uma tabela, o sistema começará a emitir mensagens de aviso como a seguinte quando os XIDs mais antigos do banco de dados atingirem quarenta milhões de transações antes do ponto de reinício:

WARNING:  database "mydb" must be vacuumed within 39985967 transactions
HINT:  To avoid XID assignment failures, execute a database-wide VACUUM in that database.

(Um comando VACUUM manual deve resolver o problema, conforme sugerido pela dica; mas note-se que o comando VACUUM deve ser executado por um superusuário, senão não conseguirá processar os catálogos do sistema, o que o impede de avançar o datfrozenxid do banco de dados.) Se estes avisos forem ignorados, o sistema se recusará a atribuir novos XIDs quando restarem menos de três milhões de transações até o reinício:

ERROR:  database is not accepting commands that assign new XIDs to avoid wraparound data loss in database "mydb"
HINT:  Execute a database-wide VACUUM in that database.

Nesta condição quaisquer transações já em andamento poderão continuar, mas somente transações de leitura poderão ser iniciadas. Operações que modificam registros do banco de dados ou truncam relações irão falhar. O comando VACUUM ainda poderá ser executado normalmente. Note-se que, ao contrário do que às vezes era recomendado em versões anteriores, não é necessário nem recomendável parar o postmaster, ou entrar no modo de usuário único, para restaurar a operação normal. Em vez disso, deve-se seguir os seguintes passos:

  1. Resolver transações preparadas antigas. Pode-se encontrá-las procurando as linhas de pg_prepared_xacts onde age(transactionid) seja grande. Estas transações devem ser efetivadas ou desfeitas.
  2. Terminar transações em aberto de longa duração. Pode-se encontrá-las procurando as linhas de pg_stat_activity onde age(backend_xid) ou age(backend_xmin) seja grande. Estas transações devem ser efetivadas ou desfeitas, ou a sessão pode ser encerrada usando pg_terminate_backend.
  3. Excluir todos os encaixes de replicação antigos. Deve-se usar pg_stat_replication para encontrar encaixes onde age(xmin) ou age(catalog_xmin) seja grande. Em muitos casos estes encaixes foram criados para replicação em servidores que não existem mais, ou que estão inativos há muito tempo. Se for removido um encaixe de um servidor que ainda existe e que ainda poderá tentar se conectar a este encaixe, esta réplica poderá precisar ser reconstruída.
  4. Executar VACUUM no banco de dados de destino. Um VACUUM para todo o banco de dados é o mais simples; para reduzir o tempo necessário, também é possível executar comandos manuais VACUUM nas tabelas onde relminxid é o mais antigo. Não deve ser usado VACUUM FULL neste cenário, porque requer um XID, e portanto irá falhar exceto no modo superusuário, onde, em vez disso, consumirá um XID e, portanto, aumentará o risco de estouro do ID da transação. Não se deve usar VACUUM FREEZE também, porque fará mais do que o mínimo necessário para restabelecer a operação normal.
  5. Após o restabelecimento da operação normal, deve-se certificar de que o autovacuum esteja configurado corretamente no banco de dados de destino para evitar problemas futuros.

Nota

Em versões anteriores às vezes era necessário parar o postmaster e executar o comando VACUUM no banco de dados em modo de usuário único. Em cenários típicos, isto não é mais necessário e deve ser evitado sempre que possível, porque envolve a desativação do sistema. Também é mais arriscado, porque desativa as proteções contra o reinício do ID da transação, que são projetadas para evitar a perda de dados. O único motivo para usar o modo de usuário único neste cenário é se for desejado usar o comando TRUNCATE ou DROP para excluir tabelas desnecessárias e evitar ter que usar o comando VACUUM para limpá-las. A margem de segurança de três milhões de transações existe para permitir que o administrador faça isto. Veja a página de referência postgres para obter detalhes sobre como usar o modo de usuário único.

24.1.5.1. Multixacts e reinício #

Os identificadores multixact (multiple transaction) são usados para oferecer suporte ao bloqueio de linha das transações múltiplas. Como existe apenas um espaço limitado no cabeçalho da tupla para armazenar informações de bloqueio, esta informação é codificada como um identificador de transação múltipla, ou ID multixact para abreviar, sempre que houver mais de uma transação bloqueando a mesma linha simultaneamente. As informações sobre quais identificadores de transação estão incluídos em qualquer ID multixact específico são armazenadas separadamente no subdiretório pg_multixact, e apenas o ID multixact aparece no campo xmax no cabeçalho da tupla. Assim como os identificadores de transação, os IDs multixact são implementados como um contador de 32 bits e o armazenamento correspondente, o que requer um cuidadoso gerenciamento de envelhecimento, limpeza de armazenamento, e tratamento de reinício. Existe uma área de armazenamento separada que contém a lista de membros em cada identificador multixact, que também usa um contador de 32 bits, e que também deve ser gerenciado. A função do sistema pg_get_multixact_members() descrita em Tabela 9.84 pode ser usada para examinar os IDs de transação associados a um ID de multixact.

Sempre que o comando VACUUM varre qualquer parte de uma tabela, ele irá substituir qualquer ID multixact que encontrar que seja mais antigo que vacuum_multixact_freeze_min_age por um valor diferente, que poderá ser o valor zero, um único identificador de transação, ou um ID multixact mais novo. Para cada tabela, pg_class.relminmxid armazena o ID multixact mais antigo possível ainda aparecendo em qualquer tupla dessa tabela. Se este valor for mais antigo que vacuum_multixact_freeze_table_age, é forçada uma limpeza agressiva. Conforme discutido na seção anterior, uma limpeza agressiva significa que apenas as páginas que estão inteiramente congeladas serão ignoradas. Pode ser usada a função mxid_age() em pg_class.relminmxid para encontrar sua idade.

Varreduras agressivas do comando VACUUM, independentemente do que as cause, garantem conseguir avançar o relminmxid da tabela. Por fim, à medida que todas as tabelas em todos os bancos de dados são varridas e seus valores de multixact mais antigos são avançados, o armazenamento em disco dos multixacts mais antigos pode ser removido.

Como mecanismo de segurança, uma varredura de limpeza agressiva será realizada em qualquer tabela cuja idade do multixact seja superior a autovacuum_multixact_freeze_max_age. Além disso, se o armazenamento ocupado por membros de multixacts exceder cerca de 10 GB, varreduras agressivas de limpeza ocorrerão com mais frequência para todas as tabelas, começando por aquelas que possuem a idade de multixact mais antiga. Ambos os tipos de varreduras agressivas ocorrerão mesmo que o autovacuum esteja nominalmente desativado. A área de armazenamento dos membros pode crescer até cerca de 20 GB antes de alcançar o reinício (wraparound).

Assim como no caso do XID, se o autovacuum não conseguir limpar os MXIDs antigos de uma tabela, o sistema começará a emitir mensagens de aviso quando os MXIDs mais antigos do banco de dados atingirem quarenta milhões de transações antes do ponto de reinício. E, assim como no caso do XID, se estes avisos forem ignorados o sistema se recusará a gerar novos MXIDs quando restarem menos de três milhões até o reinício.

A operação normal, quando os MXIDs se esgotam, pode ser restaurada de maneira muito semelhante àquela utilizada quando os XIDs se esgotam. Devem ser seguidas as mesmas etapas da seção anterior, mas com as seguintes diferenças:

  1. Transações em execução e transações preparadas podem ser ignoradas se não houver possibilidade de aparecerem em uma multixact.
  2. As informações de MXID não são diretamente visíveis em visões do sistema como pg_stat_activity; entretanto, procurar por XIDs antigos ainda é uma boa maneira de determinar quais transações estão causando problemas de reinício de MXID.
  3. O esgotamento de XID bloqueará todas as transações de escrita, mas o esgotamento de MXID bloqueará apenas um subconjunto de transações de escrita — especificamente aquelas que envolvem bloqueios de linha que requerem um MXID.

24.1.6. Processo de limpeza automática #

O PostgreSQL possui um recurso opcional, mas altamente recomendado, chamado autovacuum, cuja finalidade é automatizar a execução dos comandos VACUUM e ANALYZE. Quando ativado, o autovacuum verifica as tabelas que tiveram muitas de tuplas inseridas, atualizadas, ou excluídas. Estas verificações usam o recurso de coleta de estatísticas; portanto, o autovacuum não poderá ser usado a menos que track_counts esteja definido como on. Na configuração padrão, a limpeza automática é ativada, e os parâmetros de configuração relacionados são definidos adequadamente.

O daemon autovacuum, na verdade, consiste em vários processos. Existe um processo daemon persistente, chamado de autovacuum launcher, que se encarrega de iniciar o processo autovacuum worker para todos os bancos de dados. O processo lançador distribui o trabalho ao longo do tempo, tentando iniciar um processo trabalhador em cada banco de dados a cada autovacuum_naptime segundos. (Portanto, se a instância tiver N bancos de dados, será iniciado um novo processo trabalhador a cada autovacuum_naptime/N segundos.) No máximo autovacuum_max_workers processos trabalhadores podem ser executados ao mesmo tempo. Havendo mais de autovacuum_max_workers bancos de dados a serem processados, o próximo banco de dados será processado assim que o primeiro processo trabalhador terminar. Cada processo trabalhador irá verificar cada tabela em seu banco de dados, e executar o comando VACUUM e/ou ANALYZE conforme necessário. O parâmetro log_autovacuum_min_duration pode ser configurado para monitorar a atividade dos processos trabalhadores do autovacuum.

Se várias tabelas grandes se tornarem elegíveis para limpeza num curto espaço de tempo, todos os processos trabalhadores do autovacuum poderão ficar ocupados com a limpeza dessas tabelas por um longo período. Isto resultaria em outras tabelas e bancos de dados não sendo limpos até que um processo trabalhador se torne disponível. Não há limite de quantos processos trabalhadores podem estar em um único banco de dados, mas os processos trabalhadores tentam evitar a repetição do trabalho que já foi feito por outros processos trabalhadores. Note que o número de processos trabalhadores em execução não conta para os limites de max_connections ou superuser_reserved_connections.

As tabelas cujo valor de relfrozenxid é maior que autovacuum_freeze_max_age transações antigas são sempre limpas (isto também se aplica àquelas tabelas cuja idade máxima de congelamento foi modificada por meio de parâmetros de armazenamento; veja abaixo). Caso contrário, se o número de tuplas obsoletadas desde o último comando VACUUM exceder o limite de limpeza, a tabela será limpa. O limite de limpeza é definido como:

limite da limpeza = Mínimo(
    limite máximo da limpeza,
    limite base da limpeza +
    fator de escala da limpeza * número de tuplas)
-- original
vacuum threshold = Minimum(
    vacuum max threshold,
    vacuum base threshold +
    vacuum scale factor * number of tuples)

onde limite máximo da limpeza (vacuum max threshold) é autovacuum_vacuum_max_threshold, limite base da limpeza (vacuum base threshold) é autovacuum_vacuum_threshold, fator de escala da limpeza (vacuum scale factor) é autovacuum_vacuum_scale_factor, e o número de tuplas é pg_class.reltuples.

A tabela também passará pelo processo de limpeza se o número de tuplas inseridas desde a última limpeza tiver excedido o limite de inserção definido, o qual é definido como:

limite de inserção da limpeza =
    limite de inserção base da limpeza +
    fator de escala de inserção da limpeza * número de tuplas
-- original
vacuum insert threshold =
    vacuum base insert threshold +
    vacuum insert scale factor * number of tuples

onde o limite de inserção base da limpeza (vacuum insert base threshold) é autovacuum_vacuum_insert_threshold, o fator de escala de inserção da limpeza (vacuum insert scale factor) é autovacuum_vacuum_insert_scale_factor, e o número de tuplas é pg_class.reltuples, e a porcentagem da tabela que não está congelada é 1 - pg_class.relallfrozen / pg_class.relpages. Tais operações de limpeza podem permitir que partes da tabela sejam marcadas como inteiramente visíveis e também permitir que tuplas sejam congeladas, o que pode reduzir o trabalho necessário em operações de limpeza subsequentes. Para tabelas que recebem operações INSERT, mas nenhuma ou quase nenhuma operação de UPDATE ou DELETE, poderá ser benéfico reduzir o autovacuum_freeze_min_age da tabela, porque isto pode permitir que as tuplas sejam congeladas por operações de limpeza realizadas mais cedo. O número de tuplas obsoletas e o número de tuplas inseridas são obtidos a partir do sistema de estatísticas cumulativas; é uma contagem com consistência eventual, atualizada por cada operação de UPDATE, DELETE e INSERT. Se o valor de relfrozenxid da tabela tiver mais transações do que o especificado em vacuum_freeze_table_age, será realizada uma limpeza agressiva para congelar tuplas antigas e avançar o relfrozenxid.

Para a análise, é usada uma condição semelhante: o limite, definido como

limite da análise =
    limite base da análise +
    fator de escala da análise * número de tuplas
-- original
analyze threshold =
    analyze base threshold +
    analyze scale factor * number of tuples

é comparado com o número total de tuplas inseridas, atualizadas ou excluídas desde o último ANALYZE.

As tabelas particionadas não armazenam tuplas diretamente e, consequentemente, não são processadas pelo autovacuum. (O autovacuum processa partições de tabela da mesma forma que outras tabelas.) Infelizmente, isto significa que o autovacuum não executa o comando ANALYZE em tabelas particionadas, e isto pode resultar em planos abaixo do ideal para consultas que fazem referência a estatísticas de tabelas particionadas. Pode-se contornar este problema executando manualmente o comando ANALYZE em tabelas particionadas quando elas são populadas pela primeira vez e, novamente, sempre que a distribuição de dados em suas partições mudar significativamente.

As tabelas temporárias não podem ser acessadas pelo autovacuum. Portanto, as operações apropriadas de limpeza e análise devem ser executadas por meio de comandos SQL na sessão.

Os limites padrão e fatores de escala são obtidos do arquivo postgresql.conf, mas é possível sobrepô-los (e muitos outros parâmetros de controle de limpeza automático) por tabela; veja Parâmetros de armazenamento para obter mais informações. Se a configuração foi alterada por meio dos parâmetros de armazenamento da tabela, este valor será usado ao processar esta tabela; caso contrário, serão usadas as configurações globais. Veja Limpeza automática para obter mais informações sobre as configurações globais.

Quando vários processos trabalhadores estão em execução, os parâmetros de atraso por custo do autovacuum (veja Atraso do VACUUM baseado em custos) são balanceados entre todos os processos trabalhadores em execução, de modo que o impacto total de E/S no sistema seja o mesmo, independentemente do número de processos trabalhadores realmente em execução. Entretanto, quaisquer processos trabalhadores processando tabelas, cujos parâmetros de armazenamento autovacuum_vacuum_cost_delay ou autovacuum_vacuum_cost_limit foram definidos por tabela, não serão considerados no algoritmo de balanceamento.

Os processos trabalhadores de autovacuum geralmente não bloqueiam outros comandos. Se um processo tentar adquirir um bloqueio que entre em conflito com o bloqueio SHARE UPDATE EXCLUSIVE mantido pelo autovacuum, a aquisição do bloqueio interromperá o autovacuum. Para os modos de bloqueio conflitantes, veja a Tabela 13.2. Entretanto, se o autovacuum estiver em execução para evitar o reinício do identificador da transação (ou seja, o nome da consulta autovacuum na visão pg_stat_activity terminar com (para evitar reinício automático)), o autovacuum não será interrompido automaticamente.

Atenção

A execução regular de comandos que adquirem bloqueios conflitantes com o bloqueio SHARE UPDATE EXCLUSIVE (por exemplo, o comando ANALYZE), pode impedir que os autovacuum sejam concluídos.