Alta disponibilidade nativa PostgreSQL: Patroni, replicação de streaming e estratégias de failover de produção
HA nativa PostgreSQL com Patroni, replicação de streaming e HAProxy
A execução de uma única instância PostgreSQL é simples até que a primeira interrupção não planejada lembre você de que um banco de dados é tão valioso quanto sua disponibilidade. Falhas de disco, kernel panics, partições de rede e atualizações malfeitas não são riscos teóricos — são certezas operacionais em um cronograma suficientemente longo. O PostgreSQL não é fornecido com failover automático integrado, mas fornece todos os primitivos de replicação necessários para criar um cluster altamente disponível. Patroni, uma estrutura de HA de código aberto mantida pela Zalando, orquestra esses primitivos em um sistema de failover de nível de produção que foi testado em escala em milhares de clusters PostgreSQL em todo o mundo.
Este artigo é um guia de engenharia aprofundado. Abordaremos replicação de streaming PostgreSQL (síncrona e assíncrona), arquitetura e configuração Patroni, etcd como um armazenamento de configuração distribuído, HAProxy para roteamento de conexão com divisão de leitura e gravação, PgBouncer para pool de conexão, arquivamento WAL e recuperação point-in-time, replicação lógica para sincronização seletiva de dados, pg_basebackup para provisionamento inicial de espera, repmgr como uma alternativa ao Patroni, padrões de implantação específicos de nuvem para AWS, Azure, e GCP, implantação bare metal k3s com Rancher e Longhorn, monitoramento com pg_stat_replication e Prometheus/Grafana, procedimentos de alternância versus failover, prevenção de split-brain, ajuste de produção e engenharia de caos para validação de failover.
Fundamentos da replicação de streamingPostgreSQL
A replicação de streamingé a espinha dorsal da alta disponibilidade do PostgreSQL. Ele funciona enviando continuamente registros Write-Ahead Log (WAL) de um servidor primário para um ou mais servidores em espera. O standby aplica esses registros WAL em tempo real, mantendo uma cópia quase idêntica dos dados do primário. Esse mecanismo foi introduzido na PostgreSQL 9.0 e foi refinado em todas as versões subsequentes.
Existem dois modos de replicação de streaming:assíncronoesíncrono. No modo assíncrono, o primário não espera que os standbys confirmem o recebimento dos registros WAL antes de confirmar uma transação. Isso proporciona desempenho máximo de gravação, mas introduz uma janela de possível perda de dados – se o primário falhar antes que um modo de espera tenha recebido o WAL mais recente, essas transações serão perdidas. No modo síncrono, o primário aguarda pelo menos um modo de espera para confirmar que os registros WAL foram gravados no armazenamento durável antes de relatar uma transação como confirmada. Isso elimina a perda de dados ao custo do aumento da latência de confirmação, uma vez que cada gravação deve ir e voltar para um modo de espera.
A escolha entre replicação síncrona e assíncrona não é binária. O PostgreSQL oferece suporte aosynchronous_commitno nível da sessão, de modo que cargas de trabalho sensíveis à latência podem optar por confirmações assíncronas, enquanto transações financeiras críticas usam confirmações síncronas no mesmo cluster.
Configurando o Primário para Replicação
O servidor primário deve ser configurado para gerar registros WAL em um nível suficiente para replicação e para permitir conexões em espera. As seguintes configurações dopostgresql.confsão essenciais.
# postgresql.conf on the primary
wal_level = replica # minimum for streaming replication
max_wal_senders = 10 # max concurrent replication connections
max_replication_slots = 10 # prevent WAL removal before standby consumption
wal_keep_size = 2GB # retain WAL as fallback if slots are unused
hot_standby = on # allow read queries on standbys
synchronous_commit = on # 'on' for sync, 'off' for pure async
synchronous_standby_names = 'ANY 1 (standby1, standby2)' # sync replication targets
archive_mode = on # enable WAL archiving for PITR
archive_command = 'test ! -f /archive/%f && cp %p /archive/%f'
listening_addresses = '*'
port = 5432A autenticação para conexões de replicação é tratada empg_hba.conf. As conexões de replicação usam um tipo de conexão dedicado.
# pg_hba.conf — replication entries
# TYPE DATABASE USER ADDRESS METHOD
host replication replicator 10.0.1.0/24 scram-sha-256
host replication replicator 10.0.2.0/24 scram-sha-256
host all all 10.0.0.0/16 scram-sha-256Crie o usuário de replicação no primário.
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'strong_replication_password';Provisionando um Standby com pg_basebackup
O utilitáriopg_basebackupcria uma cópia física do diretório de dados do primário, que se torna o ponto de partida para um novo standby. Ele lida com o backup básico e o streaming WAL atomicamente, para que a cópia resultante seja consistente.
# On the standby server
pg_basebackup -h primary-host -U replicator -D /var/lib/postgresql/16/main \
-Fp -Xs -P -R
# -Fp: plain format
# -Xs: stream WAL during backup
# -P: show progress
# -R: create standby.signal and configure primary_conninfo in postgresql.auto.confO sinalizador-Ré crítico – ele gravaprimary_conninfoempostgresql.auto.confe criastandby.signal, que informa ao PostgreSQL para iniciar no modo de espera. Na PostgreSQL 12 e posterior, orecovery.confé substituído por esses dois mecanismos.
# postgresql.auto.conf (generated by pg_basebackup -R)
primary_conninfo = 'host=primary-host port=5432 user=replicator password=strong_replication_password application_name=standby1'
primary_slot_name = 'standby1_slot'Crie o slot de replicação no primário antes de iniciar o modo de espera, para evitar que o WAL seja limpo antes que o modo de espera possa consumi-lo.
SELECT pg_create_physical_replication_slot('standby1_slot');
SELECT pg_create_physical_replication_slot('standby2_slot');Patroni: Orquestração HA automatizada
A replicação de streamingoferece redundância de dados, mas não oferece failover automático. Se o primário travar, alguém — um operador humano ou um sistema de automação — deverá promover um standby para primário, reconfigurar os standbys restantes para seguir o novo primário e atualizar o roteamento da conexão. Patroni automatiza tudo isso.
Patroni é um daemon Python executado junto com cada instância PostgreSQL. Ele usa um Distributed Configuration Store (DCS) — normalmente etcd, mas também ZooKeeper ou Consul — para coordenar a eleição do líder e o estado do cluster. Cada nó Patroni grava continuamente seu status de integridade no DCS. Quando o líder (primário) não consegue renovar sua chave DCS dentro do TTL configurado, Patroni inicia uma eleição de líder entre os standbys íntegros. O vencedor é promovido a primário e os nós restantes se reconfiguram como esperas do novo primário – tudo automaticamente, normalmente dentro de 10 a 30 segundos.
ConfiguraçãoPatroni YAML
OPatroni é configurado por meio de um arquivo YAML que define a conexão DCS, parâmetros PostgreSQL, comportamento de replicação e configurações de bootstrap. A seguir está uma configuração de nível de produção para o nó primário.
# /etc/patroni/patroni.yml — Node 1 (Primary)
scope: pg-ha-cluster
namespace: /postgresql-ha/
name: node1
restapi:
listen: 0.0.0.0:8008
connect_address: 10.0.1.10:8008
etcd3:
hosts:
- 10.0.2.10:2379
- 10.0.2.11:2379
- 10.0.2.12:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576 # 1MB — only promote standbys within this lag
synchronous_mode: true
synchronous_mode_strict: false
postgresql:
use_pg_rewind: true
use_slots: true
parameters:
wal_level: replica
hot_standby: 'on'
max_connections: 200
max_wal_senders: 10
max_replication_slots: 10
wal_keep_size: 2GB
synchronous_commit: 'on'
archive_mode: 'on'
archive_command: 'test ! -f /archive/%f && cp %p /archive/%f'
archive_timeout: 60
wal_log_hints: 'on'
shared_preload_libraries: 'pg_stat_statements'
track_commit_timestamp: 'on'
pg_hba:
- host replication replicator 10.0.0.0/16 scram-sha-256
- host all all 10.0.0.0/16 scram-sha-256
- host all all 0.0.0.0/0 scram-sha-256
initdb:
- encoding: UTF8
- data-checksums
users:
admin:
password: 'admin_secure_password'
options:
- createrole
- createdb
replicator:
password: 'repl_secure_password'
options:
- replication
postgresql:
listen: 0.0.0.0:5432
connect_address: 10.0.1.10:5432
data_dir: /var/lib/postgresql/16/main
bin_dir: /usr/lib/postgresql/16/bin
config_dir: /var/lib/postgresql/16/main
pgpass: /tmp/pgpass0
authentication:
superuser:
username: postgres
password: 'postgres_secure_password'
replication:
username: replicator
password: 'repl_secure_password'
rewind:
username: postgres
password: 'postgres_secure_password'
parameters:
unix_socket_directories: '/var/run/postgresql'
create_replica_methods:
- basebackup
basebackup:
max-rate: 100M
checkpoint: fast
tags:
nofailover: false
noloadbalance: false
clonefrom: false
nosync: falseOs nós em espera usam uma configuração idêntica com seus próprios valoresname,connect_addresselisten. Patroni cuida do resto – ele detecta se um nó deve ser o líder ou uma réplica com base no estado DCS e configura o PostgreSQL de acordo.
etcd como armazenamento de configuração distribuída
etcd é o sistema nervoso de um cluster Patroni. Ele armazena a identidade do líder atual, a topologia do cluster, a configuração desejada e o status de funcionamento de cada nó. Um cluster etcd de três nós é o mínimo para produção, tolerando a falha de um nó enquanto mantém o quorum.
# Install and configure etcd on three dedicated nodes
# /etc/etcd/etcd.conf.yml — Node etcd1 (10.0.2.10)
name: etcd1
data-dir: /var/lib/etcd
listen-client-urls: http://0.0.0.0:2379
listen-peer-urls: http://0.0.0.0:2380
advertise-client-urls: http://10.0.2.10:2379
initial-advertise-peer-urls: http://10.0.2.10:2380
initial-cluster: etcd1=http://10.0.2.10:2380,etcd2=http://10.0.2.11:2380,etcd3=http://10.0.2.12:2380
initial-cluster-state: new
initial-cluster-token: patroni-etcd-cluster
# Start etcd
systemctl enable --now etcd
# Verify cluster health
etcdctl endpoint health --cluster \
--endpoints=http://10.0.2.10:2379,http://10.0.2.11:2379,http://10.0.2.12:2379Para implantações de produção, ative o TLS entre pares etcd e entre clientes etcd e Patroni. O tráfego etcd não criptografado expõe as credenciais e a configuração do cluster a invasores no nível da rede.
HAProxy para roteamento de conexão
Patroni expõe um REST API em cada nó (porta 8008 por padrão) que informa se o nó é o líder atual ou uma réplica. O HAProxy usa esses endpoints de verificação de integridade para rotear o tráfego: as gravações vão para o líder, as leituras vão para as réplicas íntegras. Isso oferece divisão automática de leitura e gravação sem quaisquer alterações no nível do aplicativo.
# /etc/haproxy/haproxy.cfg
global
maxconn 2000
log /dev/log local0
stats socket /var/run/haproxy.sock mode 660 level admin
defaults
mode tcp
log global
retries 3
timeout client 30m
timeout connect 4s
timeout server 30m
timeout check 5s
maxconn 1000
listen pg_write
bind *:5000
option httpchk GET /primary
http-check expect status 200
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
server node1 10.0.1.10:5432 check port 8008
server node2 10.0.1.11:5432 check port 8008
server node3 10.0.1.12:5432 check port 8008
listen pg_read
bind *:5001
balance roundrobin
option httpchk GET /replica
http-check expect status 200
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
server node1 10.0.1.10:5432 check port 8008
server node2 10.0.1.11:5432 check port 8008
server node3 10.0.1.12:5432 check port 8008
listen stats
bind *:7000
mode http
stats enable
stats uri /
stats refresh 10sO endpoint/primaryretorna HTTP 200 apenas no líder Patroni atual. O endpoint/replicaretorna 200 em esperas íntegras. Quando ocorre um failover, o novo primário começa a retornar 200 em/primarye o HAProxy redireciona automaticamente o tráfego de gravação — normalmente dentro de um único intervalo de verificação de integridade (3 segundos). A diretivaon-marked-down shutdown-sessionsencerra imediatamente as conexões existentes com um primário com falha, forçando os clientes a se reconectarem ao novo líder.
PgBouncer para pool de conexões
PostgreSQL cria um novo processo backend para cada conexão do cliente. Em escala – centenas ou milhares de microsserviços, cada um mantendo pools de conexões – a sobrecarga de criação de processos e consumo de memória torna-se significativa. O PgBouncer fica entre o aplicativo e o PostgreSQL, mantendo um conjunto de conexões do lado do servidor e multiplexando as conexões do cliente neles.
# /etc/pgbouncer/pgbouncer.ini
[databases]
* = host=127.0.0.1 port=5432 dbname=appdb
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 50
min_pool_size = 10
reserve_pool_size = 10
reserve_pool_timeout = 3
server_lifetime = 3600
server_idle_timeout = 600
server_connect_timeout = 5
server_login_retry = 3
log_connections = 1
log_disconnections = 1
stats_period = 60Quando usado com Patroni, o PgBouncer normalmente está localizado em cada nó PostgreSQL ou nos nós HAProxy. O modo pooltransactioné a melhor escolha para a maioria das cargas de trabalho — ele atribui uma conexão de servidor durante uma transação e a retorna ao pool entre as transações. Isto é muito mais eficiente que o modosession, que mantém uma conexão para toda a sessão do cliente.
Arquivamento WAL e recuperação pontual
A replicação de streaming protege contra falhas do servidor, mas não protege contra erros lógicos — umDROP TABLEacidental ou uma migração de aplicativo incorreta é replicado para todos os standbys imediatamente. O arquivamento WAL combinado com a recuperação pontual (PITR) permite restaurar a qualquer momento antes da ocorrência do erro.
copia segmentos WAL concluídos para um arquivo durável – normalmente um bucket S3, uma montagem NFS ou um servidor de backup dedicado. Ferramentas comopgBackResteWAL-Glidam com arquivamento de forma eficiente com compactação, criptografia e transferência paralela.
# pgBackRest configuration — /etc/pgbackrest/pgbackrest.conf
[global]
repo1-type=s3
repo1-s3-bucket=pg-wal-archive
repo1-s3-endpoint=s3.eu-west-1.amazonaws.com
repo1-s3-region=eu-west-1
repo1-s3-key=AKIAIOSFODNN7EXAMPLE
repo1-s3-key-secret=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
repo1-path=/pgbackrest
repo1-retention-full=4
repo1-retention-diff=7
repo1-cipher-type=aes-256-cbc
repo1-cipher-pass=strong_encryption_passphrase
compress-type=zst
compress-level=3
process-max=4
[pg-ha-cluster]
pg1-path=/var/lib/postgresql/16/main
pg1-port=5432
# In postgresql.conf
archive_command = 'pgbackrest --stanza=pg-ha-cluster archive-push %p'
restore_command = 'pgbackrest --stanza=pg-ha-cluster archive-get %f "%p"'Para executar uma recuperação pontual, especifique um carimbo de data/hora de destino.
# Restore to a specific point in time
pgbackrest --stanza=pg-ha-cluster --type=time \
--target="2026-04-12 11:25:00" \
--target-action=promote \
restoreReplicação lógicapara sincronização seletiva de dados
Embora a replicação de streaming crie uma cópia física exata de todo o cluster de banco de dados, a replicação lógica opera no nível da tabela, replicando tabelas individuais ou subconjuntos de dados entre instâncias PostgreSQL independentes. Isso é útil para atualizações de versões principais com tempo de inatividade zero, réplicas de leitura entre regiões que precisam apenas de tabelas específicas, alimentação de data warehouse e distribuição de dados multilocatários.
# On the publisher (source database)
wal_level = logical # must be 'logical' — higher than 'replica'
CREATE PUBLICATION app_pub FOR TABLE orders, customers, products;
# On the subscriber (target database)
CREATE SUBSCRIPTION app_sub
CONNECTION 'host=publisher-host port=5432 dbname=appdb user=replicator password=pass'
PUBLICATION app_pub;A replicação lógicapode ser executada junto com a replicação de streaming. Um padrão comum é usar replicação de streaming para HA (failover físico rápido) e replicação lógica para réplicas de análise entre regiões que precisam apenas de um subconjunto de tabelas.
Replicação de streaming multirregionalPara recuperação de desastres e desempenho de leitura global, os clusters PostgreSQL podem abranger diversas regiões. O padrão padrão é a replicação síncrona dentro de uma região (para zero perda de dados no failover local) e a replicação assíncrona entre regiões (para evitar penalidades de latência entre regiões em cada gravação). Cada região possui seu próprio HAProxy para roteamento de leitura local.
Procedimentos de failover e comutaçãoCompreender a diferença entre failover e switchover é fundamental. Um failoveré uma promoção não planejada desencadeada pela falha do primário atual. Uma alternância deé uma mudança de função planejada e elegante – normalmente realizada antes da manutenção. Patroni apoia ambos.
Mudança planejada# List cluster members
patronictlctl -c /etc/patroni/patroni.yml list
# Perform switchover to a specific node
patronictlctl -c /etc/patroni/patroni.yml switchover \
--master node1 --candidate node2 --force
# Or use the Patroni REST API
curl -s http://10.0.1.10:8008/switchover -XPOST \
-d '{"leader": "node1", "candidate": "node2"}'Durante uma transição, Patroni rebaixa o primário atual para standby, promove o candidato alvo e reconfigura todos os outros standbys para seguir o novo primário. O processo leva de 5 a 15 segundos. O HAProxy detecta a mudança por meio de verificações de integridade e redireciona o tráfego automaticamente.
Sequência de failover automáticoQuando o primário falha inesperadamente, Patroni segue uma sequência precisa para restaurar o serviço. O diagrama a seguir ilustra as etapas.
Prevenção de divisão cerebral
Split-brain – onde dois nós acreditam simultaneamente que são o primário – é o modo de falha mais perigoso em qualquer sistema HA. Patroni evita divisão do cérebro através de vários mecanismos:
- Bloqueio líder baseado em DCS:Apenas um nó pode conter a chave líder no etcd a qualquer momento. A chave possui um TTL e o líder deve renová-la continuamente. Se uma partição de rede isolar o líder do etcd, a chave expirará e o líder será rebaixado.
- Watchdog:Patroni pode configurar um dispositivo watchdog Linux (
/dev/watchdog). Se Patroni perder o acesso ao DCS e não puder confirmar se deve permanecer como líder, o watchdog irá reinicializar ou desligar o nó – um mecanismo de isolamento rígido que garante que o antigo primário não continue aceitando gravações. - pg_rewind:Quando um antigo primário fica on-line novamente, ele pode ter registros WAL que nunca foram replicados. O
pg_rewindretrocede a linha do tempo até o ponto de divergência, permitindo que o nó volte a entrar em espera sem um backup de base completo. A configuraçãouse_pg_rewind: truedo Patroni automatiza isso.
# Enable watchdog in Patroni config
bootstrap:
dcs:
postgresql:
use_pg_rewind: true
parameters:
wal_log_hints: 'on' # required for pg_rewind
# Watchdog configuration
watchdog:
mode: required # 'off', 'automatic', or 'required'
device: /dev/watchdog
safety_margin: 5 # seconds before TTL expiry to trigger watchdogRepmgrcomo alternativa ao Patroni
repmgré outra ferramenta HA popular para PostgreSQL. Ele fornece recursos de gerenciamento de espera, failover automático e alternância. No entanto, é necessária uma abordagem fundamentalmente diferente da de Patroni. repmgr usa um nó testemunha e um daemon (repmgrd) para detecção de falhas em vez de um armazenamento de consenso distribuído. Isso torna a implantação mais simples, mas mais suscetível à divisão do cérebro em cenários complexos de partição de rede.
# repmgr.conf on the primary
node_id=1
node_name='node1'
conninfo='host=10.0.1.10 user=repmgr dbname=repmgr connect_timeout=2'
data_directory='/var/lib/postgresql/16/main'
failover=automatic
promote_command='repmgr standby promote -f /etc/repmgr.conf --log-to-file'
follow_command='repmgr standby follow -f /etc/repmgr.conf --log-to-file --upstream-node-id=%n'
monitoring_history=yes
monitor_interval_secs=5
reconnect_attempts=6
reconnect_interval=10Para novas implantações, Patroni é a escolha recomendada devido às suas garantias mais fortes de prevenção de cérebro dividido e à comunidade de desenvolvimento mais ativa. repmgr continua sendo uma opção razoável para configurações mais simples ou organizações que já investiram na ferramenta.
Padrões de implantação em nuvemImplantaçãoAWS: EC2, EBS e Route53
No AWS, implante cada nó PostgreSQL + Patroni em uma instância EC2 com volumes EBS gp3 ou io2. Use instâncias separadas em várias zonas de disponibilidade para alta disponibilidade. Os nós etcd também devem abranger AZs.
# Terraform sketch for PostgreSQL HA on AWS
resource "aws_instance" "pg_node" {
count = 3
ami = "ami-0abcdef1234567890" # Ubuntu 22.04
instance_type = "r6g.2xlarge" # 8 vCPU, 64GB RAM
subnet_id = aws_subnet.private[count.index].id
vpc_security_group_ids = [aws_security_group.pg_sg.id]
availability_zone = element(["eu-west-1a", "eu-west-1b", "eu-west-1c"], count.index)
root_block_device {
volume_size = 50
volume_type = "gp3"
}
tags = {
Name = "pg-node-${count.index + 1}"
Role = "patroni"
}
}
resource "aws_ebs_volume" "pg_data" {
count = 3
availability_zone = element(["eu-west-1a", "eu-west-1b", "eu-west-1c"], count.index)
size = 500
type = "gp3"
iops = 6000
throughput = 250
encrypted = true
tags = {
Name = "pg-data-${count.index + 1}"
}
}
resource "aws_route53_health_check" "pg_primary" {
count = 3
ip_address = aws_instance.pg_node[count.index].private_ip
port = 8008
type = "HTTP"
resource_path = "/primary"
failure_threshold = 3
request_interval = 10
}Use um Network Load Balancer (NLB) em vez de HAProxy se preferir uma solução gerenciada pelo AWS. O NLB pode usar verificações de integridade do grupo-alvo no Patroni REST API para rotear o tráfego para o primário atual.
ImplantaçãoAzure: VMs, discos gerenciados e Azure LB
No Azure, use VMs Standard_E8s_v5 (com otimização de memória) com discos gerenciados SSD Premium para volumes de dados. Implante em zonas de disponibilidade. O Azure Load Balancer fornece o equivalente ao HAProxy com investigações de integridade no Patroni REST API.
# Azure CLI — create PostgreSQL VM with Managed Disk
az vm create \
--resource-group pg-ha-rg \
--name pg-node-1 \
--image Canonical:0001-com-ubuntu-server-jammy:22_04-lts:latest \
--size Standard_E8s_v5 \
--zone 1 \
--vnet-name pg-vnet \
--subnet pg-subnet \
--nsg pg-nsg \
--admin-username pgadmin \
--ssh-key-value ~/.ssh/id_rsa.pub
az disk create \
--resource-group pg-ha-rg \
--name pg-data-1 \
--size-gb 512 \
--sku Premium_LRS \
--zone 1
az vm disk attach \
--resource-group pg-ha-rg \
--vm-name pg-node-1 \
--name pg-data-1
# Azure Load Balancer health probe for Patroni
az network lb probe create \
--resource-group pg-ha-rg \
--lb-name pg-lb \
--name patroni-primary-probe \
--protocol Http \
--port 8008 \
--path /primary \
--interval 5 \
--threshold 3Implantação do GCP: Compute Engine e Cloud Load Balancing
No GCP, use instâncias n2-highmem-8 (8 vCPU, 64 GB de RAM) com discos permanentes SSD. Distribuir entre zonas dentro de uma região. Use um balanceador de carga TCP/UDP interno com verificações de integridade do Patroni.
# GCP — create instance and persistent disk
gcloud compute instances create pg-node-1 \
--zone=europe-west1-b \
--machine-type=n2-highmem-8 \
--image-family=ubuntu-2204-lts \
--image-project=ubuntu-os-cloud \
--boot-disk-size=50GB \
--network=pg-network \
--subnet=pg-subnet
gcloud compute disks create pg-data-1 \
--zone=europe-west1-b \
--size=500GB \
--type=pd-ssd
gcloud compute instances attach-disk pg-node-1 \
--disk=pg-data-1 \
--zone=europe-west1-b
# Health check for Patroni primary endpoint
gcloud compute health-checks create http patroni-primary-check \
--port=8008 \
--request-path=/primary \
--check-interval=5s \
--timeout=5s \
--unhealthy-threshold=3 \
--healthy-threshold=2Implantação Bare Metal k3scom Rancher e Longhorn
Para organizações que operam seu próprio hardware, a implantação do PostgreSQL HA em bare metal com k3s, Rancher e Longhorn fornece uma infraestrutura totalmente de código aberto e independente da nuvem. k3s é uma distribuição Kubernetes leve que funciona com eficiência em servidores bare metal sem a sobrecarga de uma distribuição Kubernetes completa.
Configuraçãok3s e Longhorn
# Install k3s on the first server node
curl -sfL https://get.k3s.io | K3S_TOKEN=my-cluster-token \
INSTALL_K3S_EXEC="server --cluster-init --disable traefik --disable servicelb" sh -
# Join additional server nodes
curl -sfL https://get.k3s.io | K3S_TOKEN=my-cluster-token \
K3S_URL=https://10.0.0.1:6443 \
INSTALL_K3S_EXEC="server" sh -
# Install Longhorn for distributed block storage
helm repo add longhorn https://charts.longhorn.io
helm install longhorn longhorn/longhorn \
--namespace longhorn-system --create-namespace \
--set defaultSettings.defaultDataPath=/mnt/longhorn \
--set defaultSettings.replicaCount=3 \
--set defaultSettings.storageMinimalAvailablePercentage=15
# Deploy PostgreSQL with Patroni using the Zalando Postgres Operator
helm repo add postgres-operator-charts https://opensource.zalando.com/postgres-operator/charts/postgres-operator
helm install postgres-operator postgres-operator-charts/postgres-operator \
--namespace postgres-system --create-namespace# PostgreSQL cluster manifest for the Zalando Postgres Operator
apiVersion: acid.zalan.do/v1
kind: postgresql
metadata:
name: pg-ha-cluster
namespace: production
spec:
teamId: "platform"
numberOfInstances: 3
volume:
size: 500Gi
storageClass: longhorn
users:
appuser:
- superuser
- createdb
replicator: []
databases:
appdb: appuser
postgresql:
version: "16"
parameters:
shared_buffers: "16GB"
effective_cache_size: "48GB"
work_mem: "256MB"
maintenance_work_mem: "2GB"
max_connections: "200"
max_wal_senders: "10"
wal_level: replica
synchronous_commit: "on"
wal_keep_size: "2GB"
archive_mode: "on"
track_commit_timestamp: "on"
patroni:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
synchronous_mode: true
resources:
requests:
cpu: "4"
memory: 32Gi
limits:
cpu: "8"
memory: 64Gimantido ativo para HAProxy VIP
# /etc/keepalived/keepalived.conf on lb1
vrrp_script chk_haproxy {
script "killall -0 haproxy"
interval 2
weight 2
}
vrrp_instance VI_PG {
state MASTER
interface eth0
virtual_router_id 52
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass pgha_vip_pass
}
virtual_ipaddress {
10.0.0.100/24
}
track_script {
chk_haproxy
}
}Monitorando Replicação PostgreSQL
O monitoramento da integridade da replicação não é negociável na produção. O PostgreSQL oferece diversas visualizações integradas para essa finalidade, e o Prometheus com Grafana fornece a visibilidade e os alertas de longo prazo que você precisa.
Consultas de monitoramento integradas do-- Check replication status on the primary
SELECT
client_addr,
application_name,
state,
sync_state,
sent_lsn,
write_lsn,
flush_lsn,
replay_lsn,
(sent_lsn - replay_lsn) AS replication_lag_bytes,
write_lag,
flush_lag,
replay_lag
FROM pg_stat_replication;
-- Check replication slot status
SELECT
slot_name,
slot_type,
active,
wal_status,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS slot_lag
FROM pg_replication_slots;
-- Check standby recovery status (run on standby)
SELECT
pg_is_in_recovery() AS is_standby,
pg_last_wal_receive_lsn() AS last_received,
pg_last_wal_replay_lsn() AS last_replayed,
pg_last_xact_replay_timestamp() AS last_replayed_timestamp,
EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp()))::int AS replay_lag_seconds;
-- Monitor WAL generation rate
SELECT
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0')) AS total_wal_generated,
pg_size_pretty(sum(size)) AS wal_directory_size
FROM pg_ls_waldir();
-- Check for long-running queries that could block replication
SELECT
pid,
now() - pg_stat_activity.query_start AS duration,
query,
state
FROM pg_stat_activity
WHERE (now() - pg_stat_activity.query_start) > interval '5 minutes'
AND state != 'idle'
ORDER BY duration DESC;PilhaPrometheus e Grafana
Opostgres_exporterexpõe métricas PostgreSQL no formato Prometheus. Combinado com opatroni_exporter, você obtém visibilidade total do desempenho do banco de dados e do estado do cluster HA.
# Deploy postgres_exporter as a sidecar or standalone
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
# Custom queries for postgres_exporter
# /etc/postgres_exporter/queries.yaml
pg_replication_lag:
query: |
SELECT
CASE WHEN pg_is_in_recovery() THEN
EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp()))::float
ELSE 0 END AS lag_seconds
master: true
metrics:
- lag_seconds:
usage: "GAUGE"
description: "Replication lag in seconds"
pg_replication_slots:
query: |
SELECT
slot_name,
active::int AS active,
pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)::float AS slot_lag_bytes
FROM pg_replication_slots
master: true
metrics:
- slot_name:
usage: "LABEL"
- active:
usage: "GAUGE"
description: "Whether the slot is active"
- slot_lag_bytes:
usage: "GAUGE"
description: "Slot lag in bytes"# PrometheusRule for PostgreSQL HA alerts
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: postgresql-ha-alerts
namespace: monitoring
spec:
groups:
- name: postgresql-replication
rules:
- alert: PostgreSQLReplicationLagHigh
expr: pg_replication_lag_seconds > 30
for: 5m
labels:
severity: warning
annotations:
summary: "PostgreSQL replication lag exceeds 30s on {{ $labels.instance }}"
- alert: PostgreSQLReplicationSlotInactive
expr: pg_replication_slots_active == 0
for: 5m
labels:
severity: critical
annotations:
summary: "Replication slot {{ $labels.slot_name }} is inactive"
- alert: PostgreSQLReplicationSlotLagHigh
expr: pg_replication_slots_slot_lag_bytes > 1073741824
for: 10m
labels:
severity: warning
annotations:
summary: "Replication slot lag exceeds 1GB on {{ $labels.slot_name }}"
- alert: PatroniClusterUnhealthy
expr: patroni_cluster_members_count < 3
for: 2m
labels:
severity: critical
annotations:
summary: "Patroni cluster has fewer than 3 members"Parâmetros de ajuste de produçãoA configuração padrão doPostgreSQL é conservadora, ajustada para um pequeno ambiente de hospedagem compartilhada. Clusters de alta disponibilidade de produção exigem ajuste cuidadoso de parâmetros de replicação, memória e WAL. A tabela a seguir resume as configurações mais importantes para um servidor de 64 GB de RAM com armazenamento NVMe.
# postgresql.conf — Production HA tuning
# === Replication ===
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
wal_keep_size = 4GB
synchronous_commit = on
synchronous_standby_names = 'ANY 1 (standby1, standby2)'
track_commit_timestamp = on
wal_log_hints = on
# === WAL ===
min_wal_size = 1GB
max_wal_size = 8GB
wal_buffers = 64MB
wal_compression = zstd
archive_mode = on
archive_timeout = 300
checkpoint_completion_target = 0.9
checkpoint_timeout = 15min
# === Memory ===
shared_buffers = 16GB # 25% of RAM
effective_cache_size = 48GB # 75% of RAM
work_mem = 256MB # per-operation sort/hash memory
maintenance_work_mem = 2GB # for VACUUM, CREATE INDEX
huge_pages = try
# === Connections ===
max_connections = 200 # use PgBouncer for higher client counts
superuser_reserved_connections = 5
# === Query Performance ===
random_page_cost = 1.1 # SSD storage
effective_io_concurrency = 200 # NVMe SSD
default_statistics_target = 500
jit = on
# === Logging ===
log_min_duration_statement = 500 # log queries > 500ms
log_checkpoints = on
log_connections = on
log_disconnections = on
log_lock_waits = on
log_temp_files = 0
log_autovacuum_min_duration = 0
# === Autovacuum ===
autovacuum_max_workers = 4
autovacuum_naptime = 30s
autovacuum_vacuum_cost_limit = 2000
autovacuum_vacuum_scale_factor = 0.05
autovacuum_analyze_scale_factor = 0.02Parâmetros Patroni DCS de ajusteO relacionamento entre os parâmetrosttl,loop_waiteretry_timeoutda Patroni impacta diretamente a velocidade de failover e o risco de falsos positivos. Um TTL mais curto significa detecção de failover mais rápida, mas aumenta o risco de failovers desnecessários durante breves interrupções na rede.
# Conservative (production default)
ttl: 30
loop_wait: 10
retry_timeout: 10
# Failover detection: ~30-40 seconds
# Aggressive (low-latency failover)
ttl: 15
loop_wait: 5
retry_timeout: 5
# Failover detection: ~15-20 seconds
# Warning: Higher risk of false failovers in unstable networksEngenharia de caos e testes de failoverUm sistema de failover que nunca foi testado é um sistema que não funciona. A engenharia do caos aplica falhas controladas para validar se sua configuração de HA se comporta corretamente sob condições reais de falha. Cada cluster Patroni deve ser submetido a exercícios regulares de failover.
Manual de teste de failover# 1. Verify cluster health before testing
patronictlctl -c /etc/patroni/patroni.yml list
+----------+---------+---------+----+-----------+
| Member | Host | Role | TL | Lag in MB |
+----------+---------+---------+----+-----------+
| node1 | 10.0.1.10| Leader | 5 | |
| node2 | 10.0.1.11| Replica | 5 | 0 |
| node3 | 10.0.1.12| Replica | 5 | 0 |
+----------+---------+---------+----+-----------+
# 2. Simulate primary crash (on node1)
sudo systemctl stop patroni
# Or more aggressive: sudo kill -9 $(pgrep -f patroni)
# 3. Monitor failover (from any node with patronictl)
watch -n 1 'patronictl -c /etc/patroni/patroni.yml list'
# 4. Verify new leader is elected (within 30-45 seconds)
# Expected: node2 or node3 promoted to Leader
# 5. Test write availability through HAProxy
PGPASSWORD=app_password psql -h haproxy-host -p 5000 -U appuser -d appdb \
-c "INSERT INTO health_check (ts) VALUES (now()) RETURNING *;"
# 6. Restart the former primary
sudo systemctl start patroni
# Patroni will use pg_rewind to rejoin as a replica
# 7. Verify the former primary rejoins as replica
patronictlctl -c /etc/patroni/patroni.yml listTeste de partição de rede# Simulate network partition on the primary using iptables
# Block all traffic to etcd from the primary
sudo iptables -A OUTPUT -d 10.0.2.10 -j DROP
sudo iptables -A OUTPUT -d 10.0.2.11 -j DROP
sudo iptables -A OUTPUT -d 10.0.2.12 -j DROP
# Expected behaviour:
# 1. Primary loses DCS access
# 2. Leader key TTL expires
# 3. Primary demotes itself (with watchdog, node may reboot)
# 4. Standby acquires leader lock and promotes
# 5. After clearing iptables rules, former primary rejoins as replica
# Clean up
sudo iptables -D OUTPUT -d 10.0.2.10 -j DROP
sudo iptables -D OUTPUT -d 10.0.2.11 -j DROP
sudo iptables -D OUTPUT -d 10.0.2.12 -j DROPTeste automatizado de caoscom Toxiproxy
# Run Toxiproxy alongside your Patroni cluster
# Create proxies for etcd and replication connections
toxiproxy-cli create etcd_proxy -l 0.0.0.0:12379 -u 10.0.2.10:2379
toxiproxy-cli create pg_repl_proxy -l 0.0.0.0:15432 -u 10.0.1.10:5432
# Add latency to etcd connections (simulates degraded network)
toxiproxy-cli toxic add etcd_proxy -t latency -a latency=500 -a jitter=200
# Add bandwidth limit to replication (simulates WAN replication)
toxiproxy-cli toxic add pg_repl_proxy -t bandwidth -a rate=1024
# Completely sever the connection (simulates network partition)
toxiproxy-cli toxic add etcd_proxy -t timeout -a timeout=0
# Monitor Patroni behaviour and verify correct failover
watch -n 2 'curl -s http://10.0.1.10:8008/patroni | python3 -m json.tool'Script de validação contínua#!/bin/bash
# continuous_ha_check.sh — Run during chaos tests to measure availability
HAPROXY_HOST="10.0.0.100"
WRITE_PORT=5000
READ_PORT=5001
DATABASE="appdb"
USER="appuser"
LOGFILE="/var/log/ha_test_$(date +%Y%m%d_%H%M%S).log"
write_count=0
write_fail=0
read_count=0
read_fail=0
while true; do
ts=$(date '+%Y-%m-%d %H:%M:%S.%3N')
# Test write path
if PGPASSWORD=app_password psql -h $HAPROXY_HOST -p $WRITE_PORT \
-U $USER -d $DATABASE -c "SELECT 1" &>/dev/null; then
((write_count++))
else
((write_fail++))
echo "$ts WRITE_FAIL total_fails=$write_fail" >> $LOGFILE
fi
# Test read path
if PGPASSWORD=app_password psql -h $HAPROXY_HOST -p $READ_PORT \
-U $USER -d $DATABASE -c "SELECT 1" &>/dev/null; then
((read_count++))
else
((read_fail++))
echo "$ts READ_FAIL total_fails=$read_fail" >> $LOGFILE
fi
total=$((write_count + write_fail))
if (( total % 100 == 0 )); then
write_avail=$(echo "scale=2; $write_count * 100 / $total" | bc)
read_total=$((read_count + read_fail))
read_avail=$(echo "scale=2; $read_count * 100 / $read_total" | bc)
echo "$ts Writes: ${write_avail}% ($write_count/$total) Reads: ${read_avail}% ($read_count/$read_total)"
fi
sleep 0.5
doneAvançado: Replicação em Cascata e Standbys Atrasados
Para clusters grandes, a replicação em cascata reduz a carga no primário. Em vez de todos os standbys replicarem diretamente do primário, alguns standbys replicam de outros standbys. Isso cria uma topologia em árvore onde o primário alimenta dois standbys e esses standbys alimentam standbys downstream adicionais.
# postgresql.auto.conf on a cascading standby
primary_conninfo = 'host=standby1-host port=5432 user=replicator application_name=cascade1'
primary_slot_name = 'cascade1_slot'Umem espera atrasada Oaplica intencionalmente registros WAL com um atraso - normalmente de 1 a 4 horas. Isso fornece uma defesa contra erros lógicos (exclusões acidentais, migrações incorretas) que são imediatamente replicados para esperas síncronas. Se ocorrer um desastre, você poderá interromper a reprodução do WAL no modo de espera atrasado e recuperar os dados anteriores ao erro.
# postgresql.conf on delayed standby
recovery_min_apply_delay = '1h'Estratégias de cadeia de conexãoOs aplicativos que se conectam a um cluster gerenciado pelo Patroni devem sempre se conectar por meio do HAProxy ou usar a cadeia de conexão multi-host integrada do PostgreSQL comtarget_session_attrs. Isso fornece failover do lado do cliente sem depender de um balanceador de carga.
# Multi-host connection string with target_session_attrs
# The client tries each host in order and connects to the one matching the target attribute
postgresql://appuser:password@node1:5432,node2:5432,node3:5432/appdb?target_session_attrs=read-write&sslmode=require
# For read-only connections
postgresql://appuser:password@node1:5432,node2:5432,node3:5432/appdb?target_session_attrs=prefer-standby&sslmode=requireEsta abordagem funciona bem para aplicativos que não podem ser facilmente reconfigurados para apontar para um HAProxy VIP. A biblioteca cliente PostgreSQL (libpq) lida com o failover de forma transparente.
Proteção de segurançaUm cluster PostgreSQL HA de produção deve impor criptografia em trânsito e em repouso, usar autenticação forte e limitar a exposição da rede.
# Enable TLS in postgresql.conf
ssl = on
ssl_cert_file = '/etc/postgresql/certs/server.crt'
ssl_key_file = '/etc/postgresql/certs/server.key'
ssl_ca_file = '/etc/postgresql/certs/ca.crt'
ssl_min_protocol_version = 'TLSv1.3'
# Require TLS for all connections in pg_hba.conf
hostssl replication replicator 10.0.0.0/16 scram-sha-256
hostssl all all 10.0.0.0/16 scram-sha-256
# etcd TLS
# In Patroni config
etcd3:
hosts:
- 10.0.2.10:2379
- 10.0.2.11:2379
- 10.0.2.12:2379
protocol: https
cacert: /etc/patroni/certs/etcd-ca.crt
cert: /etc/patroni/certs/etcd-client.crt
key: /etc/patroni/certs/etcd-client.keyEstratégia de backuppara clusters HA
Uma estratégia de backup abrangente para clusters Patroni deve incluir arquivamento WAL contínuo, backups completos regulares e backups diferenciais ou incrementais entre backups completos. pgBackRest é a ferramenta recomendada para gerenciamento de backup PostgreSQL de produção.
# Schedule backups via cron
# Full backup weekly (Sunday 2 AM)
0 2 * * 0 pgbackrest --stanza=pg-ha-cluster --type=full backup
# Differential backup daily (2 AM, Mon-Sat)
0 2 * * 1-6 pgbackrest --stanza=pg-ha-cluster --type=diff backup
# Verify backup integrity
pgbackrest --stanza=pg-ha-cluster --set=latest info
# Verify backup can be restored (dry run)
pgbackrest --stanza=pg-ha-cluster --set=latest verify
# List all backups
pgbackrest --stanza=pg-ha-cluster info
full backup: 20260412-020000F
timestamp: 2026-04-12 02:00:00 +0000
wal start/stop: 000000050000000000000040 / 000000050000000000000042
database size: 150GB, backup size: 150GB
repository size: 45GB (compressed)
diff backup: 20260412-020000F_20260413-020000D
timestamp: 2026-04-13 02:00:00 +0000
database size: 151GB, backup size: 2.1GB
repository size: 650MB (compressed)Resumo do Runbook OperacionalCada equipe que executa um cluster Patroni deve manter um runbook cobrindo os seguintes cenários. Ter procedimentos documentados e testados transforma uma interrupção estressante em uma operação de rotina.
# === Quick Reference Commands ===
# Cluster status
patronictlctl -c /etc/patroni/patroni.yml list
patronictlctl -c /etc/patroni/patroni.yml history
# Planned switchover
patronictlctl -c /etc/patroni/patroni.yml switchover --master node1 --candidate node2
# Restart PostgreSQL on a specific node (rolling restart)
patronictlctl -c /etc/patroni/patroni.yml restart pg-ha-cluster node2
# Reload PostgreSQL configuration without restart
patronictlctl -c /etc/patroni/patroni.yml reload pg-ha-cluster
# Pause automatic failover (during maintenance)
patronictlctl -c /etc/patroni/patroni.yml pause
# Resume automatic failover
patronictlctl -c /etc/patroni/patroni.yml resume
# Edit DCS configuration (applies to all nodes)
patronictlctl -c /etc/patroni/patroni.yml edit-config
# Reinitialise a failed replica
patronictlctl -c /etc/patroni/patroni.yml reinit pg-ha-cluster node3
# Check Patroni REST API directly
curl -s http://10.0.1.10:8008/patroni | python3 -m json.tool
curl -s http://10.0.1.10:8008/cluster | python3 -m json.toolComparação de desempenho doAntes de entrar em produção, compare seu cluster de HA para estabelecer o desempenho da linha de base e verifique se a latência de replicação síncrona é aceitável para sua carga de trabalho.
# Benchmark with pgbench — initialise test data
pgbench -i -s 100 -h haproxy-host -p 5000 -U appuser appdb
# Run write-heavy benchmark (measures sync replication impact)
pgbench -h haproxy-host -p 5000 -U appuser -c 32 -j 8 -T 300 appdb
# Compare with async: temporarily set synchronous_commit = off
# Run read-only benchmark through read replica port
pgbench -h haproxy-host -p 5001 -U appuser -c 64 -j 16 -T 300 -S appdb
# Measure failover impact on transactions
# Run pgbench in background, then trigger a failover
pgbench -h haproxy-host -p 5000 -U appuser -c 8 -j 4 -T 600 appdb &
sleep 60 && patronictl switchover --master node1 --candidate node2 --forceConclusãoA alta disponibilidade nativa doPostgreSQL com Patroni não é uma solução de ferramenta única – é um sistema integrado de replicação de streaming, consenso distribuído, roteamento de conexão, pooling de conexão, arquivamento WAL, monitoramento e disciplina operacional. Cada camada aborda um modo de falha específico: a replicação de streaming lida com a redundância de dados, o Patroni lida com a coordenação automática de failover, o etcd fornece o consenso distribuído necessário para a eleição do líder sem divisão cerebral, o HAProxy roteia conexões para o líder correto, o PgBouncer gerencia a sobrecarga de conexão em escala e o arquivamento WAL com pgBackRest fornece a última linha de defesa contra erros lógicos e recuperação de desastres.
Os padrões de implantação variam entre ambientes — AWS com NLB e Route53, Azure com zonas de disponibilidade e balanceador de carga Azure, GCP com grupos de instâncias gerenciadas regionais ou bare metal com k3s, Longhorn e Keepalived — mas a arquitetura principal permanece a mesma. Três ou mais nós PostgreSQL gerenciados pelo Patroni, apoiados por um cluster etcd de três nós, liderado por um balanceador de carga que segue os endpoints de verificação de integridade do Patroni.
O investimento mais importante que você pode fazer não está na configuração, mas nos testes. Execute exercícios de failover mensalmente. Injete partições de rede. Mate processos inesperadamente. Meça o tempo de recuperação e a perda de dados. Crie painéis que mostram o atraso de replicação, a taxa de geração de WAL, a saturação do pool de conexões e a integridade do DCS em tempo real. A confiança que você ganha com testes sistemáticos é o que separa um cluster que sobrevive à sua primeira interrupção real daquele que transforma uma falha de servidor em um incidente com impacto nos negócios.
PostgreSQL oferece todas as primitivas de replicação. Patroni dá a você a orquestração. etcd fornece o consenso. Seu trabalho é conectá-los corretamente, ajustá-los à sua carga de trabalho e validá-los continuamente. Este guia forneceu os projetos — agora crie, teste e opere com confiança.