Alta disponibilidad nativa de PostgreSQL: Patroni, replicación de streaming y estrategias de conmutación por error de producción
PostgreSQL HA nativo con Patroni, replicación de streaming y HAProxy
Ejecutar una única instancia PostgreSQL es sencillo hasta que la primera interrupción no planificada le recuerda que una base de datos es tan valiosa como su disponibilidad. Las fallas de disco, los pánicos en el kernel, las particiones de red y las actualizaciones fallidas no son riesgos teóricos: son certezas operativas en un período de tiempo suficientemente largo. PostgreSQL no viene con conmutación por error automática incorporada, pero proporciona todas las primitivas de replicación necesarias para construir un clúster de alta disponibilidad. Patroni, un marco de HA de código abierto mantenido por Zalando, organiza esas primitivas en un sistema de conmutación por error de nivel de producción que ha sido probado a escala en miles de clústeres PostgreSQL en todo el mundo.
Este artículo es una guía de ingeniería detallada. Cubriremos la replicación de transmisión de PostgreSQL (síncrona y asíncrona), la arquitectura y configuración de Patroni, etc. como un almacén de configuración distribuido, HAProxy para enrutamiento de conexiones con división de lectura y escritura, PgBouncer para agrupación de conexiones, archivado WAL y recuperación de un punto en el tiempo, replicación lógica para sincronización selectiva de datos, pg_basebackup para aprovisionamiento de espera inicial, repmgr como una alternativa a Patroni, patrones de implementación específicos de la nube para AWS, Azure y GCP, implementación básica de k3s con Rancher y Longhorn, monitoreo con pg_stat_replication y Prometheus/Grafana, procedimientos de conmutación versus conmutación por error, prevención de cerebro dividido, ajuste de producción e ingeniería del caos para la validación de la conmutación por error.
PostgreSQL Fundamentos de replicación de streaming
La replicación de streaminges la columna vertebral de la alta disponibilidad de PostgreSQL. Funciona enviando continuamente registros de registro de escritura anticipada (WAL) desde un servidor principal a uno o más servidores en espera. El standby aplica esos registros WAL en tiempo real, manteniendo una copia casi idéntica de los datos del primario. Este mecanismo se introdujo en PostgreSQL 9.0 y se ha perfeccionado en cada versión posterior.
Hay dos modos de replicación de streaming:asíncrono,ysíncrono,. En modo asíncrono, el primario no espera a que los standby confirmen la recepción de los registros WAL antes de confirmar una transacción. Esto proporciona el máximo rendimiento de escritura, pero introduce una ventana de posible pérdida de datos: si el primario falla antes de que un standby haya recibido el WAL más reciente, esas transacciones se pierden. En modo síncrono, el principal espera al menos un modo de espera para confirmar que los registros WAL se han escrito en un almacenamiento duradero antes de informar una transacción como confirmada. Esto elimina la pérdida de datos a costa de una mayor latencia de confirmación, ya que cada escritura debe pasar de ida y vuelta a un modo de espera.
La elección entre replicación síncrona y asíncrona no es binaria. PostgreSQL admitesynchronous_commita nivel de sesión, por lo que las cargas de trabajo sensibles a la latencia pueden optar por confirmaciones asincrónicas, mientras que las transacciones financieras críticas utilizan confirmaciones sincrónicas dentro del mismo clúster.
Configuración del primario para replicación
El servidor primario debe configurarse para generar registros WAL a un nivel suficiente para la replicación y permitir conexiones en espera. Las siguientes configuraciones depostgresql.confson esenciales.
# 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 = 5432La autenticación para conexiones de replicación se maneja enpg_hba.conf. Las conexiones de replicación utilizan un tipo de conexión dedicada.
# 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-256Cree el usuario de replicación en el principal.
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'strong_replication_password';Aprovisionamiento de un modo de espera con pg_basebackup
La utilidadpg_basebackupcrea una copia física del directorio de datos del primario, que se convierte en el punto de partida para un nuevo modo de espera. Maneja la copia de seguridad base y la transmisión WAL de forma atómica, por lo que la copia resultante es 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.confEl indicador-Res crítico: escribeprimary_conninfoenpostgresql.auto.confy creastandby.signal, lo que le indica a PostgreSQL que se inicie en modo de espera. En PostgreSQL 12 y posteriores,recovery.confse reemplaza por estos dos 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'Cree la ranura de replicación en el principal antes de iniciar el modo de espera, para evitar que WAL se limpie antes de que el modo de espera pueda consumirlo.
SELECT pg_create_physical_replication_slot('standby1_slot');
SELECT pg_create_physical_replication_slot('standby2_slot');Patrón: Orquestación HA automatizada
La replicación de Streamingle brinda redundancia de datos, pero no le brinda conmutación por error automática. Si el primario falla, alguien (un operador humano o un sistema de automatización) debe promover un respaldo a primario, reconfigurar los respaldos restantes para que sigan el nuevo primario y actualizar el enrutamiento de la conexión. Patroni automatiza todo esto.
Patroni es un demonio Python que se ejecuta junto con cada instancia PostgreSQL. Utiliza un almacén de configuración distribuida (DCS), normalmente etcd, pero también ZooKeeper o Consul, para coordinar la elección del líder y el estado del clúster. Cada nodo Patroni escribe continuamente su estado de salud en el DCS. Cuando el líder (primario) no puede renovar su clave DCS dentro del TTL configurado, Patroni inicia una elección de líder entre los recursos en espera sanos. El ganador asciende a primario y los nodos restantes se reconfiguran como reservas del nuevo primario, todo automáticamente, generalmente en 10 a 30 segundos.
Patrón YAML Configuración
Patroni se configura a través de un archivo YAML que define la conexión DCS, los parámetros PostgreSQL, el comportamiento de replicación y la configuración de arranque. La siguiente es una configuración de nivel de producción para el nodo principal.
# /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: falseLos nodos en espera utilizan una configuración idéntica con sus propios valoresname,connect_addressylisten. Patroni se encarga del resto: detecta si un nodo debe ser el líder o una réplica según el estado del DCS y configura PostgreSQL en consecuencia.
etcd como almacén de configuración distribuida
etcd es el sistema nervioso de un grupo Patroni. Almacena la identidad del líder actual, la topología del clúster, la configuración deseada y el estado de salud de cada nodo. Un clúster etcd de tres nodos es el mínimo para la producción, ya que tolera la falla de un nodo mientras se mantiene el quórum.
# 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 implementaciones de producción, habilite TLS entre pares de etcd y entre clientes de etcd y Patroni. El tráfico etcd no cifrado expone las credenciales y la configuración del clúster a atacantes a nivel de red.
HAProxy para enrutamiento de conexión
Patroni expone un REST API en cada nodo (puerto 8008 de forma predeterminada) que informa si el nodo es el líder actual o una réplica. HAProxy utiliza estos puntos finales de verificación de estado para enrutar el tráfico: las escrituras van al líder, las lecturas van a réplicas saludables. Esto le brinda una división automática de lectura y escritura sin ningún cambio a nivel de aplicación.
# /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 10sEl punto final/primarydevuelve HTTP 200 solo en el líder Patroni actual. El terminal/replicadevuelve 200 en espera en buen estado. Cuando se produce una conmutación por error, el nuevo primario comienza a devolver 200 en/primaryy HAProxy redirige automáticamente el tráfico de escritura, generalmente dentro de un único intervalo de verificación de estado (3 segundos). La directivaon-marked-down shutdown-sessionsfinaliza inmediatamente las conexiones existentes a un primario fallido, lo que obliga a los clientes a volver a conectarse al nuevo líder.
PgBouncer para agrupación de conexiones
PostgreSQL crea un nuevo proceso de backend para cada conexión de cliente. A escala (cientos o miles de microservicios, cada uno de los cuales mantiene grupos de conexiones), la sobrecarga de la creación de procesos y el consumo de memoria se vuelve significativa. PgBouncer se encuentra entre la aplicación y PostgreSQL, manteniendo un grupo de conexiones del lado del servidor y multiplexando conexiones de clientes en ellas.
# /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 = 60Cuando se usa con Patroni, PgBouncer generalmente se ubica en cada nodo PostgreSQL o en los nodos HAProxy. El modo de grupotransactiones la mejor opción para la mayoría de las cargas de trabajo: asigna una conexión de servidor durante la duración de una transacción y la devuelve al grupo entre transacciones. Esto es mucho más eficiente que el modosession, que mantiene una conexión para toda la sesión del cliente.
Archivado WAL y recuperación en un momento dado
La replicación de streamingprotege contra fallas del servidor, pero no protege contra errores lógicos: unDROP TABLEaccidental o una migración de aplicación incorrecta se replica en todos los dispositivos en espera de inmediato. El archivado WAL combinado con la recuperación en un momento dado (PITR) le permite restaurar a cualquier momento antes de que ocurriera el error.
copia segmentos WAL completos en un archivo duradero, generalmente un depósito S3, un montaje NFS o un servidor de respaldo dedicado. Herramientas comopgBackRestyWAL-Gmanejan el archivado de manera eficiente con compresión, cifrado y transferencia 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 realizar una recuperación en un momento dado, especifique una marca de tiempo 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 \
restoreReplicación lógica para sincronización selectiva de datos
Mientras que la replicación de transmisión crea una copia física exacta de todo el clúster de base de datos, la replicación lógica opera a nivel de tabla, replicando tablas individuales o subconjuntos de datos entre instancias PostgreSQL independientes. Esto es útil para actualizaciones de versiones principales sin tiempo de inactividad, réplicas de lectura entre regiones que solo necesitan tablas específicas, alimentación de almacenes de datos y distribución de datos multiinquilino.
# 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;La replicación lógica se puede ejecutar junto con la replicación de transmisión. Un patrón común es utilizar replicación de streaming para HA (conmutación por error física rápida) y replicación lógica para réplicas de análisis entre regiones que solo necesitan un subconjunto de tablas.
Replicación de streaming multirregional
Para recuperación ante desastres y rendimiento de lectura global, los clústeres PostgreSQL pueden abarcar varias regiones. El patrón estándar es la replicación sincrónica dentro de una región (para una pérdida de datos cero en la conmutación por error local) y la replicación asincrónica entre regiones (para evitar penalizaciones de latencia entre regiones en cada escritura). Cada región tiene su propio HAProxy para enrutamiento de lectura local.
Procedimientos de conmutación y conmutación por error
Es fundamental comprender la diferencia entre conmutación por error y conmutación. Una conmutación por error dees una promoción no planificada provocada por el error del servidor primario actual. Un cambio deaes un cambio de función planificado y elegante, que normalmente se realiza antes del mantenimiento. Patroni apoya a ambos.
Conmutación planificada
# 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 un cambio, Patroni degrada el principal actual a un candidato de reserva, promueve al candidato objetivo y reconfigura todos los demás candidatos para seguir a la nueva primaria. El proceso dura entre 5 y 15 segundos. HAProxy detecta el cambio mediante comprobaciones de estado y redirige el tráfico automáticamente.
Secuencia de conmutación por error automática
Cuando el primario falla inesperadamente, Patroni sigue una secuencia precisa para restaurar el servicio. El siguiente diagrama ilustra los pasos.
Prevención de cerebro dividido
El cerebro dividido, donde dos nodos creen simultáneamente que son el principal, es el modo de falla más peligroso en cualquier sistema HA. Patroni previene la división del cerebro mediante varios mecanismos:
- Bloqueo líder basado en DCS:Solo un nodo puede contener la clave líder en etcd en cualquier momento. La clave tiene un TTL y el líder debe renovarla continuamente. Si una partición de red aísla al líder de etcd, la clave caduca y el líder se degrada a sí mismo.
- Watchdog:Patroni puede configurar un dispositivo de vigilancia Linux (
/dev/watchdog). Si Patroni pierde el acceso al DCS y no puede confirmar que debe seguir siendo líder, el organismo de control reiniciará o apagará el nodo, un mecanismo de protección estricto que garantiza que la antigua primaria no siga aceptando escrituras. - pg_rewind:Cuando un antiguo primario vuelve a estar en línea, es posible que tenga registros WAL que nunca se replicaron.
pg_rewindrebobina la línea de tiempo hasta el punto de divergencia, lo que permite que el nodo se reincorpore como modo de espera sin una copia de seguridad completa de la base. La configuraciónuse_pg_rewind: truede Patroni automatiza esto.
# 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 watchdogrepmgr como alternativa a Patroni
repmgres otra herramienta HA popular para PostgreSQL. Proporciona capacidades de gestión de espera, conmutación por error automática y conmutación. Sin embargo, adopta un enfoque fundamentalmente diferente al de Patroni. repmgr utiliza un nodo testigo y un demonio (repmgrd) para la detección de fallas en lugar de un almacén de consenso distribuido. Esto hace que sea más sencillo de implementar, pero más susceptible a la división del cerebro en escenarios complejos de partición de red.
# 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 nuevas implementaciones, Patroni es la opción recomendada debido a sus mayores garantías de prevención de cerebro dividido y su comunidad de desarrollo más activa. repmgr sigue siendo una opción razonable para configuraciones más simples u organizaciones que ya han invertido en la herramienta.
Patrones de implementación en la nube
Implementación deAWS: EC2, EBS y Route53
En AWS, implemente cada nodo PostgreSQL + Patroni en una instancia EC2 con volúmenes EBS gp3 o io2. Utilice instancias independientes en varias zonas de disponibilidad para HA. Los nodos etcd también deben abarcar las AZ.
# 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
}Utilice un equilibrador de carga de red (NLB) en lugar de HAProxy si prefiere una solución administrada por AWS. El NLB puede utilizar comprobaciones de estado del grupo objetivo en comparación con Patroni REST API para enrutar el tráfico al principal actual.
Implementación deAzure: máquinas virtuales, discos administrados y Azure LB
En Azure, utilice máquinas virtuales Standard_E8s_v5 (optimizadas para memoria) con discos administrados SSD Premium para volúmenes de datos. Implemente en zonas de disponibilidad. Azure Load Balancer proporciona el equivalente de HAProxy con sondas de estado contra 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 3Implementación deGCP: Compute Engine y equilibrio de carga en la nube
En GCP, use instancias n2-highmem-8 (8 vCPU, 64 GB de RAM) con discos persistentes SSD. Distribuir entre zonas dentro de una región. Utilice un equilibrador de carga TCP/UDP interno con comprobaciones de estado de 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=2Implementación deBare Metal k3s con Rancher y Longhorn
Para las organizaciones que operan su propio hardware, la implementación de PostgreSQL HA en bare metal con k3s, Rancher y Longhorn proporciona una infraestructura totalmente de código abierto e independiente de la nube. k3s es una distribución Kubernetes liviana que se ejecuta de manera eficiente en servidores bare metal sin la sobrecarga de una distribución Kubernetes completa.
k3s y configuración de 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: 64GiKeepalived 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
}
}Monitoreo PostgreSQL Replicación
La supervisión del estado de la replicación no es negociable en producción. PostgreSQL proporciona varias vistas integradas para este propósito, y Prometheus con Grafana proporciona la visibilidad y las alertas a largo plazo que necesita.
Consultas de monitoreo integradas
-- 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;Pila Prometheus y Grafana
Elpostgres_exporterexpone métricas PostgreSQL en formato Prometheus. Combinado conpatroni_exporter, obtiene visibilidad completa tanto del rendimiento de la base de datos como del estado del clúster 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 producción deLa configuración predeterminada dePostgreSQL es conservadora y está adaptada a un entorno de alojamiento compartido pequeño. Los clústeres de alta disponibilidad de producción requieren un ajuste cuidadoso de los parámetros de replicación, memoria y WAL. La siguiente tabla resume las configuraciones más importantes para un servidor de 64 GB de RAM con almacenamiento 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.02Sintonización de parámetros Patroni DCS
La relación entre los parámetrosttl,loop_waityretry_timeoutde Patroni afecta directamente la velocidad de conmutación por error y el riesgo de falsos positivos. Un TTL más corto significa una detección de conmutación por error más rápida, pero aumenta el riesgo de conmutaciones por error innecesarias durante breves interrupciones de la red.
# 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 networksIngeniería del caos y pruebas de conmutación por error
Un sistema de conmutación por error que nunca ha sido probado es un sistema que no funciona. La ingeniería del caos aplica fallas controladas para validar que su configuración de HA se comporta correctamente en condiciones de falla reales. Cada clúster de Patroni debe someterse a simulacros de conmutación por error periódicamente.
Guía de prueba de conmutación por error# 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 listPrueba de partición de red
# 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 DROPPruebas de caos automatizadas con 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 validación continua
#!/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
doneAvanzado: Replicación en cascada y esperas retardadas
Para clústeres grandes, la replicación en cascada reduce la carga en el principal. En lugar de que todos los recursos en espera se repliquen directamente desde el principal, algunos recursos en espera se replican desde otros recursos en espera. Esto crea una topología de árbol donde el primario alimenta dos recursos en espera y esos recursos en espera alimentan a recursos en espera descendentes adicionales.
# postgresql.auto.conf on a cascading standby
primary_conninfo = 'host=standby1-host port=5432 user=replicator application_name=cascade1'
primary_slot_name = 'cascade1_slot'Un modo de espera retardado.aplica intencionalmente registros WAL con un retraso de tiempo, generalmente de 1 a 4 horas. Esto proporciona una defensa contra errores lógicos (eliminaciones accidentales, migraciones incorrectas) que se replican inmediatamente en modos de espera sincrónicos. Si ocurre un desastre, puede detener la reproducción de WAL en el modo de espera retrasado y recuperar los datos anteriores al error.
# postgresql.conf on delayed standby
recovery_min_apply_delay = '1h'Estrategias de cadena de conexión
Las aplicaciones que se conectan a un clúster administrado por Patroni siempre deben conectarse a través de HAProxy o usar la cadena de conexión de múltiples hosts incorporada de PostgreSQL contarget_session_attrs. Esto proporciona conmutación por error del lado del cliente sin depender de un equilibrador 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=requireEste enfoque funciona bien para aplicaciones que no se pueden reconfigurar fácilmente para que apunten a un HAProxy VIP. La biblioteca cliente PostgreSQL (libpq) maneja la conmutación por error de forma transparente.
Refuerzo de seguridad
Un clúster PostgreSQL HA de producción debe aplicar el cifrado en tránsito y en reposo, utilizar una autenticación sólida y limitar la exposición de la red.
# 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.keyEstrategia de respaldopara clústeres HA
Una estrategia de copia de seguridad integral para los clústeres de Patroni debe incluir archivado WAL continuo, copias de seguridad completas periódicas y copias de seguridad diferenciales o incrementales entre copias de seguridad completas. pgBackRest es la herramienta recomendada para la gestión de copias de seguridad de producción PostgreSQL.
# 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)Resumen del runbook operativoCada equipo que ejecuta un clúster Patroni debe mantener un runbook que cubra los siguientes escenarios. Tener procedimientos documentados y probados transforma una interrupción estresante en una operación de rutina.
# === 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.toolEvaluación comparativa de rendimiento
Antes de pasar a producción, compare su clúster HA para establecer el rendimiento de referencia y verificar que la latencia de replicación sincrónica sea aceptable para su carga de trabajo.
# 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 --forceConclusión
La alta disponibilidad nativa dePostgreSQL con Patroni no es una solución de herramienta única: es un sistema integrado de replicación de transmisión, consenso distribuido, enrutamiento de conexiones, agrupación de conexiones, archivo WAL, monitoreo y disciplina operativa. Cada capa aborda un modo de falla específico: la replicación de transmisión maneja la redundancia de datos, Patroni maneja la coordinación automática de conmutación por error, etcd proporciona el consenso distribuido necesario para la elección del líder sin dividir el cerebro, HAProxy enruta las conexiones al líder correcto, PgBouncer administra la sobrecarga de la conexión a escala y el archivado WAL con pgBackRest proporciona la última línea de defensa contra errores lógicos y recuperación ante desastres.
Los patrones de implementación varían según el entorno: AWS con NLB y Route53, Azure con zonas de disponibilidad y Azure Load Balancer, GCP con grupos de instancias administrados regionales o bare metal con k3s, Longhorn y Keepalived, pero la arquitectura central sigue siendo la misma. Tres o más nodos PostgreSQL administrados por Patroni, respaldados por un clúster etcd de tres nodos, encabezado por un equilibrador de carga que sigue los puntos finales de verificación de estado de Patroni.
La inversión más importante que puede hacer no es la configuración, sino las pruebas. Realice simulacros de conmutación por error mensualmente. Inyectar particiones de red. Mata procesos inesperadamente. Mida el tiempo de recuperación y la pérdida de datos. Cree paneles que muestren el retraso de replicación, la tasa de generación de WAL, la saturación del grupo de conexiones y el estado de DCS en tiempo real. La confianza que se obtiene con las pruebas sistemáticas es lo que separa a un clúster que sobrevive a su primera interrupción real de uno que convierte una falla del servidor en un incidente que afecta el negocio.
PostgreSQL le brinda todas las primitivas de replicación. Patroni te pone la orquestación. etcd te da el consenso. Su trabajo es conectarlos correctamente, ajustarlos a su carga de trabajo y validarlos continuamente. Esta guía le ha proporcionado los planos: ahora construya, pruebe y opere con confianza.