El problema#
Ayer por la noche mi réplica de PostgreSQL dejó de sincronizarse. El servidor primario seguía funcionando, pero los datos nuevos no llegaban al standby. Downtime cero, pero tampoco quería perder esa capa de respaldo.
Documenté el proceso completo porque es algo que vuelve a pasar cada cierto tiempo y necesitaba un proceso reproducible.
Paso 1: Verificar el estado de replicación#
Primero, entré en el contenedor primario y ejecuté:
docker exec postgres-primary psql -U postgres -c "SELECT slot_name, restart_lsn, confirmed_flush_lsn FROM pg_replication_slots;"Me mostró que el slot de replicación estaba ahí, pero el confirmed_flush_lsn llevaba casi una hora sin avanzar. Eso confirmaba el lag.
En el standby:
docker exec postgres-standby psql -U postgres -c "SELECT pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn();"Los valores eran idénticos y antiguos. El standby ni siquiera estaba intentando recibir WAL.
Paso 2: Revisar los logs#
docker logs postgres-standby | tail -50Encontré el culpable: FATAL: could not connect to the primary server. El standby tenía una DNS obsoleta o el puerto 5432 del primario no estaba accesible desde la red interna del contenedor.
Verifiqué la conectividad:
docker exec postgres-standby nc -zv postgres-primary 5432Confirmado: sin conexión. El primario tenía problemas de red.
Paso 3: Recuperación sin downtime#
No quería reiniciar nada si podía evitarlo. Mi plan:
- Arreglar la conectividad entre contenedores
- Forzar el standby a reconectar
- Validar la sincronización
Revisé la configuración de la red en docker-compose.yml. El alias del contenedor primario estaba correcto, pero el puerto expuesto no coincidía con el que usaba para replicación interna. Cambié la variable de entorno PRIMARY_CONNINFO en el standby:
postgres-standby:
environment:
PRIMARY_CONNINFO: "host=postgres-primary port=5432 user=replication password=***** application_name=standby"Reinicié solo el contenedor standby (el primario siguió corriendo):
docker restart postgres-standbyPaso 4: Monitorear la resincronización#
Ejecuté un script cada 30 segundos para ver el progreso:
watch -n 30 'docker exec postgres-primary psql -U postgres -c "SELECT slot_name, pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) as bytes_behind FROM pg_replication_slots;"'Tardó unos 15 minutos en sincronizar completamente. El lag bajó de 2GB a 0 bytes progresivamente.
Paso 5: Validación de consistencia#
Una vez sincronizado, necesitaba asegurarme de que los datos eran idénticos:
# En el primario
docker exec postgres-primary psql -U postgres -c "SELECT COUNT(*) as total_rows FROM informacion_importante;"
# En el standby
docker exec postgres-standby psql -U postgres -c "SELECT COUNT(*) as total_rows FROM informacion_importante;"Los conteos coincidían. Hice una validación más profunda con checksums:
docker exec postgres-primary psql -U postgres -c "SELECT md5(string_agg(id::text, ',' ORDER BY id)) FROM informacion_importante;" > /tmp/primary_hash.txt
docker exec postgres-standby psql -U postgres -c "SELECT md5(string_agg(id::text, ',' ORDER BY id)) FROM informacion_importante;" > /tmp/standby_hash.txt
diff /tmp/primary_hash.txt /tmp/standby_hash.txtSin diferencias. La replicación estaba íntegra.
Lecciones aprendidas#
- Los problemas de replicación suelen ser conectividad, no corrupción de datos
- Revisar logs es el primer paso, siempre
- Monitorear
pg_replication_slotspreviene sorpresas - Un standby desincronizado es mejor que nada, pero no es lo ideal
Ahora tengo un cron que ejecuta cada hora:
docker exec postgres-primary psql -U postgres -c "SELECT CASE WHEN pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) > 1073741824 THEN 'ALERTA: Lag > 1GB' ELSE 'OK' END;"Nunca más me cojo desprevenido.