Na última vez, nos encontramos com o dispositivo de um dos objetos importantes da memória compartilhada, o cache do buffer. A possibilidade de perda de informações da RAM é o principal motivo da necessidade de recuperação de uma falha. Hoje falaremos sobre essas ferramentas.
Magazine
Infelizmente, milagres não acontecem: para sobreviver à perda de informações na RAM, tudo o que é necessário deve ser gravado em um disco (ou outro dispositivo não volátil) em tempo hábil.
Portanto, é isso que foi feito. Juntamente com as alterações de dados, também é mantido um
diário dessas alterações. Quando alteramos algo em uma página no cache do buffer, criamos um registro no log sobre essa alteração. O registro contém as informações mínimas suficientes para que, se necessário, a alteração possa ser repetida.
Para que isso funcione, a entrada do diário deve necessariamente ir para o disco
antes que a página modificada chegue lá. Daí o nome: log write-ahead.
Se ocorrer uma falha, os dados no disco estarão em um estado inconsistente: algumas páginas foram gravadas anteriormente, outras mais tarde. Porém, resta um diário que pode ser lido e reexecutado pelas operações que já foram concluídas antes da falha, mas cujo resultado não atingiu o disco.
Por que não forçar as próprias páginas de dados a serem gravadas em disco, por que duplicar os trabalhos? Acontece tão eficaz.
Primeiro de tudo, um log é um fluxo seqüencial de dados para gravação. Até os HDDs se dão muito bem com a gravação seqüencial. Mas o registro dos dados em si é aleatório, porque as páginas estão espalhadas pelo disco mais ou menos aleatoriamente.
Em segundo lugar, uma entrada no diário pode ser muito menor que uma página.
Em terceiro lugar, ao gravar, você não precisa se preocupar em garantir que os dados no disco permaneçam consistentes em qualquer momento arbitrário (esse requisito complica muito a vida).
E quarto, como veremos mais adiante, o diário (como existe) pode ser usado não apenas para recuperação, mas também para backup e replicação.
Você precisa registrar todas as operações, durante as quais há risco de inconsistência no disco no caso de uma falha. Em particular, as seguintes ações são registradas:
- alterar páginas no cache do buffer (como regra, são tabelas e páginas de índice) - já que a página alterada não entra imediatamente no disco;
- confirmar e cancelar transações - a alteração de status ocorre nos buffers XACT e também não atinge imediatamente o disco;
- operações de arquivo (criação e exclusão de arquivos e diretórios, por exemplo, criação de arquivos ao criar uma tabela) - já que essas operações devem ocorrer simultaneamente com as alterações de dados.
Não registrado:
- operações com tabelas não registradas no diário (não registradas) - seu nome fala por si;
- operações com tabelas temporárias - não faz sentido, pois o tempo de vida dessas tabelas não excede o tempo de vida da sessão que as criou.
Antes do PostgreSQL 10, os
índices de hash não
eram registrados (eles serviam apenas para mapear funções de hash para diferentes tipos de dados), mas agora isso foi corrigido.
Dispositivo lógico

Logicamente, um diário pode ser pensado como uma sequência de registros de vários comprimentos. Cada registro contém
dados sobre uma determinada operação, precedida por um
cabeçalho padrão. O título, entre outras coisas, indica:
- O número da transação à qual o registro pertence.
- gerenciador de recursos - o componente do sistema responsável pela gravação;
- soma de verificação (CRC) - permite determinar a corrupção de dados;
- comprimento do registro e link para o registro anterior.
Os dados em si têm um formato e significado diferentes. Por exemplo, eles podem representar algum fragmento de uma página que precisa ser gravada sobre seu conteúdo com um determinado deslocamento. O gerenciador de recursos especificado "entende" como interpretar os dados em seu registro. Existem gerenciadores separados para tabelas, para cada tipo de índice, para status de transação, etc. Uma lista completa deles pode ser obtida se desejado pelo comando
pg_waldump -r list
Dispositivo físico
No disco, o log é armazenado como arquivos no diretório $ PGDATA / pg_wal. Cada arquivo padrão é 16 MB. O tamanho pode ser aumentado para evitar um grande número de arquivos em um diretório. Antes do PostgreSQL 11, isso só podia ser feito ao compilar o código-fonte, mas agora você pode especificar o tamanho ao inicializar o cluster (a
--wal-segsize
).
As entradas de log caem no arquivo atual em uso; quando termina, o próximo começa a ser usado.
Buffers especiais são alocados para o log na memória compartilhada do servidor. O tamanho do cache do diário é definido pelo parâmetro
wal_buffers (o valor padrão implica na configuração automática: 1/32 do cache do buffer é alocado).
O cache do diário é organizado como um cache de buffer, mas funciona principalmente no modo de buffer de anel: as entradas são adicionadas à "cabeça" e gravadas no disco a partir da "cauda".
As posições de gravação ("cauda") e inserção ("cabeça") mostram as funções pg_current_wal_lsn e pg_current_wal_insert lsn, respectivamente:
=> SELECT pg_current_wal_lsn(), pg_current_wal_insert_lsn();
pg_current_wal_lsn | pg_current_wal_insert_lsn --------------------+--------------------------- 0/331E4E64 | 0/331E4EA0 (1 row)
Para se referir a um registro específico, é usado o tipo de dados pg_lsn (LSN = número de sequência do log) - este é um número de 64 bits que representa o deslocamento de bytes antes do registro em relação ao início do log. O LSN é emitido como dois números de 32 bits na notação hexadecimal.
Você pode descobrir em qual arquivo vamos encontrar a posição desejada e com qual deslocamento desde o início do arquivo:
=> SELECT file_name, upper(to_hex(file_offset)) file_offset FROM pg_walfile_name_offset('0/331E4E64');
file_name | file_offset --------------------------+------------- 000000010000000000000033 | 1E4E64 \ /\ / 0/331E4E64
O nome do arquivo consiste em duas partes. Os 8 dígitos hexadecimais superiores mostram o número da ramificação de tempo (é usado ao restaurar a partir do backup), o restante corresponde aos dígitos LSN mais altos (e os dígitos LSN inferiores restantes indicam o deslocamento).
Os arquivos de log podem ser visualizados no sistema de arquivos no diretório $ PGDATA / pg_wal /, mas a partir do PostgreSQL 10, eles também podem ser vistos com uma função especial:
=> SELECT * FROM pg_ls_waldir() WHERE name = '000000010000000000000033';
name | size | modification --------------------------+----------+------------------------ 000000010000000000000033 | 16777216 | 2019-07-08 20:24:13+03 (1 row)
Gravação para frente
Vamos ver como ocorre o registro no diário e como a gravação proativa é fornecida. Crie uma tabela:
=> CREATE TABLE wal(id integer); => INSERT INTO wal VALUES (1);
Veremos o cabeçalho da página da tabela. Para fazer isso, precisamos de uma extensão já familiar:
=> CREATE EXTENSION pageinspect;
Vamos iniciar a transação e lembrar a posição de inserção no log:
=> BEGIN; => SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 0/331F377C (1 row)
Agora vamos fazer alguma operação, por exemplo, atualizar a linha:
=> UPDATE wal set id = id + 1;
Essa alteração foi registrada no log, a posição de inserção foi alterada:
=> SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 0/331F37C4 (1 row)
Para garantir que a página de dados modificada não seja enviada para o disco antes do lançamento no diário, o LSN da última entrada no diário relacionado a esta página será armazenado no cabeçalho da página:
=> SELECT lsn FROM page_header(get_raw_page('wal',0));
lsn ------------ 0/331F37C4 (1 row)
Lembre-se de que o diário é comum a todo o cluster, e novas entradas entram nele o tempo todo. Portanto, o LSN na página pode ser menor que o valor que a função pg_current_wal_insert_lsn acabou de retornar. Mas nada acontece no nosso sistema, então os números são os mesmos.
Agora complete a transação.
=> COMMIT;
O registro de confirmação também vai para o log e a posição muda novamente:
=> SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 0/331F37E8 (1 row)
A confirmação altera o status de uma transação em uma estrutura chamada XACT (
já falamos sobre isso ). Os status são armazenados em arquivos, mas eles também usam seu próprio cache, que ocupa 128 páginas na memória compartilhada. Portanto, para páginas XACT, o LSN da última entrada no diário deve ser rastreado. Mas essas informações não são armazenadas na própria página, mas na RAM.
Em algum momento, as entradas do diário criadas serão gravadas no disco. Em qual - falaremos outra vez, mas no nosso caso isso já aconteceu:
=> SELECT pg_current_wal_lsn(), pg_current_wal_insert_lsn();
pg_current_wal_lsn | pg_current_wal_insert_lsn --------------------+--------------------------- 0/331F37E8 | 0/331F37E8 (1 row)
Após esse ponto, as páginas de dados e XACT podem ser enviadas para fora do cache. Mas, se fosse necessário forçá-los a sair mais cedo, seria detectado e os lançamentos no diário seriam forçados a serem registrados primeiro.
Conhecendo as duas posições do LSN, é possível obter o tamanho dos lançamentos contábeis manuais entre eles (em bytes), subtraindo uma posição da outra. Você só precisa converter as posições para o tipo pg_lsn:
=> SELECT '0/331F37E8'::pg_lsn - '0/331F377C'::pg_lsn;
?column? ---------- 108 (1 row)
Nesse caso, a atualização e confirmação da linha exigiam 108 bytes no log.
Da mesma forma, você pode estimar quanto os lançamentos contábeis manuais são gerados pelo servidor por unidade de tempo em uma determinada carga. Esta é uma informação importante que será necessária durante a instalação (sobre a qual falaremos na próxima vez).
Agora usaremos o utilitário pg_waldump para examinar as entradas de log criadas.
O utilitário pode trabalhar com o intervalo LSN (como neste exemplo) e selecionar registros para a transação especificada. Ele deve ser executado em nome do usuário do SO do postgres, pois ele precisa acessar os arquivos de log no disco.
postgres$ /usr/lib/postgresql/11/bin/pg_waldump -p /var/lib/postgresql/11/main/pg_wal -s 0/331F377C -e 0/331F37E8 000000010000000000000033
rmgr: Heap len (rec/tot): 69/ 69, tx: 101085, lsn: 0/331F377C, prev 0/331F3014, desc: HOT_UPDATE off 1 xmax 101085 ; new off 2 xmax 0, blkref #0: rel 1663/16386/33081 blk 0
rmgr: Transaction len (rec/tot): 34/ 34, tx: 101085, lsn: 0/331F37C4, prev 0/331F377C, desc: COMMIT 2019-07-08 20:24:13.945435 MSK
Aqui vemos os cabeçalhos das duas entradas.
A primeira é a operação
HOT_UPDATE , relacionada ao gerenciador de recursos Heap. O nome do arquivo e o número da página são indicados no campo blkref e correspondem à página da tabela atualizada:
=> SELECT pg_relation_filepath('wal');
pg_relation_filepath ---------------------- base/16386/33081 (1 row)
A segunda entrada é COMMIT, relacionada ao Transaction Resource Manager.
Não é o formato mais legível, mas você pode descobrir se necessário.
Recuperação
Quando iniciamos o servidor, o processo do postmaster inicia primeiro e, por sua vez, inicia o processo de inicialização, cuja tarefa é garantir a recuperação se ocorrer uma falha.
Para determinar se a recuperação é necessária, a inicialização examina o arquivo de controle especial $ PGDATA / global / pg_control e o status do cluster. Podemos verificar o status usando o utilitário pg_controldata:
postgres$ /usr/lib/postgresql/11/bin/pg_controldata -D /var/lib/postgresql/11/main | grep state
Database cluster state: in production
Um servidor perfeitamente parado terá o status "desligado". Se o servidor não funcionar e o status permanecer "em produção", isso significa que o DBMS caiu e a recuperação será executada automaticamente.
Para recuperação, o processo de inicialização lerá sequencialmente o log e aplicará entradas nas páginas, se necessário. Você pode verificar a necessidade comparando o LSN da página no disco com o LSN da entrada do diário. Se o LSN da página for maior, o registro não será necessário. Mas, na verdade, isso nem é possível, porque os registros foram projetados para aplicativos estritamente consistentes.
Há exceções. Alguns registros são formados como uma imagem de página inteira (FPI, imagem de página inteira), e é claro que essa imagem pode ser aplicada a uma página em qualquer estado - ela ainda apagará tudo o que estava lá. Outra alteração no status da transação pode ser aplicada a qualquer versão da página XACT - portanto, nessas páginas, não há necessidade de armazenar LSN.
A alteração de páginas durante a recuperação ocorre no cache do buffer, como durante o trabalho normal - pois este postmaster inicia os processos em segundo plano necessários.
Da mesma forma, as entradas do diário se aplicam aos arquivos: por exemplo, se um registro diz que o arquivo deve existir, mas não existe, o arquivo é criado.
Bem, no final do processo de recuperação, todas as tabelas não registradas em diário são substituídas por "manequins" de suas
camadas init .
Esta é uma apresentação muito simplificada do algoritmo. Em particular, não dissemos nada sobre por onde começar a ler os lançamentos no diário (essa conversa deverá ser adiada até que o ponto de verificação seja considerado).
E o último esclarecimento. O processo de recuperação "clássico" consiste em duas fases. Na primeira fase (roll forward), as entradas do diário são roladas e o servidor repete todo o trabalho perdido durante a falha. No segundo (reversão), as transações que não foram confirmadas no momento da falha são revertidas. Mas o PostgreSQL não precisa de uma segunda fase. Como
consideramos anteriormente , devido às peculiaridades da implementação de transações com várias versões, não é necessário reverter fisicamente; basta que o bit de correção não seja definido no XACT.
Para ser continuado .