PostgreSQL desde cero (V): replicación, backups y producción
Quinta y última entrega de la serie PostgreSQL desde cero a pro. Tiempo de lectura estimado: 14 minutos.
Último tramo. En las entregas anteriores has aprendido a instalar PostgreSQL (I), diseñar esquemas serios (II), escribir consultas avanzadas (III) y diagnosticar rendimiento (IV). Ahora toca lo que separa un PostgreSQL "que funciona" de uno con el que tu jefe puede dormir tranquilo: replicación, backups, monitorización y HA.
No voy a cubrir absolutamente todo. Voy a dar la columna vertebral, suficiente para que puedas llevar a producción un clúster pequeño-mediano y saber qué buscar cuando necesites escalar.
WAL: lo que hay que entender antes de nada
PostgreSQL escribe cada cambio primero en el Write-Ahead Log (WAL). Es un log binario secuencial: cualquier modificación se persiste en el WAL antes de cambiar los ficheros de datos. Todo lo demás —replicación, backups, point-in-time recovery— se construye sobre esto.
- Los WAL segments viven en
$PGDATA/pg_wal/. - Cada segmento es un fichero (16 MB por defecto).
- Se reciclan cuando ya no hacen falta para recovery ni para réplicas.
Si un servidor tiene archive_mode = on, cada WAL lleno se copia a un almacenamiento externo vía archive_command. Esa copia es la base de los backups físicos y del point-in-time recovery.
Backups
Tipos de backup
- Lógicos (
pg_dump,pg_dumpall): exportan SQL. Portables entre versiones, lentos para datasets grandes, no permiten PITR. - Físicos (
pg_basebackup,WAL-G,pgBackRest,Barman): copias binarias del directorio de datos + los WAL archivados. Rápidos para restaurar, permiten PITR.
Para producción, siempre físicos. pg_dump sigue siendo útil para migraciones entre versiones mayores o para snapshots de tablas específicas.
pg_dump
Lógico, por base de datos:
pg_dump -Fc -f blog.dump blog
# Formato custom (-Fc): comprimido, permite restore selectivo
pg_restore -d blog_nuevo blog.dump
Para una sola tabla:
pg_dump -t posts -Fc -f posts.dump blog
Útil en desarrollo y en migraciones. En producción de un sistema grande, no es tu herramienta principal.
pg_basebackup
Backup físico básico, desde una réplica o desde el master con primary:
pg_basebackup \
-h primary.internal \
-U replicator \
-D /var/lib/postgresql/17/backup \
-Fp -Xs -P -R
-Fp: plain (los ficheros tal cual).-Xs: streamea WAL durante el backup.-P: muestra progreso.-R: escribestandby.signalyprimary_conninfo, preparando el directorio para ser réplica.
Rápido y suficiente para clusters pequeños. Para los serios, la siguiente herramienta.
WAL-G, pgBackRest, Barman
Las tres herramientas de facto para backups en producción:
- WAL-G: rápida, pensada para cloud (S3, GCS, Azure), paraleliza bien. Mi elección por defecto.
- pgBackRest: muy completa, excelentes opciones de retención y verificación. Estándar en entornos on-prem.
- Barman: veterana, buena integración con Debian.
Configuración típica de WAL-G con S3 (en postgresql.conf):
archive_mode = on
archive_command = 'wal-g wal-push %p'
archive_timeout = 60
Y la variable de entorno con la config:
WALG_S3_PREFIX=s3://backups-pg/prod
AWS_REGION=eu-west-1
WALG_COMPRESSION_METHOD=brotli
Para hacer un base backup:
wal-g backup-push /var/lib/postgresql/17/main
Y para listar/restaurar:
wal-g backup-list
wal-g backup-fetch /var/lib/postgresql/17/main LATEST
Point-in-time recovery
Lo que hace insustituibles los backups físicos: restaurar a un instante exacto.
# recovery.conf o postgresql.auto.conf
restore_command = 'wal-g wal-fetch %f %p'
recovery_target_time = '2026-05-01 12:34:56 UTC'
recovery_target_action = 'promote'
Recuperas el último base backup anterior a esa hora y aplicas los WAL hasta llegar justo al punto pedido. Perfecto para deshacer un DROP TABLE accidental, un UPDATE que olvidó el WHERE, o cualquier error de los que duelen.
Probá tus backups. Un backup sin restore probado es placebo. Programa restores periódicos a un servidor staging. Si no has restaurado nunca, no tienes backup.
Replicación streaming
Una primaria acepta escrituras y envía su WAL a una o más réplicas. Las réplicas aplican el WAL y quedan sincronizadas (con algo de lag).
Configuración básica en la primaria
En postgresql.conf:
wal_level = replica # o 'logical' si además haces logical replication
max_wal_senders = 10
wal_keep_size = 1GB # o usa replication slots
hot_standby = on
Crear el rol de replicación:
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'xxx';
En pg_hba.conf:
host replication replicator 10.0.0.0/8 scram-sha-256
Configuración de la réplica
- Parar PostgreSQL en la réplica.
- Limpiar
$PGDATA. pg_basebackup ... -Rdesde la primaria.- Arrancar. Aparece como standby.
El fichero standby.signal marca que el servidor es una réplica; postgresql.auto.conf incluye el primary_conninfo generado por -R.
Verificar:
-- En la primaria
SELECT client_addr, state, sent_lsn, replay_lsn,
pg_wal_lsn_diff(sent_lsn, replay_lsn) AS lag_bytes
FROM pg_stat_replication;
-- En la réplica
SELECT pg_is_in_recovery();
SELECT now() - pg_last_xact_replay_timestamp() AS replication_lag;
Replication slots
Sin replication slot, la primaria puede reciclar un WAL que la réplica aún necesita y romper la replicación. Los slots garantizan que la primaria conserve los WAL necesarios:
SELECT pg_create_physical_replication_slot('replica_1');
En la réplica, primary_slot_name = 'replica_1' en postgresql.conf.
Cuidado con el revés: si una réplica se cae y el slot queda huérfano, la primaria acumulará WAL indefinidamente hasta llenar el disco. Monitoriza siempre los slots.
Replicación sincrónica
Por defecto, la replicación es asíncrona: la primaria no espera a la réplica. Si quieres que los commits solo confirmen cuando la réplica los ha aplicado:
synchronous_standby_names = 'ANY 1 (replica1, replica2)'
Coste: latencia de escritura aumenta. Ganancia: cero data loss ante caída de la primaria. Para cuentas bancarias, sí. Para analítica, normalmente no.
Alta disponibilidad y failover
PostgreSQL no tiene failover automático integrado. Necesitas orquestarlo. Opciones:
- Patroni + etcd/Consul/ZooKeeper. Es el estándar de facto.
- repmgr: más simple, menos potente.
- Managed services (RDS, Cloud SQL, Crunchy Bridge): delegas el failover.
Ideas comunes a todas:
- El "leader" es uno, decidido por consenso externo.
- Las réplicas promocionan cuando el leader se cae.
- Un proxy delante (HAProxy, PgBouncer con
server_routing) enruta al leader actual.
Una vez promocionada una réplica, la antigua primaria no puede reincorporarse sin más: habría divergido. Herramientas como pg_rewind reconcilian automáticamente; Patroni lo hace por ti.
Logical replication
Distinta a la streaming: en lugar de replicar WAL binario, replica cambios lógicos (INSERT/UPDATE/DELETE en tablas concretas). Usa:
- Replicación entre versiones mayores distintas (16 → 17).
- Migración online de un servidor a otro.
- Partir datos por tablas: "estas 5 tablas al servidor A, estas 3 al B".
- Pipelines de CDC (change data capture) a Kafka, Debezium, analíticas.
Setup básico:
-- En el origen
ALTER SYSTEM SET wal_level = 'logical';
-- reiniciar
CREATE PUBLICATION pub_all FOR ALL TABLES;
-- En el destino
CREATE SUBSCRIPTION sub_all
CONNECTION 'host=origen ...'
PUBLICATION pub_all;
Caveats: no replica DDL (solo datos), los slots pueden acumular WAL si el subscriber no está al día, no todas las operaciones se replican (TRUNCATE sí desde 11, DDL no).
Monitorización mínima
Métricas imprescindibles:
- Latencia de consultas (p95, p99).
- Lag de replicación: segundos de retraso entre primaria y réplicas.
- Tasa de commits y rollbacks.
- Uso de conexiones vs
max_connections. - Tasa de cache hit:
blks_hit / (blks_hit + blks_read)desdepg_stat_database. Objetivo: >99%. - Tamaño de
pg_wal/y tamaño de WAL archivados pendientes. - Autovacuum: tablas con
n_dead_tupcreciente ylast_autovacuumantiguo. - Disco: llenado de
$PGDATAy del volumen de WAL.
Herramientas:
- Prometheus +
postgres_exporter+ Grafana. Dashboards listos en grafana.com. - pgwatch2, pganalyze (comercial), pgHero (simple y bueno).
Si ya montas Prometheus/Grafana, el post Prometheus y Grafana para servicios pequeños aplica igual.
Alertas mínimas
replication_lag_bytes> 100 MB sostenido 5 minutos.replication_lag_time> 30 segundos.disk_usage> 80%.connections_used / max_connections> 80%.cache_hit_ratio< 95%.deadlocks> 0 en última hora.autovacuum_runningcon duración extrema.- WAL archivados pendientes > umbral.
Seguridad
Lo mínimo en producción:
- TLS obligatorio en conexiones externas (
hostsslen pg_hba,ssl = onen postgresql.conf). - Contraseñas
scram-sha-256, nuncamd5en nuevos despliegues. - Roles sin
SUPERUSERpara la app (ya lo vimos en la entrega I). - Red privada: PostgreSQL jamás expuesto a internet directamente.
- Secret management: contraseñas en variables de entorno, vaults o equivalentes, no en repos.
- Row Level Security para multitenancy:
ALTER TABLE posts ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation ON posts USING (tenant_id = current_setting('app.tenant_id')::bigint); - Audit log si el compliance lo exige (extensión
pgaudit).
Upgrades
- Minor (17.1 → 17.2): parar, instalar paquete, arrancar. No hay cambios de formato en disco.
- Major (16 → 17): varias rutas:
pg_upgrade: rápido, in-place, requiere downtime corto.- Logical replication: sin downtime pero más complejo.
pg_dump/pg_restore: simple pero lento para datos grandes.
La recomendación: probar siempre en staging con una copia real antes de tocar producción. Y tener un plan de rollback explícito.
Checklist rápida de producción
Antes de declarar "en producción", repasa:
- Backups físicos automatizados a almacenamiento externo.
- Restore probado en entorno limpio.
- Al menos una réplica con lag monitorizado.
- Failover documentado y probado (runbook).
- Connection pooler (PgBouncer u otro).
- Monitorización con las métricas de arriba + alertas.
- TLS y autenticación robusta.
-
postgresql.confajustado a los recursos reales (ver entrega IV). - Autovacuum ajustado para tablas calientes.
- Plan de upgrades (minor y major) con ventanas conocidas.
- Logs centralizados y con retención.
- Segurización de red (sin PostgreSQL expuesto a internet).
Si marcas todo, estás muy por encima de la media.
Cierre de la serie
En cinco posts hemos recorrido PostgreSQL desde instalarlo hasta operarlo con seriedad. Como siempre, el 80% del valor está en las decisiones tempranas: modelo bien pensado, tipos correctos, restricciones que garantizan invariantes, índices donde hacen falta. Lo demás es operación: importante pero secundaria.
Mis dos recomendaciones finales:
- Empieza pequeño, instrumenta desde el día 1. Un PostgreSQL con
pg_stat_statementsy métricas básicas te ahorra sorpresas. Las mejores optimizaciones las hace PostgreSQL por sí solo si tiene estadísticas buenas. - No caigas en la tentación de microservicios-base-de-datos antes de tiempo. Una PostgreSQL bien diseñada con schemas y RLS aguanta mucho más de lo que la gente cree. El coste de operar varias bases es real.
Serie completa:
- I: instalación, psql y primeros pasos
- II: tipos, restricciones y relaciones
- III: CTEs, window functions y consultas avanzadas
- IV: índices, EXPLAIN y rendimiento
- V: replicación, backups y producción (estás aquí)
Y si quieres seguir adentrándote en el ecosistema de bases de datos:
- ClickHouse desde cero a pro: cuando PostgreSQL deja de rendir en analytics.
- PostgreSQL: 10 consultas que todo desarrollador debería conocer: repaso rápido de las herramientas que más he usado.
Si tienes dudas sobre montar replicación, diseñar un esquema concreto o depurar una consulta lenta, mis DMs siguen abiertos.