Javier Valencia Javier Valencia
PostgreSQL desde cero (V): replicación, backups y producción

PostgreSQL desde cero (V): replicación, backups y producción

Javier Valencia · · 7 min de lectura · 45 visitas · Desarrollo
bases-de-datos tutorial postgresql devops postgresql-desde-cero

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: escribe standby.signal y primary_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

  1. Parar PostgreSQL en la réplica.
  2. Limpiar $PGDATA.
  3. pg_basebackup ... -R desde la primaria.
  4. 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) desde pg_stat_database. Objetivo: >99%.
  • Tamaño de pg_wal/ y tamaño de WAL archivados pendientes.
  • Autovacuum: tablas con n_dead_tup creciente y last_autovacuum antiguo.
  • Disco: llenado de $PGDATA y 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_running con duración extrema.
  • WAL archivados pendientes > umbral.

Seguridad

Lo mínimo en producción:

  1. TLS obligatorio en conexiones externas (hostssl en pg_hba, ssl = on en postgresql.conf).
  2. Contraseñas scram-sha-256, nunca md5 en nuevos despliegues.
  3. Roles sin SUPERUSER para la app (ya lo vimos en la entrega I).
  4. Red privada: PostgreSQL jamás expuesto a internet directamente.
  5. Secret management: contraseñas en variables de entorno, vaults o equivalentes, no en repos.
  6. 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);
    
  7. 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.conf ajustado 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:

  1. Empieza pequeño, instrumenta desde el día 1. Un PostgreSQL con pg_stat_statements y métricas básicas te ahorra sorpresas. Las mejores optimizaciones las hace PostgreSQL por sí solo si tiene estadísticas buenas.
  2. 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:

Y si quieres seguir adentrándote en el ecosistema de bases de datos:

Si tienes dudas sobre montar replicación, diseñar un esquema concreto o depurar una consulta lenta, mis DMs siguen abiertos.