O que é o MVCC
Uma técnica para controle de versão das tuplas (linhas) de uma tabela, conhecida como MVCC no PostgreSQL e gerenciamento de XIDs. Nesse contexto, o PostgreSQL utiliza esta técnica como forma de melhorar a performance dos processos.
Além disso, o controle de multiversão faz com que o sistema, ao atualizar uma linha, não a sobrescreva, mas crie uma nova versão. Para isso, os XIDs (Transaction IDs), atribuídos a cada tupla, controlam cada uma dessas versões.
Por fim, quando uma consulta é executada em uma tabela, ela acessa um snapshot dessa tabela no momento do início da transação, independentemente de possíveis alterações que a tabela esteja sofrendo. Dessa forma, os leitores nunca bloqueiam nem sofrem bloqueio por outros leitores ou escritores.
Entendendo melhor os XIDs
MVCC no PostgreSQL e gerenciamento de XIDs
Cada tabela possui campos ocultos para controle das transações. Os mais importantes e que exploraremos hoje são os seguintes:

xmin: neste campo está o id da transação que criou esta versão da tupla.
xmax: neste está o id da transação que atualizou esta versão da tupla.
Visualizaremos melhor este comportamento através de uma tabela de exemplo.
Aqui estão todos os dados da nossa tabela:

Se executarmos um SELECT na tabela, incluindo estes dois campos de XIDs, poderemos verificar que cada tupla tem os dois XIDs mencionados:

Agora, para facilitar a nossa compreensão, criamos a extensão “pageinspect”. Esta extensão nos ajudará a visualizar informações também das tuplas mortas (linhas que sofreram modificações e não estão mais disponíveis para consultas). Neste momento, realizando a seguinte consulta, temos o resultado:

Faremos algumas atualizações no campo “salary” e visualizaremos o que esta última consulta retorna:

O que verificamos aqui:
- As tuplas de t_max=0 são as tuplas que estão disponíveis para visualização, a versão atualizada dessas linhas. As demais, portanto, são versões antigas;
- A tupla 1 foi atualizada pela transação de id 776, mesma transação que criou a tupla 4;
- As tuplas 2 e 3 foram atualizadas pelo xid 777, o mesmo que criou as tuplas 5 e 6.
Limite de XIDs
Podemos dizer que, inicialmente, o PostgreSQL organiza os XIDs em duas partes iguais, ficando uma parte para XIDs passados e outra para futuros. Ao todo, o limite é de aproximadamente 4,2 bilhões. Quando o PostgreSQL chega a esse limite, então ele reinicia a contagem, reutilizando os XIDs antigos. Entretanto, caso esses XIDs antigos ainda estejam associados a linhas, o PostgreSQL perde o controle do que é passado e futuro, o que acaba causando uma parada do cluster para novas operações de escrita.
Para evitar esse problema, o PostgreSQL utiliza um mecanismo de freeze (congelamento) de XIDs, no qual ele substitui o xmin por um ID padrão, que o próprio PostgreSQL interpreta como se nenhum ID fosse mais antigo do que este.
Parâmetros no PostgreSQL
Os parâmetros no PostgreSQL que controlam o freeze de XIDs antigos são, principalmente:
vacuum_freeze_min_age: este parâmetro indica “idade mínima” para o congelamento da tupla. Por exemplo, este parâmetro vem configurado com o valor de 50 milhões, o que sinaliza ao PostgreSQL que uma linha deve ser congelada somente se ela tiver “sobrevivido” 50 milhões de transações.
vacuum_freeze_table_age: é o gatilho para o vacuum percorrer toda a tabela. Esse parâmetro é importante porque o vacuum pula páginas que não têm versões de linhas mortas, mesmo que haja linhas com versões com XIDs antigos. Quando a tabela atinge este número de transações, o vacuum passa a escanear toda a tabela, página a página, para congelar as tuplas antigas.
autovacuum_freeze_max_age: este é o parâmetro mais crítico, o limite de segurança do PostgreSQL. Seu valor padrão é de 200 milhões, o que significa que quando uma tabela atinge 200 milhões de transações sem um freeze completo, o PostgreSQL dispara um Autovacuum Anti-wraparound. Este vacuum é agressivo, prioritário, e disparado mesmo que o Autovacuum esteja desabilitado.
É importante considerar que os parâmetros de vacuum e vacuum analyze são parâmetros que também impactam no freeze de XIDs, pois é baseado nestes que o Autovacuum “padrão” é disparado.
Algumas dicas
Manter o Autovacuum habilitado: o Autovacuum tem um papel fundamental no gerenciamento das tuplas e dos XID’s, evitando problemas relacionados ao limite de XID’s.
Monitorar a idade dos XID’s: use a seguinte query para verificar quantas transações faltam para que o autovacuum_freeze_max_age seja atingido:

Ela retornará resultados como este. A coluna em destaque mostra o número de transações que faltam para chegar no número de 200 milhões (padrão do autovacuum_freeze_max_age):

Se uma tabela ultrapassar 100–150 milhões de transações, nesse caso, isso é um sinal para investigar se o vacuum está realmente completando seu trabalho e se alguma ação adicional será necessária.
Além disso, é importante ter atenção com longas transações: o Autovacuum só realiza o congelamento de tuplas que não são mais necessárias para nenhuma transação ativa. Nessa situação, se houver um BEGIN esquecido aberto ou um dump muito longo, os XIDs não serão passíveis de sofrerem freeze.
Por fim, esperamos que estas explicações sobre MVCC no PostgreSQL e gerenciamento de XIDs sejam esclarecedoras e úteis para a administração do seu banco de dados PostgreSQL.
Por Jonas Natario