Un nodo può rimanere fuori servizio durante un aggiornamento di versione principale di PostgreSQL senza che il cluster distribuito smetta di accettare scritture: gli altri nodi continuano a servire l’applicazione. La condizione è che la topologia abbia nodi sufficienti per mantenerne due attivi mentre si interviene sul terzo. Nel test di pgEdge, il passaggio è stato da PostgreSQL 17.10 a 18.6 su un nodo Spock gestito da Patroni.
Questa procedura non mantiene disponibile il nodo in fase di aggiornamento. Mantiene accessibili gli altri punti di scrittura mentre quel nodo è fermo. L’esempio segue il laboratorio di pgEdge: tre nodi Spock, con n3 come obiettivo, e uno standby fisico associato a n3. Gli altri due nodi continuano a servire le richieste durante l’intervento.
La sequenza centrale consiste nel drenare n3, aggiornarlo con pg_upgrade, ripristinare lo stato della replica Spock, reintegrarlo in Patroni e ricostruire il suo standby. Si aggiorna un nodo alla volta.
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"]
Prima di fermare il nodo
Verifica la versione, la codifica e la versione di Spock sul nodo che stai per aggiornare. Annota anche lo stato dei checksum: la nuova directory deve corrispondere al cluster precedente. In PostgreSQL 18, initdb attiva i checksum per impostazione predefinita; se il vecchio cluster non li usa, è necessario disattivarli esplicitamente.
-- 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'
Installa i nuovi binari e la stessa versione di Spock usata dal cluster. Nell’esempio di pgEdge viene installato Spock 5.0.13 per PostgreSQL 18. Prima di avviare la distribuzione, disattiva la replica DDL su tutti i nodi e ricarica la configurazione. Lasciala disattivata fino al completamento dell’aggiornamento di tutti i nodi.
# 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();
Fermare n3 e preparare la nuova directory
Rimuovi n3 dal bilanciatore del carico o dal pooler. Non procedere finché non hai verificato che le sottoscrizioni in entrata e in uscita siano aggiornate. Poi metti in pausa Patroni e ferma prima lo standby, quindi il nodo. pgEdge avverte che, quando PostgreSQL viene arrestato, Patroni rilascia il lock del leader anche in modalità di manutenzione: lasciare attivo il vecchio standby crea il rischio che venga promosso se Patroni riprende prima del previsto.
-- 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
I nodi rimanenti devono continuare a servire il traffico. Inizializza la directory di PostgreSQL 18 con la stessa codifica e le stesse impostazioni dei checksum del vecchio cluster. In questo esempio, il cluster usava UTF-8 e non aveva checksum. Copia il file di autenticazione e configura Spock prima di eseguire pg_upgrade: l’estensione deve essere in shared_preload_libraries affinché il controllo possa caricarla.
# 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
I parametri devono corrispondere alla configurazione del vecchio nodo. In particolare, pgEdge segnala che max_active_replication_origins è una novità di PostgreSQL 18 e che output_plugin_libraries è disponibile nelle build recenti di pgEdge, come la 18.6.
Eseguire pg_upgrade e ripristinare le origini
Esegui prima --check da una directory in cui l’utente abbia i permessi di scrittura: pg_upgrade vi deposita i propri log. Includi --link nel controllo per convalidare questa modalità. Se il controllo ha esito positivo, ripeti il comando senza --check. La modalità link riutilizza i file di dati tramite hard link; richiede che le directory dei dati si trovino sullo stesso file system e, una volta avviato il nuovo cluster, non è più possibile usare quello vecchio.
# 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
Nel test, PostgreSQL 17 ha conservato gli slot logici, ma questo non è bastato perché n3 applicasse le modifiche in entrata. pg_upgrade non conserva le origini di Spock: gli altri nodi potevano leggere da n3, ma n3 non applicava ciò che riceveva. Recupera la posizione confermata di ogni slot sul provider e ricrea ogni origine sul nodo aggiornato, facendola avanzare fino a quel LSN. I nomi nell’esempio seguente corrispondono alla topologia di pgEdge; adattali agli slot effettivi del tuo cluster.
# 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');"
Verifica che le sottoscrizioni riprendano a replicare ed esegui scritture di prova in entrambe le direzioni prima di passare al nodo successivo. Nel caso testato da pgEdge, una riga scritta mentre n3 non applicava ancora le modifiche è comparsa lì dopo il ripristino delle origini. Non considerare riuscito il ripristino basandoti solo sullo stato delle sottoscrizioni.
Reintegrare il nodo in Patroni e ricreare lo standby
Avvia il nuovo cluster e aggiorna la configurazione di Patroni in modo che punti alla directory e ai binari di PostgreSQL 18. Nel test, un semplice patronictl reload non è bastato: Patroni continuava a puntare alla vecchia directory finché il suo processo non è stato riavviato.
# 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
L’identificativo di sistema cambia dopo pg_upgrade, ma etcd conserva quello precedente. Secondo la procedura testata, è necessario fermare Patroni, rimuovere la registrazione del cluster e lasciare che Patroni adotti il nodo in esecuzione. Anche la configurazione di Spock deve trovarsi in bootstrap.dcs; in caso contrario, Patroni può sostituire parametri come wal_level e shared_preload_libraries con i valori gestiti da Patroni.
# 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 &
Lo standby fisico non può seguire il primario attraverso un aggiornamento di versione principale. Nel test, Patroni ha rifiutato la reinizializzazione del vecchio standby a causa della differenza nell’identificativo di sistema. La procedura utilizzata è stata eliminare la sua directory e riavviare Patroni, che ha creato una nuova replica tramite 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 &
In seguito, ripristina le impostazioni applicate con ALTER SYSTEM: pg_upgrade non trasferisce postgresql.auto.conf. Nello scenario di pgEdge, sul leader è stato riconfigurato synchronized_standby_slots ed è stato verificato che lo standby avesse copie aggiornate degli slot Spock.
-- Ejecutar en el líder actualizado cuando el standby esté reconstruido
ALTER SYSTEM SET synchronized_standby_slots = 'n3_standby';
SELECT pg_reload_conf();
Completa le attività che pg_upgrade lascia in sospeso: aggiorna le estensioni, rigenera le statistiche e conserva la vecchia directory finché non sei soddisfatto del risultato. Solo allora elimina i dati precedenti. Ripeti la procedura nodo per nodo; quando avrai terminato con l’ultimo, riattiva la replica 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
L’ordine è importante: ripristina la replica in entrata prima di considerare reintegrato il nodo, verifica le scritture in entrambe le direzioni e non aggiornare il membro successivo finché il precedente non è operativo. Nell’esempio, l’aggiornamento di un nodo ha lasciato disponibili gli altri due per tutta la durata dell’intervento.
Fonti: pgedge.com, PostgreSQL.de, Documentazione PostgreSQL, GitHub