Un nodo puede quedar fuera durante una actualización mayor de PostgreSQL sin que el clúster distribuido deje de aceptar escrituras: los otros nodos siguen atendiendo a la aplicación. La condición es que la topología tenga suficientes nodos para mantener dos activos mientras se trabaja sobre el tercero. En la prueba de pgEdge, el salto fue de PostgreSQL 17.10 a 18.6 en un nodo Spock gestionado por Patroni.
Este procedimiento no mantiene disponible el nodo que se actualiza. Mantiene otros puntos de escritura accesibles mientras ese nodo está parado. El ejemplo sigue el laboratorio de pgEdge: tres nodos Spock, con n3 como objetivo, y un standby físico asociado a n3. Los otros dos nodos siguen atendiendo durante la intervención.
La secuencia central es drenar n3, actualizarlo con pg_upgrade, reparar el estado de replicación de Spock, devolverlo a Patroni y reconstruir su standby. Se actualiza un nodo cada vez.
flowchart TD A["Drenar n3"] --> B["Parar standby y nodo"] B --> C["Preparar PostgreSQL 18"] C --> D["Ejecutar pg_upgrade"] D --> E["Restaurar orígenes Spock"] E --> F["Reintegrar en Patroni"] F --> G["Reconstruir standby"] G --> H["Probar escrituras y seguir"]
Antes de detener el nodo
Comprueba la versión, la codificación y la versión de Spock en el nodo que vas a actualizar. Anota también el estado de los checksums: el nuevo directorio debe coincidir con el clúster anterior. En PostgreSQL 18, initdb activa los checksums de forma predeterminada; si el clúster antiguo no los usa, hay que desactivarlos explícitamente.
-- Ejecutar en n3 antes de detenerlo
SELECT version();
SHOW server_encoding;
SELECT extversion FROM pg_extension WHERE extname = 'spock';
# Comprobar checksums en el directorio de datos antiguo
/usr/pgsql-17/bin/pg_controldata ~/n3data | grep 'checksum version'
Instala los binarios nuevos y la misma versión de Spock que usa el clúster. El ejemplo de pgEdge instala Spock 5.0.13 para PostgreSQL 18. Antes de iniciar el despliegue, desactiva la replicación de DDL en todos los nodos y recarga la configuración. Mantenla desactivada hasta terminar la actualización de todos ellos.
# Ejemplo del paquete usado en la prueba con Spock 5.0.13
sudo dnf install -y pgedge-postgresql18-server pgedge-postgresql18-contrib pgedge-spock50_18-5.0.13
-- Ejecutar en cada nodo antes del despliegue
ALTER SYSTEM SET spock.enable_ddl_replication = off;
SELECT pg_reload_conf();
Detener n3 y preparar el directorio nuevo
Retira n3 del balanceador o del pooler. No sigas hasta verificar que las suscripciones entrantes y salientes están al día. Después pausa Patroni y detén primero el standby y luego el nodo. pgEdge advierte que, al parar PostgreSQL, Patroni libera el bloqueo de líder incluso en modo de mantenimiento: dejar activo el standby antiguo crea el riesgo de que se promocione si Patroni se reanuda antes de tiempo.
-- Confirmar el estado de las suscripciones antes de parar n3
SELECT subscription_name, status FROM spock.sub_show_status();
# Drenar n3 antes de ejecutar estos comandos
patronictl -c ~/patroni/n3.yml pause n3
/usr/pgsql-17/bin/pg_ctl -D ~/n3standby stop
/usr/pgsql-17/bin/pg_ctl -D ~/n3data stop
Los nodos restantes deben seguir sirviendo tráfico. Inicializa el directorio PostgreSQL 18 con la misma codificación y configuración de checksums que el antiguo. En este ejemplo, el clúster usaba UTF-8 y no tenía checksums. Copia el archivo de autenticación y configura Spock antes de ejecutar pg_upgrade: la extensión debe estar en shared_preload_libraries para que la comprobación pueda cargarla.
# Inicializar el nuevo directorio con los ajustes de la prueba
/usr/pgsql-18/bin/initdb -D ~/n3data_new --encoding=UTF8 --no-data-checksums
# Configurar PostgreSQL y Spock antes de pg_upgrade
cat >> ~/n3data_new/postgresql.conf <<'EOF'
port = 5442
listen_addresses = '*'
unix_socket_directories = '/tmp'
wal_level = logical
track_commit_timestamp = on
shared_preload_libraries = 'spock'
output_plugin_libraries = 'pgoutput, test_decoding, spock_output'
max_active_replication_origins = 10
max_replication_slots = 10
max_wal_senders = 10
max_worker_processes = 16
EOF
cp ~/n3data/pg_hba.conf ~/n3data_new/pg_hba.conf
Los parámetros deben corresponder a la configuración del nodo antiguo. En particular, pgEdge señala que max_active_replication_origins es nuevo en PostgreSQL 18 y que output_plugin_libraries está disponible en compilaciones recientes de pgEdge, como la 18.6.
Ejecutar pg_upgrade y recuperar los orígenes
Ejecuta primero --check desde un directorio donde el usuario tenga permiso de escritura: pg_upgrade deja allí sus registros. Incluye --link en la comprobación para validar ese modo. Si pasa, repite el comando sin --check. El modo link reutiliza los archivos de datos mediante enlaces duros; exige que los directorios de datos estén en el mismo sistema de archivos y, una vez iniciado el clúster nuevo, no se puede volver a usar el antiguo.
# Comprobar compatibilidad y modo link desde un directorio escribible
cd ~
/usr/pgsql-18/bin/pg_upgrade \
--old-datadir ~/n3data --new-datadir ~/n3data_new \
--old-bindir /usr/pgsql-17/bin --new-bindir /usr/pgsql-18/bin \
--link --check
# Ejecutar la actualización tras superar la comprobación
cd ~
/usr/pgsql-18/bin/pg_upgrade \
--old-datadir ~/n3data --new-datadir ~/n3data_new \
--old-bindir /usr/pgsql-17/bin --new-bindir /usr/pgsql-18/bin \
--link
En la prueba, PostgreSQL 17 conservó los slots lógicos, pero eso no bastó para que n3 aplicara cambios entrantes. pg_upgrade no conserva los orígenes de Spock: los otros nodos podían leer desde n3, pero n3 no aplicaba lo que recibía. Recupera la posición confirmada de cada slot en el proveedor y recrea cada origen en el nodo actualizado, avanzándolo hasta ese LSN. Los nombres del siguiente ejemplo corresponden a la topología de pgEdge; ajústalos a los slots reales de tu clúster.
# Leer en cada proveedor el último LSN confirmado por el slot
L1=$(psql -h 192.168.169.158 -p 5432 -d testdb -Atc \
"SELECT confirmed_flush_lsn FROM pg_replication_slots WHERE slot_name='spk_testdb_n1_sub_n1_n3';")
L2=$(psql -h 192.168.169.159 -p 5432 -d testdb -Atc \
"SELECT confirmed_flush_lsn FROM pg_replication_slots WHERE slot_name='spk_testdb_n2_sub_n2_n3';")
# Crear y avanzar los orígenes correspondientes en n3
psql -p 5442 -d testdb \
-c "SELECT pg_replication_origin_create('spk_testdb_n1_sub_n1_n3');" \
-c "SELECT pg_replication_origin_advance('spk_testdb_n1_sub_n1_n3', '$L1');" \
-c "SELECT pg_replication_origin_create('spk_testdb_n2_sub_n2_n3');" \
-c "SELECT pg_replication_origin_advance('spk_testdb_n2_sub_n2_n3', '$L2');"
Comprueba que las suscripciones vuelven a replicar y haz escrituras de prueba en ambas direcciones antes de pasar al siguiente nodo. En el caso probado por pgEdge, una fila escrita mientras n3 todavía no aplicaba cambios apareció allí después de restaurar los orígenes. No des por buena la recuperación basándote solo en el estado de las suscripciones.
Devolver el nodo a Patroni y rehacer el standby
Arranca el clúster nuevo y actualiza la configuración de Patroni para que apunte al directorio y a los binarios de PostgreSQL 18. Un simple patronictl reload no bastó en la prueba: Patroni seguía mirando el directorio antiguo hasta reiniciar su proceso.
# Arrancar n3 con PostgreSQL 18 antes de devolverlo a Patroni
/usr/pgsql-18/bin/pg_ctl -D ~/n3data_new -l ~/n3data_new/n3.log start
El identificador del sistema cambia tras pg_upgrade, pero etcd conserva el anterior. Según el procedimiento probado, hay que parar Patroni, quitar el registro del clúster y dejar que Patroni adopte el nodo en ejecución. La configuración Spock también debe estar en bootstrap.dcs; de lo contrario, Patroni puede sustituir parámetros como wal_level y shared_preload_libraries por sus valores gestionados.
# Fragmento de la configuración de Patroni
postgresql:
data_dir: /home/pgedge/n3data_new
bin_dir: /usr/pgsql-18/bin
bootstrap:
dcs:
postgresql:
use_slots: true
parameters:
wal_level: logical
track_commit_timestamp: on
shared_preload_libraries: spock
max_worker_processes: 16
max_wal_senders: 10
max_replication_slots: 10
sync_replication_slots: on
hot_standby_feedback: on
# Detener Patroni, retirar el registro antiguo y arrancarlo de nuevo
pkill -f 'patroni/n3_standby.yml'; pkill -f 'patroni/n3.yml'
patronictl -c ~/patroni/n3.yml remove n3
nohup patroni ~/patroni/n3.yml > ~/patroni/n3.log 2>&1 &
El standby físico no puede seguir al primario a través de una actualización mayor. En la prueba, Patroni rechazó la reinicialización del standby antiguo por la diferencia de identificador del sistema. El procedimiento utilizado fue borrar su directorio y volver a arrancar Patroni, que creó una réplica nueva mediante pg_basebackup.
# Eliminar los datos antiguos del standby y dejar que Patroni lo reconstruya
rm -rf ~/n3standby
nohup patroni ~/patroni/n3_standby.yml > ~/patroni/n3_standby.log 2>&1 &
Después, restaura los ajustes que se aplicaban con ALTER SYSTEM: pg_upgrade no traslada postgresql.auto.conf. En el escenario de pgEdge se volvió a configurar synchronized_standby_slots en el líder y se comprobó que el standby tuviera copias actuales de los slots de Spock.
-- Ejecutar en el líder actualizado cuando el standby esté reconstruido
ALTER SYSTEM SET synchronized_standby_slots = 'n3_standby';
SELECT pg_reload_conf();
Finaliza con las tareas que pg_upgrade deja pendientes: actualizar extensiones, regenerar estadísticas y conservar el directorio antiguo hasta estar satisfecho con el resultado. Solo entonces elimina los datos anteriores. Repite el proceso nodo a nodo; cuando termine el último, vuelve a activar la replicación de DDL.
# Ejecutar en el nodo actualizado; borrar el clúster antiguo al final
psql -p 5442 -d postgres -f ~/update_extensions.sql
/usr/pgsql-18/bin/vacuumdb -p 5442 --all --analyze-in-stages --missing-stats-only
/usr/pgsql-18/bin/vacuumdb -p 5442 --all --analyze-only
~/delete_old_cluster.sh
El orden importa: recuperar la replicación entrante antes de dar por reincorporado el nodo, comprobar escrituras en ambos sentidos y no actualizar el siguiente miembro hasta que el anterior esté operativo. En el ejemplo, actualizar un nodo dejó disponibles los otros dos mientras duró el trabajo.
Fuentes: pgedge.com, PostgreSQL.de, PostgreSQL Documentation, GitHub