postgres postgresql WAL

WAL

O WAL (Write Ahead Log) é um log de transação, necessário para garantir a atomicidade e durabilidade das transações mesmo após falhas.

O log de transação pode ser visto como uma longa sequência de alterações causadas pela aplicação sobre os arquivos do banco de dados ao longo de uma linha do tempo. Essa linha do tempo e as alterações contidas nela são endereçadas pelo LSN (Log Sequence Number), que é um número monotonicamente crescente que indica a posição em bytes desde o início do log.

Para fins de durabilidade e recuperação de desastres, o WAL é armazenado como um buffer circular em disco no diretório pg_wal em segmentos (arquivos de 16MB, mas o tamanho pode ser configurado por initdb) que são rotacionados à medida que deixam de ser necessários.

Eles são necessários enquanto o estado consistente do cluster, indicado por um LSN contido em pg_control, estiver no seu futuro.

O ciclo de vida simplificado do WAL é o seguinte:

  1. Aplicação faz leituras e escritas nos bancos de dados através de DML.
  2. Alterações nos bancos de dados mudam blocos em disco.
  3. Mudanças dos blocos são escritas no WAL avançando o LSN de escrita (a), como no segmento …014. Elas também são enviadas para as réplicas (b).
  4. Segmentos de WAL são preenchidos e fechados, como o segmento …013 foi recentemente completado. Eles também podem ser arquivados para restauração de backups e réplicas atrasadas.
  5. Quando os segmentos de WAL não são mais alcançáveis por estarem no passado do LSN de estado de consistência (d), eles são reciclados, ou seja, limpos e renomeados, como o segmento …012, que se tornará …015 no futuro próximo.
                                   ╔═══════════╗↔──────────────────┐
                                   ║ Aplicação ║╗↔─────────────────┤
                                   ╚═══════════╝║╗↔────────────────┤
                                    ╚═══════════╝║↔────────────────┤
                                     ╚═══════════╝↔────────────────┤
                                                                (1)│
                         ┌──────────┐                              │
╔════════════════════════╡PostgreSQL╞══════════════════════════╗   │
║                        └──────────┘                          ║   │
╠═╡global/╞═══════╗                     ╔═╡base/╞══════════════╣   │
║                 ║                     ║                      ║   │
║     O     O     ║                     ║ ┏━┥banco de dados┝━┓ ║   │
║    ─┼─   ─┼─    ║                 (2) ║ ┃ ┌─┤schema├─────┐ ┃ ║   │
║     │     │     ║                  ┊←┈╢ ┃ │ ☑ tabelas    │←╂─╫───┤
║    ╱ ╲   ╱ ╲    ║                  ┊←┈╢ ┃ │ ☑ índices    │←╂─╫───┤
║ ☑ usuários      ║                  ┊←┈╢ ┃ │ ☑ visões     │←╂─╫───┤
║ ☑ grupos        ║                  ┊←┈╢ ┃ │ ☑ sequências │←╂─╫───┤
║ ☑ slots de      ║                  ┊←┈╢ ┃ │ ☑ …          │←╂─╫───┘
║ ☑ replicação    ║                  ┊  ║ ┃ └──────────────┘ ┃ ║
║ ☑ pg_control┅┅┅┅╫┅┅┓               ┊  ║ ┗━━━━━━━━━━━━━━━━━━┛ ║
║                 ║  ┋               ┊  ║                      ║
╠═════════════════╝  ┋               ┊  ╚══════════════════════╣
║                    ┋            (3)↓                         ║
╠═╡pg_wal/╞══════════╪═══════════════╪═══════════╗     (b)     ║
║        (5)      (d)┋               ┊┈┈┈┈┈┈┈┈┈┈→╫→┈┈┈┈┈┈┈┈┈┈┈→╫→┈┈┈
║  ┌────────────┐ ┌──↓─────────┐  (a)↓           ║             ║
║ ╍│+--++--+--++│╍│-++++-++---+│╍╷++-.........╷╍ ║     (c)     ║
║  └┤/…00000012├┘ └┤/…00000013├┘↓└┤/…00000014├┘  ║  ┌───────┐  ║
║                            (4)└┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈→╫→┈│…++---+│┈→╫→┈┈┈
║                                                ║  └┤/…013├┘  ║
╚════════════════════════════════════════════════╩═════════════╝

LSN e pg_wal

O LSN é um número grande que aponta para bytes dentro da linha do tempo contida nos segmentos, então é natural que o nome do arquivo seja construído a partir do LSN. Contudo, o nome do arquivo não precisa conter os bits menos significativos do endereço (aqueles que apontam para posições dentro do próprio arquivo).

Por outro lado, é desejável que ele contenha uma indicação da linha do tempo que o cluster segue, assim evita-se a aplicação de segmentos de outros ramos.

Timeline é a linha do tempo que um cluster segue. Uma linha do tempo nova é criada em toda restauração de backups e promoção de réplicas porque após esses eventos o cluster se torna um novo primário independente do primário anterior.

Os segmentos contidos em pg_wal são nomeados com números hexadecimais partindo de 000000010000000000000001 e criados a partir de três partes, sendo uma o identificador de timeline e as outras geradas a partir do LSN.

LSN foi quebrado em duas partes, com os bits mais significativos no meio do nome do arquivo e, dos menos significativos, apenas oito deles são mantidos no final do nome do arquivo (variando de 00000000 a 000000FF).

Por exemplo, um LSN 1234567/89abcdef mapeado para arquivo na timeline 1:

         timeline LSN alto LSN baixo
Arquivo: 00000001 01234567 00000089
    LSN:           1234567/89abcdef

O LSN atual pode ser consultado com pg_current_wal_lsn():

[[local]:5432] postgres@postgres=# select pg_current_wal_lsn();
╔════════════════════╗
║ pg_current_wal_lsn ║
╠════════════════════╣
║ 7F/91FEE298        ║
╚════════════════════╝

E a função pg_walfile_name() pode ser usada para gerar o respectivo nome de arquivo:

[[local]:5432] postgres@postgres=# select pg_walfile_name('7F/91FEE298');
╔══════════════════════════╗
║     pg_walfile_name      ║
╠══════════════════════════╣
║ 000000010000007F00000091 ║
╚══════════════════════════╝

Arquivamento

O arquivamento é a ação de guardar uma cópia de cada segmento de WAL em um local externo que seja acessível no momento da restauração de backups e para réplicas que tenham ficado atrasadas.

Quando um segmento é completado, o PostgreSQL executa um comando externo que deve fazer essa cópia. É importante que ele não retorne quando a cópia tiver sido finalizada, mas sim quando a escrita em disco no sistema remoto tenha sido garantida (fsync). Caso contrário, é possível que uma falha no sistema remoto após o retorno do comando, mas antes da confirmação da escrita em disco, cause a perda de um segmento, o que invalida todos os backups existentes por introduzir uma lacuna na linha do tempo.

Para habilitar o arquivamento, é necessário usar a configuração archive_mode e informar o comando em archive_command como em (obs.: o scp não garante a escrita no destino; não use em produção):

archive_mode = on
archive_command = 'scp %p pg-a.local:archive/14/%f'

No archive_command, o padrão %p é substituído pelo caminho absoluto completo do segmento de WAL dentro de pg_wal, incluindo o nome do arquivo. E o padrão %f é substituído apenas pelo nome do arquivo.

Obs.: Os comandos scp, cp, rsync e outros não garantem a escrita no destino. Em produção, use um comando que forneça essa garantia, como barman-wal-archive e o pgbackrest archive-push.

Arquivamento por replicação

Outra forma de fazer o arquivamento é por replicação. Com ela, a máquina de destino inicia um processo pg_receivewal que estabelece uma conexão com o servidor e, através dela, recebe toda a sequência de alterações em tempo real, criando e preenchendo os segmentos os localmente enquanto recebe o WAL em tempo real.

Mas se isso for usado sem o arquivamento por archive_command, é recomendado que a conexão use um slot de replicação para evitar lacunas no histórico.

Configurações

O tamanho do buffer circular em pg_wal varia em função da demanda. O PostgreSQL automaticamente cria novos segmentos quando necessário, até o limite máximo de max_wal_size e apaga segmentos desnecessários até o limite mínimo de min_wal_size.

Atenção: O limite máximo de max_wal_size pode ser ultrapassado quando o arquivamento não consegue terminar com sucesso (erros no comando archive_command) ou quando slots de replicação não conseguem avançar. Nesses casos o PostgreSQL guarda mais segmentos para evitar a quebra da linha do tempo com réplicas e backups.

Prática

Configure a máquina pg-1 para arquivar os segmentos de WAL na máquina pg-a por scp como no exemplo acima.

Dica: É importante que a comunicação a partir do PostgreSQL (com o usuário postgres do sistema operacional) até o diretório remoto funcione sem senha ou qualquer outro método de autenticação interativo. A autenticação por confiança de chaves públicas ssh é um dos métodos mais usados.