postgres postgresql replication physical
O WAL é uma longa sequência de alterações físicas dos blocos de disco registradas em uma linha do tempo que pode ser armazenada, comunicada e reproduzida. E um backup é uma cópia física dos dados em um ponto da linha do tempo, que pode ser restaurado e avançado adiante ao longo daquela linha do tempo e de outras que tenham surgido dela, contanto que o WAL esteja íntegro desde o momento do backup até o alvo desejado.
Uma replicação física é um backup em ciclo de contínua restauração de WAL, como se o alvo da restauração estivesse sempre adiante. O PostgreSQL primário aceita conexões que atendem transações de leitura e escrita. O PostgreSQL réplica (também chamado de secundário ou standby) também aceita conexões que atendem transações, mas estas apenas de leitura. Isso porque qualquer escrita feita pela réplica localmente ou seria sobrescrita pelos blocos provenientes da replicação, gerando inconsistências nos dados, ou quebraria a cadeia de replicação.
Atenção: O PostgreSQL não usa termos como mestre, master, escravo ou slave pelo forte significado social negativo que essas palavras carregam. Os termos corretos são primário/primary e secundário/réplica/standby.
A seguir temos uma descrição simplificada da replicação de um primário (à esquerda) para a réplica (à direita), com backups sendo criados (A) e restaurados (B) e arquivamento de WAL em um terceiro local, abaixo. O diagrama pode ser lido seguindo os passos:
- Aplicação faz leituras e escritas nos bancos de dados do primário.
- Alterações nos bancos de dados mudam blocos em disco.
- Mudanças dos blocos são escritas no WAL (a) e nas conexões de replicação (b) (primary_conninfo) e (c) (pg_receivewal).
- Segmentos de WAL completados são arquivados (d) (archive_command).
- Segmentos de WAL são buscados do arquivamento pela réplica (restore_command).
- Mudanças de blocos são trazidas pela conexão de replicação e pelos segmentos de WAL.
- Blocos são escritos em disco na réplica.
- Aplicação consulta os bancos de dados da réplica como somente leitura.
╔═══════════╗↔──────────────────┬─────┐ ║ Aplicação ║╗↔─────────────────┼─────┤ ╚═══════════╝║╗↔────────────────┼─────┤ ╚═══════════╝║↔────────────────┼─────┤ ╚═══════════╝↔────────────────┼─────┤ (1)│ │(8) ┌─────────────────────┐ │ │ ┌────────────────────┐ ╔══════════════════╡PostgreSQL (primário)╞═════════════════════╗ │ │ ╔══════════════════╡PostgreSQL (réplica)╞══════════════════════╗ ║ └─────────────────────┘ ║ │ │ ║ └────────────────────┘ ║ ╠═╡global/╞═══════╗ ╔══════════════╡base/╞═╣ │ │ ╠═╡base/╞══════════════╗ ╔═══════╡global/╞═╣ ║ ║ ║ ║ │ │ ║ ║ ║ ║ ║ O O ║ ║ ┏━┥banco de dados┝━┓ ║ │ │ ║ ┏━┥banco de dados┝━┓ ║ ║ O O ║ ║ ─┼─ ─┼─ ║ (2) ║ ┃ ┌─┤schema├─────┐ ┃ ║ │ │ ║ ┃ ┌─┤schema├─────┐ ┃ ║ (7) ║ ─┼─ ─┼─ ║ ║ │ │ ║ ┊←┈╢ ┃ │ ☑ tabelas │←╂─╫───┤ ├───╫─╂→│ ☑ tabelas │ ┃ ╟←┈┊ ║ │ │ ║ ║ ╱ ╲ ╱ ╲ ║ ┊←┈╢ ┃ │ ☑ índices │←╂─╫───┤ ├───╫─╂→│ ☑ índices │ ┃ ╟←┈┊ ║ ╱ ╲ ╱ ╲ ║ ║ ☑ usuários ║ ┊←┈╢ ┃ │ ☑ visões │←╂─╫───┤ ├───╫─╂→│ ☑ visões │ ┃ ╟←┈┊ ║ ☑ usuários ║ ║ ☑ grupos ║ ┊←┈╢ ┃ │ ☑ sequências │←╂─╫───┤ ├───╫─╂→│ ☑ sequências │ ┃ ╟←┈┊ ║ ☑ grupos ║ ║ ☑ slots de ║ ┊←┈╢ ┃ │ ☑ … │←╂─╫───┘ └───╫─╂→│ ☑ … │ ┃ ╟←┈┊ ║ ☑ slots de ║ ║ ☑ replicação ║ ┊ ║ ┃ └──────────────┘ ┃ ║ ║ ┃ └──────────────┘ ┃ ║ ┊ ║ ☑ replicação ║ ║ ☑ pg_control┅┅┅┅╫┅┅┓ ┊ ║ ┗━━━━━━━━━━━━━━━━━━┛ ║ ║ ┗━━━━━━━━━━━━━━━━━━┛ ║ ┊ ┏┅┅╫┅☑ pg_control ║ ║ ║ ┋ ┊ ║ ║ ║ ║ ┊ ┋ ║ ║ ╠═════════════════╝ ┋ ┊ ╚══════════════════════╣ ╠══════════════════════╝ ┊ ┋ ╚═════════════════╣ ║ ┋ (3)↓ ║ ║ ↑ ┋ ║ ╠═╡pg_wal/╞══════════╪═══════════════╪═══════════╗ ║ (b) ║ ╔═══════════╪═══════════════╪══════════╡pg_wal/╞═╣ ║ ┋ ┊┈┈┈┈┈┈┈┈┈┈→╫→┈┈┈┈┈┈┈┈┈┈┈→╫→┈┈┈┈┈┬┈┈┈┈┈→╫→┈┈┈┈┈┈┈┈┈┈┈→╫→┈┈┈┈┈┈┈┈┈→┊(6) ┋ ║ ║ ┌────────────┐ ┌──↓─────────┐ (a)↓ ║ ║ ┊ ║ ║ ↑ ┌─────────↓──┐ ┌────────────┐ ║ ║ →│+--++--+--++│→│-++++-++---+│→╷++-.........╷→ ║ (d) ║ (c)┊ ║ (5) ║ ←╷.........-++╷←│+---++-++++-│←│++--+--++--+│← ║ ║ └┤/…00000012├┘ └┤/…00000013├┘↓└┤/…00000014├┘ ║ ┌───────┐ ║ ┊ ║ ┌───────┐ ║ └┤/…00000014├┘↑└┤/…00000013├┘ └┤/…00000012├┘ ║ ║ (4)└┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈→╫→┈│…++---+│┈→╫→┈┈┐ ┊ ┌┈┈→╫→┈│…++---+│┈→╫→┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┘ ║ ║ ║ └┤/…013├┘ ║ ┊ ┊ ┊ ║ └┤/…013├┘ ║ ║ ╚════════════════════════════════════════════════╩═════════════╝ ┊ ┊ ┊ ╚═════════════╩════════════════════════════════════════════════╝ ╏ ┊ ┊ ┊ ╏ (A) ╏ ┊ ┊ ┊ ╏ (B) ╏ ┊ ┊ ┊ ╏ ╏ ↓ ↓ ↑ ╏ ╏ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━┷━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ╏ ╏ ┃ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┃ ╏ ╔═══════╗ ┃ →│…++-+++│→│…+++---│→│…----++│→│…+--+-+│→│…-+--++│→│…++---+│→ ┃ ╔═══════╗ ╠══╗ ╔══╣ ┃ └┤/…008├┘ └┤/…009├┘ └┤/…010├┘ └┤/…011├┘ └┤/…012├┘ └┤/…013├┘ ┃ ╠══╗ ╔══╣ ╠══╝ ╚══╣ ┠────────↑────────↑────────────↑─────────↑─────────↑───────↑──────────┨ ╠══╝ ╚══╣ ╠═════╗ ║ ┃ ╔═══════════════╗ ╔═══════════════╗ ╔═══════════════╗ ┃ ║ ╔═════╣ ╚═════╩═╝ ┃ ╠═════╗ ╔═════╣ ╠═════╗ ╔═════╣ ╠═════╗ ╔═════╣ ┃ ╚═╩═════╝ ╏ ┃ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ┃ ╏ ┗╍╍╍╍╍╍╸┃………………╠═════╝ ╚═════╣………╠═════╝ ╚═════╣………╠═════╝ ╚═════╣………………┃╺╍╍╍╍╍╍┛ ┃ ╠══════════╗ ║ ╠══════════╗ ║ ╠══════════╗ ║ ┃ ┃ ║ ║ ║ ║ ║ ║ ║ ║ ║ ┃ ┃ ╚══════════╩════╝ ╚══════════╩════╝ ╚══════════╩════╝ ┃ ┃ ┌──────────────────┐ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┥Servidor de backup┝━━━━━━━━━━━━━━━━━━━━━━━━━┛ └──────────────────┘
Para a réplica acompanhar o primário, ela depende do recebimento confiável de todo o WAL gerado. Esse recebimento pode sofrer atrasos e até interrupções causadas por problemas de rede ou indisponibilidade da réplica. Mas para que esses eventos não causem lacunas na linha do tempo de WAL, é vital que exista ao menos uma estratégia que permita que o WAL volte a ser transmitido assim que possível e que evite as lacunas. Duas estratégias existem para tratar esse problema.
A primeira é o arquivamento de segmentos de WAL. Com ela, o primário continua
trabalhando e arquivando o trabalho feito. Quando a réplica retorna, ela
recupera os segmentos do arquivamento e consegue reaplicar todas as alterações
até alcançar o estado mais recente do primário. Ela é adequada para a
restauração de backups e indisponibilidades de longo prazo, mas não para
replicações em tempo real, já que dependem do segmento ser preenchido e
arquivado. Adicionalmente, o primário apenas remove o segmento local quando o
arquivamento dele foi feito com sucesso, o que evita riscos de perdas de
segmentos mas aumenta o espaço ocupado em pg_wal
no primário enquanto a falha
persistir.
A segunda estratégia é pela conexão de replicação com um slot de replicação.
Um slot de replicação é uma forma do primário conhecer uma réplica e de lembrar o último ponto recebido pela réplica. Com isso, se a réplica fica indisponível temporariamente, o primário não recicla os segmentos que são necessários para ela, garantindo que quando a conexão for reestabelecida, a réplica conseguirá receber toda a linha do tempo sem lacunas.
É possível criar um slot de replicação com a função
pg_create_physical_replication_slot()
:
[[local]:5432] postgres@postgres=# SELECT pg_create_physical_replication_slot('pg-2');
E usar em uma conexão adicionando o parâmetro primary_slot_name
nas
configurações da réplica:
primary_slot_name = 'pg-2'
Quando uma transação altera tuplas, as novas versões são escritas diretamente (mas não necessariamente imediatamente, em função de buffers de escrita) na tabela. Mas essas tuplas novas só são visíveis para outras transações após um COMMIT de sucesso daquela transação.
Em um primário independente, o comando COMMIT faz com que a escrita dos registros no WAL seja garantida antes do sucesso ser retornado para a aplicação.
Esse também é o comportamento em um primário com replicações assíncronas, que são o tipo padrão de replicação. Ou seja, as transações são processadas completamente no primário para posteriormente serem replicadas para o secundário através do WAL.
Mas em um sistema com replicação síncrona o comportamento em torno da garantia de escrita é diferente. Nesse caso, o primário registra o COMMIT no WAL, replica o WAL para o secundário e aguarda a confirmação de que a transação foi escrita em disco no secundário antes do sucesso ser retornado para a aplicação.
As diferenças do modo assíncrono sobre o síncrono são:
- alterações são visíveis antes na réplica e com atraso no primário, ordem inversa quando comparado com o modo assíncrono;
- maior risco de indisponibilidade, dado que falhas nas duas máquinas e na rede causam indisponibilidade, diferentemente do modo assíncrono, em que apenas falhas no primário causam indisponibilidade;
- maior latência e tempo de transações, dado que o tempo do COMMIT é proporcional ao tempo da latência da rede e do disco remoto, diferentemente do modo assíncrono em que é limitado apenas pelo tempo de escrita em disco local;
- toda transação é escrita em ao menos dois locais, então uma falha não perde transações, diferentemente do modo assíncrono em que uma falha no primário pode perder as transações que foram escritas no WAL e ainda não tinham sido replicadas.
Para configurar um primário com replicação síncrona, adicionamos o nome da
conexão de replicação (definido por application_name
na conexão iniciada pela
réplica) na configuração synchronous_standby_names
:
synchronous_standby_names = 'pg-2'
Também é possível atenuar o risco de indisponibilidade usando mais réplicas e aguardando a escrita de apenas algumas delas. Por exemplo, com três réplicas, mas aguardando apenas uma:
synchronous_standby_names = 'ANY 1 (pg-2, pg-3, pg-4)'
E podemos aguardar as primeiras conexões ativas da lista, ou seja, pg-2
e
pg-3
na maior parte do tempo, mas incluindo pg-4
nos momentos em que uma das
duas primeiras estiver inacessível.
synchronous_standby_names = 'FIRST 2 (pg-2, pg-3, pg-4)'
O termo especial *
inclui toda conexão de replicação:
synchronous_standby_names = 'ANY 1 (*)'
Com a replicação é possível distribuir as tarefas e, portanto, a carga do sistema em nós além do primário. Como visto anteriormente, com uma réplica podemos direcionar as transações de leitura e escrita para o primário (1) e transações somente leitura para a réplica (2).
Adicionalmente, a réplica pode ser usada para criação de backups (3), o que alivia o primário dessa tarefa. Ainda assim é preferível que o primário faça o arquivamento, em geral por conexão de replicação com slot (4), para minimizar a possível perda de transações em andamento no caso de falha do primário.
Por fim, cada réplica pode ser configurada, assim como o primário, para aceitar conexões de replicação e, portanto, para replicar o WAL para outras. Isso é chamado replicação cascateada e evita que o primário tenha que enviar grandes volumes de WAL através de diversas conexões de replicação. Ao mesmo tempo, permite que a aplicação perceba uma boa escalabilidade horizontal da leitura se direcionar as elas para as réplicas (5).
Novas réplicas são instanciadas a partir do backup (6).
╔═══════════╗↔───────────────┬──────────────────┐ ║ Aplicação ║╗↔──────────────┼───────────────────┐ ╚═══════════╝║╗↔─────────────┼────────────────────┐ ╚═══════════╝║↔─────────────┼─────────────────────┐ ╚═══════════╝↔─────────────┼──────────────────────┐ (1) │ (2) │ (5) │││││ │ │ ↓↓↓↓↓ │ │ ┌─────────┐ │ │ ╔═╡ Réplica ╞═╗ │ │ ┌┈┈→╢ └─────────┘ ║ │ │ ┊ ╚═════════════╝ │ │ ┊ ┌─────────┐ │ │ ┊ ╔═╡ Réplica ╞═╗ │ │ ┊┈┈→╢ └─────────┘ ║ ↓ ↓ ┊ ╚═════════════╝ ┌──────────┐ ┌─────────┐ ┊ ┌─────────┐ ╔═╡ Primário ╞═╗ ╔═╡ Réplica ╞═╗ ┊ ╔═╡ Réplica ╞═╗ ║ └──────────┘ ╟→┈┈┬┈┈→╢ └─────────┘ ╟→┈┈┴┈┈→╢ └─────────┘ ║ ╚══════════════╝ ┊ ╚═════════════╝ ╚═════════════╝ ┊ ╏ (6) ╏ ┊ ╏ ╏ (4) ↓ (3) ╏ ╏ ┏━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ╏ ┃ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┃ ╏ ┃→└┤…├┘→└┤…├┘→└┤…├┘→└┤…├┘→└┤…├┘→└┤…├┘→└┤…├┘→┃ ╏ ┠──↑─────↑─────↑────↑─────↑──↑──────↑───↑───┨ ╏ ┃ ╔═══════╗ ╔═══════╗ ╔═══════╗ ╔═══════╗ ┃ ╏ ┃ ╠══╗ ╔══╣ ╠══╗ ╔══╣ ╠══╗ ╔══╣ ╠══╗ ╔══╣ ┃ ╏ ┃……╠══╝ ╚══╣…╠══╝ ╚══╣…╠══╝ ╚══╣…╠══╝ ╚══╣……┃╺╍╍╍┛ ┃ ╠═════╗ ║ ╠═════╗ ║ ╠═════╗ ║ ╠═════╗ ║ ┃ ┃ ╚═════╩═╝ ╚═════╩═╝ ╚═════╩═╝ ╚═════╩═╝ ┃ ┃ ┌──────────────────┐ ┃ ┗━━━━━━━━━━━┥Servidor de backup┝━━━━━━━━━━━━┛ └──────────────────┘
Algumas visões de sistema apresentam o estado da replicação:
-
pg_stat_replication
: Usada no primário ou em qualquer nó enviando WAL por conexões de replicação, mostra uma linha para cada conexão de replicação, incluindo atrasos na aplicação do WAL. -
pg_stat_wal_receiver
: Usada na réplica ou em qualquer nó recebendo WAL por uma conexão de replicação, mostra uma linha para a conexão de replicação ativa. -
pg_replication_slots
: Mostra uma linha para cada slot de replicação existente, com informações sobre atividade e atraso.
A partir da versão 12, todas as configurações de replicação são colocadas no
postgresql.conf
. Em versões anteriores, as configurações de arquivamento
também são colocadas no postgresql.conf
mas as configurações de restauração
são colocadas no recovery.conf
, localizado no mesmo diretório.
As configurações de arquivamento foram vistas em WAL.
As configurações de restauração são:
-
synchronous_standby_names
: Lista de conexões de replicação síncrona. Configurado no primário. -
primary_conninfo
: Parâmetros de conexão da réplica para o primário, na forma'host=pg-1.local port=5342 application_name=abc …'
. Configurado em cada réplica, comapplication_name
opcional para identificá-la no primário. -
primary_slot_name
: Nome do slot de replicação existente no primário que será usado para a conexão informada porprimary_conninfo
. Configurado em cada réplica. -
recovery_min_apply_delay
: Atraso aplicado na replicação, assim é possível manter uma réplica continuamente no passado, para consultas. Configurado na réplica. -
max_standby_archive_delay
,max_standby_streaming_delay
: Atraso máximo aceito na replicação quando consultas de longa duração estão em execução na réplica. Configurado na réplica. -
hot_standby_feedback
: Normalmente quando a réplica está executando consultas de longa duração mas o primário já avançou o horizonte de passado distante (porpg_control
e vacuum), ou as consultas na réplica são canceladas ou a replicação é atrasada. Esta configuração faz com que a réplica informe o primário das consultas de longa duração, impedindo o avanço do horizonte de xid. Configurado na réplica. -
…
: primário e réplica
Crie uma réplica da instância de pg-1 na máquina pg-2 com as seguintes características:
-
replicação física assíncrona (
standby.signal
) -
com conexão de replicação (
primary_conninfo
) -
OU com recuperação de WAL do arquivamento (
restore_command
) e sem slot de replicação -
OU sem recuperação de WAL do arquivamento (
restore_command
) e com slot de replicação