Si vous cherchez à faire une upgrade d’engine PostgreSQL d’une version majeure à une autre, il n’est pas possible
de le faire en utilisant une réplication physique standard, et vous pouvez le faire à froid avec la commande pg_upgrade
.
Cela fonctionera, mais nécessite de couper complètement l’engine, et peut prendre des heures suivant la volumétrie, ce qui
en terme de service rendu n’est clairement pas optimal.
Note: je vous conseille d’utiliser Patroni si vous faites tourner PostgreSQL en réplication, c’est le meilleur outil opensource du marché à l’heure actuelle pour gérer des clusters PostgreSQL.
Nous allons voir ici comment pouvoir faire des upgrades majeures PostgreSQL à chaud, sans downtime (mais avec quelques petites subtilités).
max_logical_replication_workers
adaptée, si vous avez énormément de bases de données (il faut compter 2 workers par base)wal_level
mise à logical sur les 2 clusters (attention il faut rédémarrer et pas juste reload les instances)Vous pouvez vérifier la configuration de celui-ci à chaud de la manière suivante:
sudo su - postgres -c 'psql postgres -c "show wal_level;"'
wal_level
-----------
logical
(1 row)
Si vous utilisez patroni il faudra éditer la configuration (patronictl -c /etc/patroni/config.yml edit-config
) et redémarrer chaque instance:
postgresql:
parameters:
...
wal_level: logical
max_logical_replication_workers: 8
Afin de mettre en place la réplication logique, nous avons besoin de reproduire notre schéma (sans les données) dans le cluster de destination:
export PGPASSWORD="<redacted>"
pg_dumpall -h <legacy_pg_primary> -U postgres --schema-only | psql -h <new_pg> -U postgres
unset PGPASSWORD
Si vous profitez de cette migration pour changer de topologie, vous pouvez ne répliquer qu’une base source par exemple:
export PGPASSWORD="<redacted>"
pg_dump -h <legacy_pg_primary> -U postgres --schema-only <database_name> | psql -h <new_pg> -U postgres <new_database_name>
unset PGPASSWORD
Maintenant il ne nous reste plus qu’à répliquer. Cela va utiliser le modèle de pub/sub de PostgreSQL.
Sur l’ancien cluster, il faut créer une publication pour toutes les tables:
CREATE PUBLICATION zero_downtime_upgrade FOR ALL TABLES;
Maintenant sur le nouveau cluster on va y souscrire:
CREATE SUBSCRIPTION my_subscription CONNECTION 'dbname=mydb host=myhost user=myuser password=mypass' PUBLICATION zero_downtime_upgrade;
A partir de maintenant le PostgreSQL cible va récupérer les données de toutes les tables
depuis la publication. Si ce nouveau PostgreSQL
est d’une version >= 16 vous allez pouvoir suivre table par table l’avancée de la réplication initiale dans les logs.
La réplication logique prend énormément d’espace disque au niveau des WALs. Si vous archivez ces WALs, il se peut que l’archive soit trop long par rapport à la vitesse de réplication, ce qui risque de remplir tout l’espace. Ce que vous pouvez faire c’est les nettoyer nettoyer une partie toutes les minutes pendant cette synchronisation initiale, l’archivage n’est pas très important à ce moment du process, il le sera uniquement une fois la synchronisation initiale terminée.
Vous pouvez faire tourner dans un tmux
le script suivant qui va garder les 50 derniers WALs sur le système de fichiers:
cd /var/lib/postgresql/16/main/data
while true; do
pg_archivecleanup pg_wal $(ls pg_wal/|tail -50|head -1) && sleep 60
done
Si votre cible est un cluster et pas une instance isolée, validez avant que ses replicas suivent bien la réplication au fil de l’eau et ne sont pas perdus, par ex si leurs performances CPU/disques sont moindres que le primaire.
Vous pouvez vérifier l’avancement de la réplication en lisant les offsets sur les 2 clusters:
select slot_name,confirmed_flush_lsn from pg_replication_slots;
select application_name,replay_lsn from pg_stat_replication;
select subname,received_lsn from pg_catalog.pg_stat_subscription;
Une fois la synchronisation initiale terminée, cela va synchroniser au fur et à mesure les données de l’ancien vers le nouveau cluster. Vous pouvez basculer sur le nouveau cluster en changeant vos URL/identifiants dès que vous le souhaitez, néanmoins il faut prendre en considération quelques points:
SERIAL
ou BIGSERIAL
. Ceux-ci
ne sont pas répliqués dans la mécanique de pub/sub. Cela va déclencher des soucis à l’insertion sur votre cluster cible, du fait que vos séquences
ont été réinitialiséesSur Stackoverflow (source) quelqu’un propose astucieusement le script suivant pour générer les requêtes à jouer sur le nouveau cluster afin de remettre les séquences au bon offset:
#!/bin/bash
port=$1
db=$2
query="select schemaname as schema,
sequencename as sequence,
start_value,
last_value
from pg_sequences order by sequencename asc;
"
while read schema sequence start_value last_value
do
if [ -z "$last_value" ]
then
echo "SELECT pg_catalog.SETVAL('${schema}.\"${sequence}\"', $start_value, true);"
else
echo "SELECT pg_catalog.SETVAL('${schema}.\"${sequence}\"', $last_value, true);"
fi
done < <(psql -t -A -F" " -p ${port} ${db} -c "${query}")
Cet article donne une idée générale de la migration “à chaud” d’une version majeure de PostgreSQL, cela va couvrir énormément de cas d’usage courants néanmoins il faut prendre en considération la topologie de l’application concernée, est-ce qu’elle sépare lectures et écritures ? Est ce que la latence est importante ? Est ce que je peux me permettre une micro coupure pour m’assurer que tout a été répliqué ? Ce sont des points à traîter au cas par cas et qu’un simple article sur le net ne peut traîter à votre place :).