Reviers, le 2 janvier 2025

Cela fait maintenant plusieurs mois que la version 16 est sortie. Il est vraiment temps de mettre à jour notre serveur en version 15 avec la version 16. Pour cela, il existe trois méthodes, et nous allons décrire chacune de ces méthodes dans cet article.

Linux Pratique 143

Commençons déjà par dire qu’il n’est pas nécessaire de passer par chaque version intermédiaire. Vous pouvez très bien mettre à jour une version 12 en version 16, sans avoir à faire les passages intermédiaires (versions 13, 14 et 15).

Le point important à savoir lors d’une mise à jour majeure est que les catalogues systèmes changent d’une version majeure à une autre. De nouveaux catalogues peuvent apparaître, les anciens peuvent être modifiés. Par exemple, des colonnes peuvent leur être ajoutées ou supprimées, voire, dans certains cas, renommées ou changées de type. Ces changements dans les catalogues systèmes nécessitent donc un travail préalable pour passer d’une version majeure à une autre, ce qui va occasionner une coupure de service pour les utilisateurs.

La méthode historique (généralement appelée dump/restore) est une méthode sûre mais potentiellement lente. En fait, plus la volumétrie de l’instance est importante, plus la mise à jour sera longue. Deux autres méthodes (réplication logique et pg_upgrade) sont apparues au fil des années pour éviter justement une coupure importante du service mais elles sont généralement plus complexes à mettre en place.

Installation de PostgreSQL 16

Quelle que soit la méthode sélectionnée, la première étape concerne l’installation de la nouvelle version de PostgreSQL. Seule la méthode pg_upgrade nécessite que la nouvelle version soit installée sur le même serveur. Cette nouvelle version peut être installée sur un nouveau serveur pour les deux autres méthodes. Dans ce cas, c’est généralement l’occasion de passer à un matériel plus performant ou tout du moins de mettre à jour le système d’exploitation.

Sur tous les exemples de cet article, je travaillerai sur le même serveur.

L’article « Installation de PostgreSQL » de cette série se trouve dans le numéro 139 de Linux Pratique et est aussi disponible sur le site des éditions Diamond

Voici rapidement les commandes exécutées pour l’installation de la version 16 :

dnf install -y postgresql16-server postgresql16-contrib
mkdir -p /etc/systemd/system/postgresql-16.service.d
cat > /etc/systemd/system/postgresql-16.service.d/override.conf <<_EOF_
[Service]
Environment=PGDATA=/srv/data16
_EOF_
install -d -o postgres -g postgres -m 700 /srv/data16
PGSETUP_INITDB_OPTIONS="--data-checksums" /usr/pgsql-16/bin/postgresql-16-setup initdb

Si vous suivez bien notre série sur PostgreSQL, vous avez peut-être remarqué que je n’ai pas utilisé l’option --wal-dir avec l’outil initdb. Comme je n’ai pas de partition pour les journaux de transactions de la nouvelle version, je ne déplace pas immédiatement le répertoire des journaux de transactions. Je ne le ferais qu’à la fin.

Pour la configuration du serveur, il est possible de copier les fichiers pg_hba.conf et pg_ident.conf de l’ancienne version :

cp /srv/data/pg_{hba,ident}.conf /srv/data16

Par contre, il ne faut jamais copier le fichier postgresql.conf de l’ancienne version vers la nouvelle version car il manquerait les nouveaux paramètres et surtout les paramètres qui ont changé de nom se retrouveraient avec l’ancien nom, ce qui empêcherait le démarrage du service. Dans notre cas, notre configuration se trouve dans le fichier linuxpratique.conf et nous n’avons pas configuré de paramètres qui ont changé de nom, donc nous pouvons le copier sans souci :

install -o postgres -g postgres -m 600 /srv/data/linuxpratique.conf /srv/data16

Comme nous allons avoir deux services PostgreSQL démarrés sur le même serveur, leur numéro de port respectif doit être différent. Il est donc nécessaire d’éditer le fichier /srv/data16/linuxpratique.conf pour remplacer la ligne du paramètre port par la ligne suivante :

port = 5433

Il nous faut maintenant modifier le fichier /srv/data16/postgresql.conf pour ajouter l’inclusion de notre fichier de configuration. Cela se fait avec cette ligne :

include 'linuxpratique.conf'

Enfin, nous pouvons activer et démarrer le service :

systemctl enable postgresql-16
systemctl start postgresql-16

Migration des données

Méthode dump/restore

Comme dit juste avant, cette méthode est la méthode historique. Elle peut être utilisée dans tous les cas, et elle est sûre. De plus, elle est simple à comprendre.

Elle a cependant un inconvénient de taille : un arrêt de production d’autant plus long que la volumétrie est importante (notamment lors de l’étape de restauration).

L’opération de mise à jour se passe en trois étapes :

  • couper l’application ;
  • sauvegarder les objets globaux et chaque base de l’ancienne version ;
  • restaurer les objets globaux puis chaque base sur la nouvelle version.

Couper l’application revient à l’arrêter. En fait, il ne faut pas qu’il y ait d’écritures sur le serveur pendant les deux étapes suivantes, sinon ces nouvelles écritures ne seront pas migrées. Une solution assez simple et très sûre revient à couper les accès à la base. Cela peut se faire en configurant le fichier pg_hba.conf ou en modifiant les rôles applicatifs. Bref, il y a plusieurs solutions. Il faut juste s’assurer que les écritures ne sont pas possibles sur les bases de l’instance.

Ceci fait, nous pouvons procéder à une sauvegarde complète du serveur avec les outils pg_dump et pg_dumpall. Encore mieux, il est possible d’utiliser pg_back. C’est ce que nous allons faire maintenant :

$ pg_back
2023/12/28 17:36:43 INFO: dumping globals
2023/12/28 17:36:43 INFO: dumping instance configuration
2023/12/28 17:36:43 INFO: dumping database postgres
2023/12/28 17:36:43 INFO: dump of postgres to /srv/backups/pg_back/postgres_2023-12-28T17:36:43+01:00.dump done
2023/12/28 17:36:43 INFO: dumping database b1
2023/12/28 17:36:43 INFO: dump of b1 to /srv/backups/pg_back/b1_2023-12-28T17:36:43+01:00.dump done
2023/12/28 17:36:43 INFO: dumping database b2
2023/12/28 17:36:49 INFO: dump of b2 to /srv/backups/pg_back/b2_2023-12-28T17:36:43+01:00.dump done
2023/12/28 17:36:49 INFO: dumping database temboard
2023/12/28 17:36:49 INFO: dump of temboard to /srv/backups/pg_back/temboard_2023-12-28T17:36:49+01:00.dump done
2023/12/28 17:36:49 INFO: waiting for postprocessing to complete
2023/12/28 17:36:49 INFO: purging old dumps

Ceci fait, il nous faut restaurer cette sauvegarde sur la nouvelle version. Nous commençons par restaurer les objets globaux :

psql -p 5433 -f /srv/backups/pg_back/pg_globals_2023-12-28T17:36:43+01:00.sql

Cette commande va se plaindre que le rôle postgres existe déjà. C’est normal, il est créé automatiquement par l’outil initdb. Puis nous pouvons restaurer chaque base :

pg_restore -p 5433 -C -d postgres /srv/backups/pg_back/b1_2023-12-28T17:36:43+01:00.dump
pg_restore -p 5433 -C -d postgres /srv/backups/pg_back/b2_2023-12-28T17:36:43+01:00.dump
pg_restore -p 5433 -C -d postgres /srv/backups/pg_back/temboard_2023-12-28T17:36:49+01:00.dump

Comme nous pouvons le voir, cette méthode est vraiment très simple. Elle utilise des outils de sauvegarde standards normalement connus. Dans mon cas, cela a été très rapide vu que les bases ont une volumétrie minuscule. Cependant, pour des bases conséquentes, l’opération complète peut prendre de nombreuses heures pendant laquelle les applications ne peuvent pas travailler.

Méthode réplication logique

Contrairement à la réplication physique qui ne peut pas se faire entre deux serveurs ayant une version majeure différente, la réplication logique peut le faire. Il est possible d’utiliser la solution native ou une solution externe (comme Slony par exemple), mais cet article n’abordera que la solution native.

Cette méthode nécessite de mettre en place la réplication logique sur chaque base à récupérer sur le nouveau serveur en version 16. Mais pour cela, il faut déjà configurer le serveur actuel pour que les journaux de transaction soient à un niveau compatible avec la réplication logique (valeur logical pour le paramètre wal_level). De plus, nous allons mettre en place une réplication par base. Il faut donc un bon nombre de workers et de slots de réplication. Tout cela passe par la configuration des paramètres wal_level, max_logical_replication_workers (et de ce fait max_worker_processes), et max_replication_slots dans le fichier /srv/data/linuxpratique.conf :

wal_level = logical
max_worker_processes = 30
max_logical_replication_workers = 20
max_replication_slots = 20

Pour que cette modification soit prise en compte, il faut redémarrer le serveur :

systemctl restart postgresql-15

Pour rappel, le serveur en version 15 utilise le port 5432, et le serveur en version 16 utilise le port 5433.

Commençons par sauvegarder la définition des objets globaux sur le serveur en version 15 et à la restaurer sur le serveur en version 16 :

pg_dumpall -p 5432 --globals-only | psql -qp 5433

Le script suivant va récupérer la liste des bases où il est possible de se connecter et, pour chacune de ses bases, va :

  • créer la base sur le serveur en version 16 ;
  • sauvegarder le schéma de la base sur le serveur en version 15 ;
  • restaurer ce schema sur cette base du serveur en version 16 ;
  • créer la publication de toutes les tables de la base du serveur en version 15 ;
  • abonner le serveur en version 16 à la publication tout juste créée.
psql -p 5432 -XAtc "SELECT datname FROM pg_database WHERE datallowconn" | while read base
do
  createdb -p 5433 ${base}
  pg_dump -p 5432 -s ${base} | psql -qp 5433 ${base}
  psql -p 5432 -c "CREATE PUBLICATION pub_${base} FOR ALL TABLES" ${base}
  psql -p 5433 -c "CREATE SUBSCRIPTION sub_${base} CONNECTION 'dbname=${base}' PUBLICATION pub_${base}" ${base}
done

Ce script donnera deux erreurs sur la création des bases postgres et template1. Elles ne peuvent pas être créées étant donné qu’elles existent déjà.

À chaque création de souscription (ou abonnement), PostgreSQL va commencer par une phase de synchronisation des données entre le serveur en version 15 et le serveur en version 16. Dans mon cas, j’ai quatre bases, et donc quatre synchronisations en cours. Je dois attendre la fin de ces synchronisations, et pour cela, je dois surveiller le catalogue système pg_stat_replication :

psql -p 5432 -c "SELECT application_name, pg_current_wal_lsn(), sent_lsn FROMpg_stat_replication"
 application_name | pg_current_wal_lsn |  sent_lsn  
------------------+--------------------+------------
 sub_postgres     | 0/52008E98         | 0/52008E98
 sub_b1           | 0/52008E98         | 0/52008E98
 sub_b2           | 0/52008E98         | 0/52008E98
 sub_temboard     | 0/52008E98         | 0/52008E98
(4 rows)

Avec ce résultat, la deuxième colonne a la même valeur que la troisième colonne pour chaque ligne. Les quatres synchronisations sont donc terminées.

Il est néanmoins un type de données à synchroniser. Les séquences ne font pas partie des données répliquées. Il convient donc de les réinitialiser manuellement :

psql -p 5432 -Atc        \
  "SELECT format('ALTER SEQUENCE %I.%I RESTART WITH %s', schemaname, sequencename, last_value+1)
   FROM pg_sequences" b1 \
  | psql -p 5433 b1

Maintenant, nous pouvons arrêter le serveur en version 15 :

systemctl stop postgresql-15

et supprimer les souscriptions du serveur en version 16. :

psql -p 5433 -XAtc "SELECT datname FROM pg_database WHERE datallowconn" | while read base
do
  psql -p 5433 -c "ALTER SUBSCRIPTION sub_${base} DISABLE"                \
               -c "ALTER SUBSCRIPTION sub_${base} SET (slot_name = NONE)" \
               -c "DROP SUBSCRIPTION  sub_${base}"                        \
               ${base}
done

Simplement supprimer la souscription ne suffit pas. En effet, à l’exécution d’un DROP SUBSCRIPTION, PostgreSQL va se connecter au serveur de publication pour supprimer le slot de réplication. Or nous avons arrêté le serveur en version 15 pour éviter de nouvelles écritures sur ce serveur. Donc nous devons supprimer l’information du slot de réplication sur la souscription (c’est le but de l’ordre ALTER SUBSCRIPTION ... SET (slot_nane=NONE)) mais pour cela, nous devons tout d’abord désactiver la souscription, d’où le ALTER SUBSCRIPTION ... DISABLE.

Méthode pg_upgrade

pg_upgrade est certainement l’outil le plus récent mais il a quand même maintenant une dizaine d’années. Autant dire que les bugs assez catastrophiques des premières années sont du passé.

Il fonctionne en deux modes : copie de fichiers et liens physiques. Le mode copie est plus long que le mode lien physique, mais il a l’avantage de permettre un retour arrière si nécessaire, ce qui ne sera pas le cas avec le mode lien physique. Dans le cas de l’utilisation de ce deuxième mode, il convient donc de s’assurer d’avoir à sa disposition une sauvegarde complète de l’instance au cas malheureux où un retour arrière serait nécessaire.

Les étapes de la mise à jour sont très restreintes : arrêt du serveur, lancement de pg_upgrade, démarrage du serveur. Voici un exemple concernant ma VM :

systemctl stop postgresql-15
systemctl stop postgresql-16
/usr/pgsql-16/bin/pg_upgrade \
  -b /usr/pgsql-15/bin       \
  -B /usr/pgsql-16/bin       \
  -d /srv/data               \
  -D /srv/data16
systemctl start postgresql-16

L’outil pg_upgrade indique qu’il faut exécuter une étape supplémentaire mais cette étape est nécessaire quelque soit la méthode. Nous la verrons donc dans le prochain chapitre.

Nettoyage et remise en place des services

Il reste maintenant du nettoyage à faire pour revenir dans la situation nominale : déplacer les fichiers de données dans /srv/data et les journaux de transactions dans /srv/wal, et utiliser le port 5432 pour le service PostgreSQL en version 16.

Commençons déjà par arrêter les deux serveurs :

systemctl stop postgresql-15
systemctl stop postgresql-16

Nous allons maintenant déplacer les fichiers de données et les journaux de transactions de la version 15 dans un emplacement autre en attendant de les supprimer après quelques jours :

mv /srv/data /srv/data15
rm /srv/data15/pg_wal
install -d -o postgres -g postgres -m 700 /srv/data15/pg_wal
mv /srv/wal/* /srv/data15/pg_wal

Nous pouvons maintenant déplacer les fichiers de données et les journaux de transaction de la version 16 à leur emplacement nominal :

mv /srv/data16 /srv/data
mv /srv/data/pg_wal/* /srv/wal
rm -r /srv/data/pg_wal/
ln -s /srv/wal /srv/data/pg_wal

Enfin, avant de démarrer PostgreSQL, nous devons configurer le port de connexion à 5432 :

sed -i.old -e 's/port = 5433/port = 5432/' /srv/data/linuxpratique.conf

Notez que si vous voulez toujours pouvoir démarrer la version 15 en même temps que la version 16, vous devez changer son numéro de port (par le 5433 par exemple).

Il ne reste plus qu’à modifier le répertoire de données dans le script de démarrage de la version 16 :

cat > /etc/systemd/system/postgresql-16.service.d/override.conf <<_EOF_
[Service]
Environment=PGDATA=/srv/data
_EOF_

Il est enfin possible de démarrer PostgreSQL version 16 :

systemctl daemon-reload
systemctl start postgresql-16

La version 15 reste éteinte mais disponible dans le cas où des vérifications seraient à entreprendre (mais n’oubliez pas de modifier son numéro de port et son script de démarrage).

Une fois la nouvelle version en place, il est essentiel d’exécuter un VACUUM ANALYZE sur toutes bases. C’était justement l’étape supplémentaire préconisée par pg_upgrade mais, encore une fois, elle est valable pour toutes les méthodes. Cela peut se faire en une seule commande Unix :

/usr/pgsql-16/bin/vacuumdb --all --analyze-in-stages

Nous avons mis en place deux services de sauvegarde. Il faut s’assurer qu’ils fonctionnent avec la nouvelle version.

Ce ne sera pas le cas immédiatement pour pg_back. En effet, nous lui avons indiqué dans son fichier de configuration où se trouvaient les binaires de PostgreSQL. Il faut donc modifier sa configuration. Cela se fait ainsi :

sed -i.old -e 's#/usr/pgsql-15/bin#/usr/pgsql-16/bin#' /etc/pg_back/pg_back.conf

Quant à pgbackrest, les sauvegardes précédemment effectuées sont incompatibles sur une autre version majeure. Il est donc préférable de supprimer les anciennes sauvegardes et de refaire une sauvegarde complète dès que possible :

rm -rf /srv/backups/pgbackrest/*
pgbackrest stanza-create --stanza linuxpratique
pgbackrest backup --stanza linuxpratique

Quant aux outils de supervision/monitoring, pgBadger n’a pas d’attachement fort à la version majeure. Profitez-en néanmoins pour le mettre à jour. PgCluu et temboard lisant les catalogues statistiques, ces derniers ont pu changer entre deux versions majeures. Il est donc essentiel d’avoir une version de ces deux outils compatible avec cette nouvelle version de PostgreSQL. Dans le doute, mettez les à jour.

Pensez à faire de même pour tous les autres outils se connectant à PostgreSQL : outils d’administration, outils de modélisation, pilotes JDBC ou autre, etc.

Suppression de l’ancienne instance

Il est conseillé d’attendre au moins quelques jours, pour s’assurer que la nouvelle version fonctionne bien, que les données n’ont pas été altérées, qu’il n’y a pas eu d’erreurs commises, bref, qu’il n’y a pas de mauvaise surprise suite au passage à la nouvelle version de PostgreSQL. Évidemment, cela nécessite d’avoir suffisamment de place sur le serveur pour héberger deux versions de l’instance, mais il est toujours rassurant d’avoir l’ancienne version en place.

Si tous les voyants sont au vert, il est important de supprimer l’ancienne instance. Pour cela, il faut supprimer les données et les programmes installés. Voici comment faire en trois commandes :

systemctl stop postgresql-15 # au cas ou il serait démarré
rm -rf /srv/data15
dnf remove postgresql-15

Quel méthode choisir ?

Maintenant que nous avons vu les trois méthodes possibles, discutons de laquelle choisir.

pg_dump/pg_restore

+ fonctionne dans tous les cas
+ mise en place simple
- coupure de service importante
+ supprime la fragmentation
- retour arrière possible mais sans conservation des nouvelles données

réplication logique

- peut ne pas fonctionner (voir plus bas)
- mise en place complexe
- mise en place encore plus complexe si plus d'une base utilisateur
+ coupure de service très faible
+ supprime la fragmentation
+ retour arrière possible avec conservation des nouvelles données (simple avec Slony, compliqué avec réplication native)
- pas de changement de structure de la base pendant la synchronisation
- clé primaire ou contrainte d'unicité obligatoire pour toutes les tables
- pas de support des Large Objects

pg_upgrade

- peut ne pas fonctionner
+ mise en place simple
+ coupure de service faible
- conserve la fragmentation
- retour arrière possible si mode copie mais sans conservation des nouvelles données

Et les versions mineures ?

Il n’y a strictement aucune raison valable d’ignorer les versions mineures. Elles ne contiennent que des corrections de bugs, il faut donc les installer. C’est heureusement bien plus simple et bien plus rapide que les mises à jour majeures. Généralement, il suffit d’arrêter le service PostgreSQL, de mettre à jour les paquets, puis de redémarrer le service. Donc :

systemctl stop postgresql-16
dnf update
systemctl start postgresql-16

Il est néanmoins préférable de lire les Release Notes de la version pour savoir si des opérations supplémentaires sont à réaliser. C’est rare mais cela peut arriver, autant le savoir.


Depuis quelques années, Guillaume Lelarge publie des articles dans le magazine « Linux Pratique » édité par les Éditions Diamond. Avec leur accord, il reprend ici une série destinée à guider l’installation, la maintenance et l’utilisation de PostgreSQL.

Cet article parle d’une mise à jour vers la version 16 comme si c’était la dernière version sortie. Ceci était vrai au moment de la rédaction de cet article. Le contrat qui nous lie aux Éditions Diamond nous impose de publier l’article tel qu’il est paru chez eux, et c’est bien normal. Cependant, il est tout aussi vrai que ce n’est plus la dernière version majeure publiée par la communauté PostgreSQL. Cet article reste utilisable pour mettre à jour vers n’importe quelle version de PostgreSQL actuellement maintenue, à condition évidemment de changer le numéro de version dans les commandes indiquées.

La version que vous installez dépend de trois facteurs : les versions actuellement maintenues par la communauté, les versions supportées par les éditeurs de logiciel utilisant l’instance mise à jour, et votre politique spécifique concernant les versions des logiciels que vous utilisez. À l’heure de publication de cet article sur le blog de Dalibo, nous vous conseillons fortement les versions 16 et 17 pour une mise à jour.


DALIBO

DALIBO est le spécialiste français de PostgreSQL®. Nous proposons du support, de la formation et du conseil depuis 2005.