Réplication par streaming, promotion, installation de PostgreSQL et travaux pratiques avec solutions.
HAPAT
Réplication physique : rappels
Figure 3-45-f01Diapositive d’ouverture du chapitre 3, page PDF 45.
Introduction
Patroni repose sur la réplication physique de PostgreSQL
la haute disponibilité implique les équipes techniques
les équipes techniques doivent comprendre la mécanique
Note :
Patroni configure la réplication physique native de PostgreSQL pour assurer la redondance des données au sein de l’agrégat. Il est important de bien maîtriser comment cette réplication fonctionne.
Effectivement, Patroni ayant pour but d’optimiser la disponibilité, l’administrateur doit être lui-même réactif et identifier rapidement les causes d’un incident ou d’un problème de réplication, savoir ré-intégrer un nœud en réplication, etc. Et ce, sans créer de sur-incident bien entendu.
Au menu
Mise en place de la réplication physique
Promotion
Retour à l’état stable
Note :
Réplication par streaming
Mise en place de la réplication par streaming
Réplication en flux
Un processus du serveur primaire discute avec un processus du serveur secondaire
d’où un lag moins important
Asynchrone ou synchrone
En cascade
Note :
Le serveur PostgreSQL secondaire lance un processus appelé walreceiver, dont le but est de se connecter au serveur primaire et d’attendre les modifications de la réplication.
Le walreceiver a donc besoin de se connecter sur le serveur PostgreSQL primaire. Ce dernier doit être configuré pour accepter cette connexion. Quand elle est acceptée par le serveur primaire, le serveur PostgreSQL du serveur primaire lance un nouveau processus, appelé walsender. Ce dernier a pour but d’envoyer les données de réplication au serveur secondaire. Les données de réplication sont envoyées suivant l’activité et certains paramètres de configuration.
Cette méthode permet une réplication plus proche du serveur primaire que le log shipping. On peut même configurer un mode synchrone : un client du serveur primaire ne récupère pas la main tant que ses modifications ne sont pas enregistrées sur le serveur primaire et sur le serveur secondaire synchrone. Cela s’effectue à la validation de la transaction, implicite ou lors d’un COMMIT.
Enfin, la réplication en cascade permet à un secondaire de fournir les informations de réplication à un autre secondaire, déchargeant ainsi le serveur primaire d’un certain travail et diminuant aussi la bande passante réseau utilisée par le serveur primaire.
Serveur primaire (1/2) - Configuration
Dans postgresql.conf :
wal_level = replica (ou logical)
max_wal_senders = X
1 par client par streaming
défaut : 10
wal_sender_timeout = 60s
Note :
Il faut tout d’abord s’assurer que PostgreSQL enregistre suffisamment d’informations pour que le serveur secondaire puisse rejouer toutes les modifications survenant sur le serveur primaire. Dans certains cas, PostgreSQL peut économiser l’écriture de journaux quand cela ne pose pas de problème pour l’intégrité des données en cas de crash. Par exemple, sur une instance sans archivage ni réplication, il est inutile de tracer la totalité d’une transaction qui commence par créer une table, puis qui la remplit. En cas de crash pendant l’opération, l’opération complète est annulée, la table n’existera plus : PostgreSQL peut donc écrire directement son contenu sur le disque sans journaliser.
Cependant, pour restaurer cette table ou la répliquer, il est nécessaire d’avoir les étapes intermédiaires (le contenu de la table) et il faut donc écrire ces informations supplémentaires dans les journaux.
Le paramètre wal_level fixe le comportement à adopter. Comme son nom l’indique, il permet de préciser le niveau d’informations que l’on souhaite avoir dans les journaux. Il connaît trois valeurs :
Le niveau replica est adapté à l’archivage ou la réplication, en plus de la sécurisation contre les arrêts brutaux. C’est le niveau par défaut. L’optimisation évoquée plus haut n’est pas possible.
Le niveau minimal n’offre que la protection contre les arrêts brutaux, mais ne permet ni réplication ni sauvegarde PITR. Ce niveau ne sert plus guère qu’aux environnements ni archivés, ni répliqués, pour réduire la quantité de journaux générés, comme dans l’optimisation ci-dessus.
Le niveau logical est le plus complet et doit être activé pour l’utilisation du décodage logique, notamment pour utiliser la réplication logique. Il n’est pas nécessaire pour la sauvegarde PITR ou la réplication physique, ni incompatible.
Le serveur primaire accepte un nombre maximum de connexions de réplication : il s’agit du paramètre max_wal_senders. Il faut compter au moins une connexion pour chaque serveur secondaire susceptible de se connecter, ou les outils utilisant le streaming comme pg_basebackup ou pg_receivewal. Il est conseillé de prévoir « large » d’entrée : l’impact mémoire est négligeable, et cela évite d’avoir à redémarrer l’instance primaire à chaque modification. La valeur par défaut de 10 devrait suffire dans la plupart des cas.
Le paramètre wal_sender_timeout permet de couper toute connexion inactive après le délai indiqué par ce paramètre. Par défaut, le délai est d’une minute. Cela permet au serveur primaire de ne pas conserver une connexion coupée ou dont le client a disparu pour une raison ou une autre. Le secondaire retentera par la suite une connexion complète.
Serveur primaire (2/2) - Authentification
Le serveur secondaire doit pouvoir se connecter au serveur primaire
Pseudo-base replication
Utilisateur dédié conseillé avec attributs LOGIN et REPLICATION
Configurer pg_hba.conf :
Exemple 3-e001Extrait de code ou sortie de commande du chapitre 3.
Il est nécessaire après cela de configurer le fichier pg_hba.conf. Dans ce fichier, une ligne (par secondaire) doit indiquer les connexions de réplication. L’idée est d’éviter que tout le monde puisse se connecter pour répliquer l’intégralité des données.
Pour distinguer une ligne de connexion standard et une ligne de connexion de réplication, la colonne indiquant la base de données doit contenir le mot « replication ». Par exemple :
Exemple 3-e002Extrait de code ou sortie de commande du chapitre 3.
Dans ce cas, l’utilisateur user_repli pourra entamer une connexion de réplication vers le serveur primaire à condition que la demande de connexion provienne de l’adresse IP 10.0.0.2 et que cette demande de connexion précise le bon mot de passe au format scram-sha-256.
Un utilisateur dédié à la réplication est conseillé pour des raisons de sécurité. On le créera avec les droits suivants :
Exemple 3-e003Extrait de code ou sortie de commande du chapitre 3.
CREATEROLEuser_repliLOGINREPLICATION;
et bien sûr un mot de passe complexe.
Les connexions locales de réplication sont autorisées par défaut sans mot de passe.
Après modification du fichier postgresql.conf et du fichier pg_hba.conf, il est temps de demander à PostgreSQL de recharger sa configuration. L’action reload suffit dans tous les cas, sauf celui où max_wal_senders est modifié (auquel cas il faudra redémarrer PostgreSQL).
Serveur secondaire (1/4) - Copie des données
Copie des données du serveur primaire (à chaud !) :
Copie généralement à chaud donc incohérente !
Le plus simple : pg_basebackup
simple mais a des limites
Idéal : outil PITR
Possible : rsync, cp…
ne pas oublier pg_backup_start()/pg_backup_stop() !
exclure certains répertoires et fichiers
garantir la disponibilité des journaux de transaction
Note :
La première action à réaliser ressemble beaucoup à ce que propose la sauvegarde en ligne des fichiers. Il s’agit de copier le répertoire des données de PostgreSQL ainsi que les tablespaces associés.
Attention :
Rappelons que généralement cette copie aura lieu à chaud, donc une simple copie directe sera incohérente.
pg_basebackup :
L’outil le plus simple est pg_basebackup. Ses avantages sont sa disponibilité et sa facilité d’utilisation. Il sait ce qu’il n’y a pas besoin de copier et peut inclure les journaux nécessaires pour ne pas avoir à paramétrer l’archivage.
Il peut utiliser la connexion de réplication déjà prévue pour le secondaire, poser des slots temporaires ou le slot définitif.
Pour faciliter la mise en place d’un secondaire, il peut générer les fichiers de configuration à partir des paramètres qui lui ont été fournis (option --write-recovery-conf).
Malgré beaucoup d’améliorations dans les dernières versions, la limite principale de pg_basebackup reste d’exiger un répertoire cible vide : on doit toujours recopier l’intégralité de la base copiée. Cela peut être pénible lors de tests répétés avec une grosse base, ou avec une liaison instable. Toutefois, à partir de PostgreSQL 17, il permet une sauvegarde incrémentale.
Outils PITR :
L’idéal est un outil de restauration PITR permettant la restauration en mode delta, par exemple pgBackRest avec l’option --delta. Ne sont restaurés que les fichiers ayant changé, et le primaire n’est pas chargé par la copie.
rsync :
Un script de copie reste une option possible. Il est possible de le faire manuellement, tout comme pour une sauvegarde PITR.
Attention :
Une copie manuelle implique que les journaux sont archivés par ailleurs.
Rappelons les trois étapes essentielles :
le pg_backup_start() ;
la copie des fichiers : généralement avec rsync --whole-file, ou tout moyen permettant une copie fiable et rapide ;
le pg_backup_stop().
On exclura les fichiers inutiles lors de la copie qui pourraient gêner un redémarrage, notamment les fichiers postmaster.pid, postmaster.opts, pg_internal.init, les répertoires pg_wal, pg_replslot, pg_dynshmem, pg_notify, pg_serial, pg_snapshots, pg_stat_tmp, pg_subtrans, pgslq_tmp*. La liste complète figure dans la documentation officielle.
Serveur secondaire (2/4) - Fichiers de configuration
postgresql.conf & postgresql.auto.conf
paramètres
standby.signal (dans PGDATA)
vide
Note :
Au choix, les paramètres sont à ajouter dans postgresql.conf, dans un fichier appelé par ce dernier avec une clause d’inclusion, ou dans postgresql.auto.conf (forcément dans le répertoire de données pour ce dernier, et qui surcharge les fichiers précédents). Cela dépend des habitudes, de la méthode d’industrialisation…
S’il y a des paramètres propres au primaire dans la configuration d’un secondaire, ils seront ignorés, et vice-versa. Dans les cas simples, le postgresql.conf peut donc être le même.
Puis il faut créer un fichier vide nommé standby.signal dans le répertoire PGDATA, qui indique à PostgreSQL que le serveur doit rester en recovery permanent.
Conseil :
Au cas où vous rencontreriez un vieux serveur en version antérieure à la 12 : jusqu’en version 11, on activait le mode standby non dans la configuration, mais en créant un fichier texte recovery.conf dans le PGDATA de l’instance, et en y plaçant le paramètre standby_mode à on. Les autres paramètres sont les mêmes. Toute modification impliquait un redémarrage.
Serveur secondaire (3/4) - Paramètres
primary_conninfo (streaming) :
Exemple 3-e004Extrait de code ou sortie de commande du chapitre 3.
PostgreSQL doit aussi savoir comment se connecter au serveur primaire. C’est le paramètre primary_conninfo qui le lui dit. Il s’agit d’un DSN standard où il est possible de spécifier l’adresse IP de l’hôte ou son alias, le numéro de port, le nom de l’utilisateur, etc. Il est aussi possible de spécifier le mot de passe, mais c’est risqué en terme de sécurité. En effet, PostgreSQL ne vérifie pas si ce fichier est lisible par quelqu’un d’autre que lui. Il est donc préférable de placer le mot de passe dans le fichier .pgpass, généralement dans ~postgres/ sur le secondaire, fichier qui n’est utilisé que s’il n’est lisible que par son propriétaire. Par exemple :
Exemple 3-e005Extrait de code ou sortie de commande du chapitre 3.
Toutes les options de la libpq sont accessibles. Par exemple, cette chaîne de connexion a été générée pour un nouveau secondaire par pg_basebackup -R :
Exemple 3-e006Extrait de code ou sortie de commande du chapitre 3.
S’y trouvent beaucoup de paramétrage par défaut dépendant de méthodes d’authentification, ou pour le SSL.
Parmi les autres paramètres optionnels de primary_conninfo, il est conseillé d’ajouter application_name, par exemple avec le nom du serveur. Cela facilite la supervision. C’est même nécessaire pour paramétrer une réplication synchrone.
Exemple 3-e007Extrait de code ou sortie de commande du chapitre 3.
Si application_name n’est pas fourni, le cluster_name du secondaire sera utilisé, mais il est rarement correctement configuré (par défaut, il vaut 16/main sur Debian/Ubuntu, et n’est pas configuré sur Red Hat/Rocky Linux).
De manière optionnelle, nous verrons que l’on peut définir aussi deux paramètres :
primary_slot_name, pour sécuriser la réplication avec un slot de réplication ;
restore_command, pour sécuriser la réplication avec un accès à la sauvegarde PITR.
Le paramètre wal_receiver_timeout sur le secondaire est le symétrique de wal_sender_timeout sur le primaire. Il indique au bout de combien de temps couper une connexion inactive. Le secondaire retentera la connexion plus tard.
Serveur secondaire (4/4) - Démarrage
Démarrer PostgreSQL
Suivre dans les traces que tout va bien
Note :
Il ne reste plus qu’à démarrer le serveur secondaire.
En cas de problème, le premier endroit où aller chercher est bien entendu le fichier de trace postgresql.log.
Processus
Sur le primaire :
walsender ... streaming 0/3BD48728
Sur le secondaire :
walreceiver streaming 0/3BD48728
Note :
Sur le primaire, un processus walsender apparaît pour chaque secondaire connecté. Son nom de processus est mis à jour en permanence avec l’emplacement dans le flux de journaux de transactions :
Exemple 3-e008Extrait de code ou sortie de commande du chapitre 3.
Si un serveur secondaire devient le nouveau primaire
s’assurer que l’ancien primaire ne reçoit plus d’écriture
Éviter que les deux instances soient ouvertes aux écritures
confusion et perte de données !
Note :
La pire chose qui puisse arriver lors d’une bascule est d’avoir les deux serveurs, ancien primaire et nouveau primaire promu, ouverts tous les deux en écriture. Les applications risquent alors d’écrire dans l’un ou l’autre…
Avant une bascule, il est capital de vérifier que toutes les modifications envoyées par le primaire sont arrivées sur le secondaire. Si le primaire a été arrêté proprement, ce sera le cas. Après un CHECKPOINT sur le secondaire, on y retrouvera le même emplacement dans les journaux de transaction.
Ce contrôle doit être systématique avant une bascule. Même si toutes les écritures applicatives sont stoppées sur le primaire, quelques opérations de maintenance peuvent en effet écrire dans les journaux et provoquer un écart entre les deux serveurs (divergence). Il n’y aura alors pas de perte de données mais cela pourrait gêner la transformation de l’ancien primaire en secondaire, par exemple. En revanche, même avec un arrêt propre du primaire, il peut y avoir perte de données s’il y a un lag important entre primaire et secondaire : même si le rejeu va toujours jusqu’au bout avant le changement de timeline, les WAL qui n’ont pas pu être récupérés avant la déconnexion, ou après la récupération des archives (si le log shipping est en place) sont perdus pour le nouveau primaire.
Noter que pg_controldata n’est pas dans les chemins par défaut des distributions. La fonction SQL pg_control_checkpoint() affiche les même informations, mais n’est bien sûr pas accessible sur un primaire arrêté.
Promotion du standby : méthode
Shell :
pg_ctl promote
SQL :
fonction pg_promote()
Note :
Il existe plusieurs méthodes pour promouvoir un serveur PostgreSQL en mode standby. Les méthodes les plus appropriées sont :
l’action promote de l’outil pg_ctl, ou de son équivalent dans les scripts des paquets d’installation, comme pg_ctlcluster sous Debian ;
la fonction SQL pg_promote.
Ces deux méthodes sont à préférer à la méthode historique du fichier de déclenchement (trigger file), qui existe encore jusque PostgreSQL 15 inclus. Le paramètre promote_trigger_file sur une instance secondaire définit un fichier dont l’apparition provoque la promotion de l’instance. Par sécurité, utiliser un emplacement accessible uniquement aux administrateurs.
Promotion du standby : déroulement
Une promotion déclenche :
déconnexion de la streaming replication (bascule programmée)
rejeu des dernières transactions en attente d’application
récupération et rejeu de toutes les archives disponibles
choix d’une nouvelle timeline du journal de transaction
suppression du fichier standby.signal
nouvelle timeline et fichier .history
ouverture aux écritures
Note :
La promotion se déroule en bonne partie comme un recovery après restauration PITR.
Une fois l’instance promue, elle finit de rejouer les derniers journaux de transaction en provenance du serveur principal en sa possession, puis se déconnecte de celui-ci (si l’on est encore connecté en streaming). Après la déconnexion, si une restore_command est configurée, toutes les archives disponibles sont récupérées et rejouées (en général, il n’y a pas d’archive contenant des WAL plus récents que le dernier récupéré en streaming ; mais des écritures lourdes et/ou un réseau trop lent peuvent entraîner un retard du streaming).
Le dernier journal reçu de l’ancien primaire est souvent incomplet. Il est renommé avec le suffixe .partial et archivé. Cela évite un conflit de nom éventuel avec le même fichier issu de l’ancien serveur, qui a pu aussi être archivé, à un point éventuellement postérieur à la divergence.
Ensuite, l’instance choisit une nouvelle timeline pour son journal de transactions. Rappelons que la timeline est le premier numéro dans le nom du segment (fichier WAL) ; par exemple une timeline 5 pour un fichier nommé 000000050000003200000031. Le nouveau primaire choisit généralement le numéro suivant celui du primaire (à moins que les archives ne contiennent d’autres timelines de numéro supérieur, s’il y a eu plusieurs restaurations et retours en arrière, et il choisit alors le numéro suivant la dernière).
Le choix d’une nouvelle timeline permet à PostgreSQL de rendre les journaux de transactions de ce nouveau serveur en écriture incompatibles avec son ancien serveur principal. De plus, des journaux de nom différent permet l’archivage depuis ce primaire sans perturber l’ancien s’il existe encore. Il n’y a plus de fichier en commun même si l’espace d’archivage est partagé.
Attention :
Les timelines ne changent pas que lors des promotions, mais aussi lors des restaurations PITR. En général, on désire que les secondaires (parfois en cascade) suivent. Heureusement, ceci est le paramétrage par défaut depuis la version 12 :
Exemple 3-e013Extrait de code ou sortie de commande du chapitre 3.
recovery_target_timeline=latest
Un secondaire suit donc par défaut les évolutions de timeline de son primaire, tant que celui-ci n’effectue pas de retour en arrière.
L’instance crée un fichier d’historique dans pg_wal/, par exemple 00000006.history pour la nouvelle timeline 6. C’est un petit fichier texte qui contient les différentes timelines ayant mené à la nouvelle. Ce fichier est immédiatement archivé s’il y a archivage.
Enfin, l’instance autorise les connexions en lecture et en écriture.
Opérations après promotion du standby
VACUUM ANALYZE conseillé
calcul d’informations nécessaires pour autovacuum
Note :
Il n’y a aucune opération obligatoire après une promotion. Cependant, il est conseillé d’exécuter un VACUUM ou un ANALYZE pour que PostgreSQL mette à jour les estimations de nombre de lignes vivantes et mortes. Ces estimations sont utilisées par l’autovacuum pour lutter contre la fragmentation des tables et mettre à jour les statistiques sur les données. Or ces estimations faisant partie des statistiques d’activité, elles ne sont pas répliquées vers les secondaires. Il est donc intéressant de les mettre à jour après une promotion.
Retour à l’état stable
Si un standby a été momentanément indisponible :
Rattrapage possible si tous les journaux sont disponibles :
streaming depuis le primaire (slot, wal_keep_size)
log shipping depuis les archives (restore_command)
ou les deux (si configurés)
Sinon :
« décrochage… »
reconstruction nécessaire
Note :
Si un serveur secondaire est momentanément indisponible mais revient en ligne sans perte de données (réseau coupé, problème OS…), alors il a de bonnes chances de se « raccrocher » à son serveur primaire. Il faut bien sûr que l’ensemble des journaux de transactions depuis son arrêt soit accessible à ce serveur, sans exception.
Prudence :
Si le secondaire ne peut rattraper le flux des journaux du primaire, il peut rester ouvert en lecture, mais il ne rattrapera jamais le primaire. Il est donc irrécupérable et doit être reconstruit par l’une des méthodes précédentes, ce qui peut être long et lourd.
Le secondaire cherche ces journaux par log shipping et par streaming auprès de son primaire, s’ils sont configurés et fonctionnels. Si une méthode échoue, le secondaire tentera l’autre, alternativement, et indéfiniment jusqu’à obtenir les journaux nécessaires. Si une seule méthode est configurée, le secondaire ne tente bien sûr que celle-ci. Tant qu’une méthode fonctionne, le secondaire l’utilise jusqu’à ce qu’elle tombe en échec.
En cas de réplication par streaming :
Le secondaire utilise la primary_conninfo pour demander au primaire les journaux à partir de sa propre position dans le flux de journaux, successivement et dans l’ordre. Le primaire ne doit pas avoir recyclé ou supprimé ces journaux devenus inutiles après leur archivage et un checkpoint, ce qui peut ne prendre que quelques minutes.
Si le primaire n’a plus le journal nécessaire, les deux instances afficheront cette erreur :
Exemple 3-e014Extrait de code ou sortie de commande du chapitre 3.
ERROR: requested WAL segment 000000010000009900000045 has already been removed
On peut forcer le primaire à conserver des journaux pour un serveur secondaire :
La première méthode est simple et consiste à définir un paramètre nommé wal_keep_size, pour que le primaire conserve toujours une certaine quantité de journaux, inutiles pour lui, au cas où un secondaire le demande.
La méthode la plus propre consiste à créer un « slot de réplication » sur le primaire, et dédié à ce secondaire (paramètre primary_slot à définir sur celui-ci). Le primaire sait alors en permanence où en est son secondaire, et lui conserve toujours les journaux nécessaires. C’est très sûr, mais peut avoir des inconvénients.
En cas de réplication par log shipping :
Il faut que la restore_command fonctionne, que le stock des journaux remonte assez loin dans le temps (jusqu’au moment où le secondaire a perdu contact), et qu’aucun journal ne manque ni ne soit corrompu. Souvent, le secondaire utilise le log shipping depuis les archives PITR pour rattraper son retard, jusqu’à ce que l’archivage tombe en erreur après épuisement de tous les journaux disponibles ; puis il bascule sur le streaming si c’est possible.
Conclusion
La réplication physique native est très robuste
Patroni peut automatiser :
la mise en réplication
la promotion
le raccrochage d’un ancien primaire
Note :
La robustesse de la réplication physique est éprouvée depuis longtemps. C’est à cette base solide que Patroni amène l’automatisation de :
l’ajout de nœuds supplémentaires ;
la promotion automatique en fonction du nœud ;
le raccrochage d’un ancien primaire au nouveau primaire.
Ces mécanismes sont abordé dans le module consacré à Patroni.
Installation de PostgreSQL depuis les paquets communautaires
Note :
L’installation est détaillée ici pour Rocky Linux 8 et 9 (similaire à Red Hat et à d’autres variantes comme Oracle Linux et Fedora), et Debian/Ubuntu.
Elle ne dure que quelques minutes.
Sur Rocky Linux 8, 9 ou 10
Attention :
ATTENTION : Red Hat, CentOS, Rocky Linux fournissent souvent par défaut des versions de PostgreSQL qui ne sont plus supportées. Ne jamais installer les packages postgresql, postgresql-client et postgresql-server ! L’utilisation des dépôts du PGDG est fortement conseillée.
Les outils clients et les librairies nécessaires seront automatiquement installés.
Une fonctionnalité avancée optionnelle, le JIT (Just In Time compilation), nécessite un paquet séparé.
Exemple 3-e017Extrait de code ou sortie de commande du chapitre 3.
# dnf install postgresql18-llvmjit
Création d’une première instance :
C’est optionnel, mais il est conseillé de déclarer PGSETUP_INITDB_OPTIONS, notamment pour forcer les traces en anglais. Depuis la version 18, les sommes de contrôle sont automatiquement activées.
Exemple 3-e018Extrait de code ou sortie de commande du chapitre 3.
(Rappelons que listen_addresses doit être également modifié dans postgresql.conf.)
Création d’autres instances :
Si des instances de versions majeures différentes doivent être installées, il faut d’abord installer les binaires pour chacune (adapter le numéro dans dnf install …) et appeler le script d’installation de chaque version. l’instance par défaut de chaque version vivra dans un sous-répertoire numéroté de /var/lib/pgsql automatiquement créé à l’installation. Il faudra juste modifier les ports dans les postgresql.conf pour que les instances puissent tourner simultanément.
Si plusieurs instances d’une même version majeure (forcément de la même version mineure) doivent cohabiter sur le même serveur, il faut les installer dans des PGDATA différents.
Ne pas utiliser de tiret dans le nom d’une instance (problèmes potentiels avec systemd).
Respecter les normes et conventions de l’OS : placer les instances dans un nouveau sous-répertoire de /var/lib/pgsqsl/18/ (ou l’équivalent pour d’autres versions majeures).
Pour créer une seconde instance, nommée par exemple infocentre :
Création du fichier service de la deuxième instance :
Exemple 3-e025Extrait de code ou sortie de commande du chapitre 3.
Ce dernier ordre créera le fichier du dépôt /etc/apt/sources.list.d/pgdg.list adapté à la distribution en place.
Installation de PostgreSQL 18 :
La méthode la plus propre consiste à modifier la configuration par défaut avant l’installation :
Dans /etc/postgresql-common/createcluster.conf, paramétrer au moins les traces en anglais. Depuis la version 18, les sommes de contrôle sont automatiquement activées.
Exemple 3-e030Extrait de code ou sortie de commande du chapitre 3.
initdb_options='--lc-messages=C'
Puis installer les paquets serveur et clients et leurs dépendances :
Exemple 3-e031Extrait de code ou sortie de commande du chapitre 3.
# apt install postgresql-18 postgresql-client-18
La première instance est automatiquement créée, avec un nom (main par défaut), déclarée comme service à lancer au démarrage du système, puis démarrée.
Exemple 3-e032Extrait de code ou sortie de commande du chapitre 3.
$ pg_lsclusters
Ver Cluster Port Status Owner Data directory Log file
18 main 5432 online postgres /var/lib/postgresql/18/main /var/log/postgresql/postgresql-18-main.log
$ systemctl status postgresql@18-main
● postgresql@18-main.service - PostgreSQL Cluster 18-main
Loaded: loaded (/usr/lib/systemd/system/postgresql@.service; enabled-runtime; preset: enabled)
Active: active (running) since Fri 2026-01-30 15:18:43 UTC; 2min 13s ago
…
L’utilisateur système postgres peut y accéder immédiatement.
Chemins :
Objet
Chemin
Binaires
/usr/lib/postgresql/18/bin/
Répertoire de l’utilisateur postgres
/var/lib/postgresql
PGDATA de l’instance par défaut
/var/lib/postgresql/18/main
Fichiers de configuration
dans /etc/postgresql/18/main/
Traces
dans /var/log/postgresql/
Tableau 3-t02Tableau du chapitre 3, extrait du manuel HAPAT.
Configuration
Modifier postgresql.conf est facultatif pour un premier essai.
Démarrage/arrêt de l’instance, rechargement de configuration :
Debian fournit ses propres outils, qui demandent en paramètre la version et le nom de l’instance :
Exemple 3-e033Extrait de code ou sortie de commande du chapitre 3.
# pg_ctlcluster 18 main [start|stop|reload|status|restart]
Démarrage de l’instance avec le serveur :
C’est en place par défaut, et modifiable dans /etc/postgresql/18/main/start.conf.
Ouverture du firewall :
Debian et Ubuntu n’installent pas de firewall par défaut.
Statut des instances du serveur :
Exemple 3-e034Extrait de code ou sortie de commande du chapitre 3.
# pg_lsclusters
Exemple 3-e035Extrait de code ou sortie de commande du chapitre 3.
Ver Cluster Port Status Owner Data directory Log file
18 main 5432 online postgres /var/lib/postgresql/18/main /var/log/postgresql/postgresql-18-main.log
Test rapide de bon fonctionnement et connexion à psql :
Exemple 3-e036Extrait de code ou sortie de commande du chapitre 3.
Exemple 3-e037Extrait de code ou sortie de commande du chapitre 3.
# pg_dropcluster 18 main
Création d’autres instances :
Ce qui suit est valable pour remplacer l’instance par défaut par une autre, par exemple pour activer les sommes de contrôles (checksums) avant la version v18 :
optionnellement, /etc/postgresql-common/createcluster.conf permet de mettre en place tout d’entrée les messages en anglais, le format des traces, l’emplacement séparé pour les journaux ou les checksums pour une version inférieure à 18 :
Exemple 3-e038Extrait de code ou sortie de commande du chapitre 3.
Exemple 3-e039Extrait de code ou sortie de commande du chapitre 3.
# pg_createcluster 18 infocentre
Il est également possible de préciser certains paramètres du fichier postgresql.conf, voire les chemins des fichiers (il est conseillé de conserver les chemins par défaut) :
Exemple 3-e040Extrait de code ou sortie de commande du chapitre 3.
adapter au besoin /etc/postgresql/18/infocentre/postgresql.conf ;
démarrage :
Exemple 3-e041Extrait de code ou sortie de commande du chapitre 3.
# pg_ctlcluster 18 infocentre start
Accès à l’instance depuis le serveur même (toutes distributions)
Par défaut, l’instance n’est accessible que par l’utilisateur système postgres, qui n’a pas de mot de passe. Un détour par sudo est nécessaire :
Exemple 3-e042Extrait de code ou sortie de commande du chapitre 3.
$ sudo -iu postgres psql
psql (18.1)
Type "help" for help.
postgres=#
Ce qui suit permet la connexion directement depuis un utilisateur du système :
Pour des tests (pas en production !), il suffit de passer à trust le type de la connexion en local dans le pg_hba.conf :
Exemple 3-e043Extrait de code ou sortie de commande du chapitre 3.
local all postgres trust
La connexion en tant qu’utilisateur postgres (ou tout autre) n’est alors plus sécurisée :
Exemple 3-e044Extrait de code ou sortie de commande du chapitre 3.
dalibo:~$ psql -U postgres
psql (18.1)
Type "help" for help.
postgres=#
Une authentification par mot de passe est plus sécurisée :
dans pg_hba.conf, paramétrer une authentification par mot de passe pour les accès depuis localhost (déjà en place sous Debian) :
Exemple 3-e045Extrait de code ou sortie de commande du chapitre 3.
# IPv4 local connections:
host all all 127.0.0.1/32 scram-sha-256
# IPv6 local connections:
host all all ::1/128 scram-sha-256
(Ne pas oublier de recharger la configuration en cas de modification.)
ajouter un mot de passe à l’utilisateur postgres de l’instance :
Exemple 3-e046Extrait de code ou sortie de commande du chapitre 3.
dalibo:~$ sudo -iu postgres psql
psql (18.1)
Type "help" for help.
postgres=# \password
Enter new password for user "postgres":
Enter it again:
postgres=# quit
dalibo:~$ psql -h localhost -U postgres
Password for user postgres:
psql (18.1)
Type "help" for help.
postgres=#
Pour se connecter sans taper le mot de passe à une instance, un fichier .pgpass dans le répertoire personnel doit contenir les informations sur cette connexion :
Exemple 3-e047Extrait de code ou sortie de commande du chapitre 3.
localhost:5432:*:postgres:motdepassetrèslong
Ce fichier doit être protégé des autres utilisateurs :
Exemple 3-e048Extrait de code ou sortie de commande du chapitre 3.
$ chmod 600 ~/.pgpass
Pour n’avoir à taper que psql, on peut définir ces variables d’environnement dans la session voire dans ~/.bashrc :
Exemple 3-e049Extrait de code ou sortie de commande du chapitre 3.
en cas de problème, consulter les traces (dans /var/lib/pgsql/18/data/log ou /var/log/postgresql/) ;
toute modification de pg_hba.conf ou postgresql.conf impliquant de recharger la configuration peut être réalisée par une de ces trois méthodes en fonction du système :
Exemple 3-e050Extrait de code ou sortie de commande du chapitre 3.
root:~# systemctl reload postgresql-18
Exemple 3-e051Extrait de code ou sortie de commande du chapitre 3.
root:~# pg_ctlcluster 18 main reload
Exemple 3-e052Extrait de code ou sortie de commande du chapitre 3.
Les commandes systemctl doivent être exécutées avec l’utilisateur root ou (de préférence) avec la commande sudo, depuis un utilisateur qui dispose des droits nécessaire. Par défaut, ce n’est pas le cas pour l’utilisateur postgres. Dans les machines virtuelles, l’utilisateur admin dispose de ces droits.
Il est possible de changer ce comportement, en exécutant la commande sudo visudo et en ajoutant la ligne postgres après root :
Exemple 3-e053Extrait de code ou sortie de commande du chapitre 3.
root ALL=(ALL:ALL)
postgres ALL=(ALL) NOPASSWD: ALL
L’utilisateur postgres sera sudoer au prochain démarrage de session. Attention à la syntaxe et à l’éventuel message d’erreur de visudo, une erreur dans le fichier peut empêcher tout accès à la VM. Cette solution n’est pas recommandée en production car elle donne trop de droits a l’utilisateur postgres.
Sur Rocky Linux 8 ou 9
Note :
Attention :
Ce TP utilise deux VM : p1 et p2.
Dans la réalité, les accès entre VM seront probablement filtrés par des firewalls qu’il faudra configurer. firewalld n’est pas activé sur les VM de TP.
Réplication asynchrone en flux avec un seul secondaire
Note :
But : Mettre en place une réplication asynchrone en flux.
Créer l’instance principale dans /var/lib/pgsql/17/main sur le serveur p1. Les sommes de contrôles devront être activées.
Mettre en place la configuration de la réplication par streaming.
L’utilisateur dédié sera nommé repli.
Créer la première instance secondaire sur le serveur p2, par copie à chaud du répertoire de données.
Démarrer la nouvelle instance sur p2 et s’assurer que la réplication fonctionne bien avec ps.
Tenter de se connecter au serveur secondaire.
Créer quelques tables pour vérifier que les écritures se propagent du primaire au secondaire.
Promotion de l’instance secondaire
Note :
But : Promouvoir un serveur secondaire en primaire.
En respectant les étapes de vérification de l’état des instances, effectuer une promotion contrôlée de l’instance secondaire.
Tenter de se connecter au serveur secondaire fraîchement promu.
Les écritures y sont-elles possibles ?
Retour à la normale
Note :
But : Revenir à l’architecture d’origine.
Reconstruire l’instance initiale sur p1 comme nouvelle instance secondaire en repartant d’une copie complète de p2 en utilisant pg_basebackup.
Démarrer cette nouvelle instance.
Vérifier que les processus adéquats sont bien présents, et que les données précédemment insérées dans les tables créées plus haut sont bien présentes dans l’instance reconstruite.
Inverser à nouveau les rôles des deux instances afin que p2 redevienne l’instance secondaire, cette fois-ci effectuer la remise en service de l’ancienne instance primaire sans reconstruction.
Optionnel: Types de réplication synchrone
Note :
But : Montrer le fonctionnement de la réplication synchrone en fonction du paramétrage de synchronous_standby_names.
Au début du TP, deux instances sont disponibles p1, la primaire, et p2 sa standby.
Configurer la réplication synchrone de p1 vers p2.
Stopper l’instance secondaire p2, créer une table t1, que se passe-t-il ?
Redémarrer l’instance p2, que se passe-t-il ?
Stopper l’instance p2, créer une table t2. Annuler la requête bloquée avec la commande pg_cancel_backend(), qu’observez-vous ?
Suivre la méthode utilisée dans les TP précédants pour mettre en place une seconde instance standby sur p3.
Configurer la réplication synchrone de p1 vers p2 et p3 en utilisant la syntaxe p2, p3 pour synchronous_standby_names. Observer les champs sync_priority et sync_state de pg_stat_replication.
Stopper p2 puis p3, redémarrer p3 puis p2. À chaque étape regarder l’état de la vue pg_stat_replication.
Modifier la configuration de p1 pour demander deux standbys synchrones.
Configurer synchronous_standby_names à *. Répéter les mêmes tests que précédemment. Qu’observez-vous ?
Redémarrer p2.
Mettre en place la réplication synchrone par quorum avec un serveur synchrone minimum.
Désactiver la réplication synchrone.
Optionnel: Log shipping et initialisation d’instance avec pgBackRest
Note :
But : Montrer l’utilisation de pgBackRest dans une architecture de réplication
Nous allons utiliser la vm b1 comme serveur de sauvegarde et y créer un dépôt pgBackRest dédié.
Installer pgBackRest sur tous les serveurs (p1, p2, p3, b1).
Configurer pgBackRest sur le serveur de sauvegarde.
Configurer pgBackRest sur les serveurs de base de données.
Créer la stanza, tester l’archivage.
Lancer une sauvegarde.
Si p3 existe, le stopper et détruire le répertoire de données.
Créer l’instance à partir de la sauvegarde.
Interdire la connexion à p1 depuis p3, que se passe-t-il sur p3 ?
Faire marche arrière sur la modification, que se passe-t-il sur p3 ?
Optionnel: pg_rewind
Note :
But : Remonter une instance secondaire après une divergence.
L’instance p1 est l’instance primaire.
Promouvoir l’instance p2. Générer de l’activité sur p1.
Stopper p1 et configurer la réplication pour raccrocher p1 à p2. Qu’observez-vous ?
Utiliser pg_rewind pour remettre l’instance p1 d’aplomb.
Sur Debian 12
Note :
Attention :
Ce TP utilise deux machines virtuelles : p1 et p2.
Dans la réalité, les accès entre VM seront probablement filtrés par des firewalls qu’il faudra configurer. Par défaut, aucun firewall n’est installé sur Debian, cette étape sera donc inutile dans notre cas.
Réplication asynchrone en flux avec un seul secondaire
Note :
But : Mettre en place une réplication asynchrone en flux.
Ce TP suppose que les binaires de PostgreSQL sont présents mais qu’aucune instance n’est en place. - Vérifier avec pg_lsclusters que les binaires de PostgreSQL sont installés et qu’il n’existe pas d’autre instance. - Créer l’instance principale en utilisant pg_createcluster sur le serveur p1. Les sommes de contrôles devront être activées.
Vérifier la configuration de la réplication par streaming.
L’utilisateur dédié sera nommé repli.
Créer la première instance secondaire sur le serveur p2, par copie à chaud du répertoire de données. Le répertoire dédié aux fichiers de configuration devra également être copié.
Démarrer la nouvelle instance sur p2 et s’assurer que la réplication fonctionne bien avec ps.
Tenter de se connecter au serveur secondaire.
Créer quelques tables pour vérifier que les écritures se propagent du primaire au secondaire.
Promotion de l’instance secondaire
Note :
But : Promouvoir un serveur secondaire en primaire.
En respectant les étapes de vérification de l’état des instances, effectuer une promotion contrôlée de l’instance secondaire.
Tenter de se connecter au serveur secondaire fraîchement promu.
Les écritures y sont-elles possibles ?
Retour à la normale
Note :
But : Revenir à l’architecture d’origine.
Reconstruire l’instance initiale sur p1 comme nouvelle instance secondaire en repartant d’une copie complète de p2 en utilisant pg_basebackup.
Démarrer cette nouvelle instance.
Vérifier que les processus adéquats sont bien présents, et que les données précédemment insérées dans les tables créées plus haut sont bien présentes dans l’instance reconstruite.
Inverser à nouveau les rôles des deux instances afin que p2 redevienne l’instance secondaire, cette fois-ci effectuer la remise en service de l’ancienne instance primaire sans reconstruction.
Optionnel: Types de réplication synchrone
Note :
But : Montrer le fonctionnement de la réplication synchrone en fonction du paramétrage de synchronous_standby_names.
Au début du TP, deux instances sont disponibles p1, la primaire, et p2 sa standby.
Configurer la réplication synchrone de p1 vers p2.
Stopper l’instance secondaire p2, créer une table t1, que se passe-t-il ?
Redémarrer l’instance p2, que se passe-t-il ?
Stopper l’instance p2, créer une table t2. Annuler la requête bloquée avec la commande pg_cancel_backend(), qu’observez-vous ?
Suivre la méthode utilisée dans les TP précédants pour mettre en place une seconde instance standby sur p3.
Configurer la réplication synchrone de p1 vers p2 et p3 en utilisant la syntaxe p2, p3 pour synchronous_standby_names. Observer les champs sync_priority et sync_state de pg_stat_replication.
Stopper p2 puis p3, redémarrer p3 puis p2. À chaque étape regarder l’état de la vue pg_stat_replication.
Modifier la configuration de p1 pour demander deux standbys synchrones.
Configurer synchronous_standby_names à *. Répéter les mêmes tests que précédemment. Qu’observez-vous ?
Redémarrer p2.
Mettre en place la réplication synchrone par quorum avec un serveur synchrone minimum.
Désactiver la réplication synchrone.
Optionnel: Log shipping et initialisation d’instance avec pgBackRest
Note :
But : Montrer l’utilisation de pgBackRest dans une architecture de réplication
Nous allons utiliser la vm b1 comme serveur de sauvegarde et y créer un dépôt pgBackRest dédié.
Installer pgBackRest sur tous les serveurs (p1, p2, p3, b1).
Configurer pgBackRest sur le serveur de sauvegarde.
Configurer pgBackRest sur les serveurs de base de données.
Créer la stanza, tester l’archivage.
Lancer une sauvegarde.
Si p3 existe, le stopper et détruire le répertoire de données.
Créer l’instance à partir de la sauvegarde.
Interdire la connexion à p1 depuis p3, et générer de l’activité. Que se passe-t-il sur p3 ?
Faire marche arrière sur la modification, que se passe-t-il sur p3 ?
Optionnel: pg_rewind
Note :
But : Remonter une instance secondaire après une divergence.
L’instance p1 est l’instance primaire.
Promouvoir l’instance p2. Générer de l’activité sur p1.
Stopper p1 et configurer la réplication pour raccrocher p1 à p2. Qu’observez-vous ?
Utiliser pg_rewind pour remettre l’instance p1 d’aplomb.
Travaux pratiques (solutions)
Note :
Conseil :
La version de PostgreSQL est la version 17. Adapter au besoin pour une version ultérieure.
Sur Rocky Linux 8 ou 9
Note :
Conseil :
Cette solution se base sur un système Rocky Linux 8, installé à minima depuis les paquets du PGDG, et en anglais.
En préalable, nettoyer les instances précédemment créées sur le serveur.
Note :
Réplication asynchrone en flux avec un seul secondaire
Créer l’instance principale dans /var/lib/pgsql/17/data sur le serveur p1. Les sommes de contrôles devront être activées.
Exemple 3-e054Extrait de code ou sortie de commande du chapitre 3.
Exemple 3-e055Extrait de code ou sortie de commande du chapitre 3.
Initializing database ... OK
Exemple 3-e056Extrait de code ou sortie de commande du chapitre 3.
sudo systemctl start postgresql-17.service
Mettre en place la configuration de la réplication par streaming.
L’utilisateur dédié sera nommé repli.
Au sein du fichier /var/lib/pgsql/17/data/pg_hba.conf, ajouter les entrées ci-dessous pour que l’utilisateur repli (avec l’attribut REPLICATION) ait accès en réplication à l’instance depuis p2 et p3 :
Exemple 3-e057Extrait de code ou sortie de commande du chapitre 3.
Les entrées configurées par défaut qui permettent de faire de la réplication en local peuvent être supprimées :
Exemple 3-e058Extrait de code ou sortie de commande du chapitre 3.
# Allow replication connections from localhost, by a user with the# replication privilege.local replication all peerhost replication all 127.0.0.1/32 scram-sha-256host replication all ::1/128 scram-sha-256
Créer le rôle repli, qui sera dédié à la réplication, en lui affectant le mot de passe confidentiel :
Exemple 3-e059Extrait de code ou sortie de commande du chapitre 3.
Il faut adapter la configuration pour que PostgreSQL écoute sur l’interface réseau qui sera utilisé pour la réplication (listen_addresses = '*'). Afin d’éviter que la mise en réplication n’échoue car les WAL ont été recyclés par PostgreSQL, il faut dire à PostgreSQL de conserver des WAL (wal_keep_size = '256MB'). Cette modification est facultative mais elle permet d’avoir plus de confort dans les manipulations. Un effet similaire peut être obtenu en utilisant des slots de réplication. La mise à jour de la configuration doit être réalisée dans /var/lib/pgsql/17/data/postgresql.conf.
Exemple 3-e062Extrait de code ou sortie de commande du chapitre 3.
Exemple 3-e071Extrait de code ou sortie de commande du chapitre 3.
23172/23172 kB (100%), 1/1 tablespace
L’option -R ou --write-recovery-conf de pg_basebackup a préparé la configuration de la mise en réplication en créant le fichier standby.signal ainsi qu’en configurant primary_conninfo dans le fichier postgresql.auto.conf (dans les versions antérieures à la 11, il renseignerait recovery.conf) :
Exemple 3-e072Extrait de code ou sortie de commande du chapitre 3.
file /var/lib/pgsql/17/data/standby.signal
Exemple 3-e073Extrait de code ou sortie de commande du chapitre 3.
/var/lib/pgsql/17/data/standby.signal: empty
Exemple 3-e074Extrait de code ou sortie de commande du chapitre 3.
cat /var/lib/pgsql/17/data/postgresql.auto.conf
Exemple 3-e075Extrait de code ou sortie de commande du chapitre 3.
# paramètre remis en forme pour le manuel
primary_conninfo = 'user=repli passfile=''/var/lib/pgsql/.pgpass''
channel_binding=prefer host=10.0.0.21 port=5432
sslmode=prefer sslcompression=0 sslcertmode=allow sslsni=1
ssl_min_protocol_version=TLSv1.2
gssencmode=prefer krbsrvname=postgres gssdelegation=0
target_session_attrs=any load_balance_hosts=disable'
Supprimer les traces :
Exemple 3-e076Extrait de code ou sortie de commande du chapitre 3.
rm /var/lib/pgsql/17/data/log/*
Les fichiers pg_hba.conf et du postgresql.conf ont été répliqués avec le reste de l’instance présente sur p1. Il faut cependant adapter la configuration du pg_hba.conf pour qu’elle autorise les connexions de réplication depuis p1 et p3 :
Exemple 3-e077Extrait de code ou sortie de commande du chapitre 3.
Exemple 3-e109Extrait de code ou sortie de commande du chapitre 3.
waiting for server to promote.... done
server promoted
Tenter de se connecter au serveur secondaire fraîchement promu.
Les écritures y sont-elles possibles ?
Connectons-nous à ce nouveau primaire et tentons d’y insérer des données :
Exemple 3-e110Extrait de code ou sortie de commande du chapitre 3.
psql b1
Exemple 3-e111Extrait de code ou sortie de commande du chapitre 3.
psql (17.1)
Type "help" for help.
Exemple 3-e112Extrait de code ou sortie de commande du chapitre 3.
b1=#CREATETABLEt2(idinteger);
Exemple 3-e113Extrait de code ou sortie de commande du chapitre 3.
CREATE TABLE
Exemple 3-e114Extrait de code ou sortie de commande du chapitre 3.
b1=#INSERTINTOt2SELECTgenerate_series(1,1000000);
Exemple 3-e115Extrait de code ou sortie de commande du chapitre 3.
INSERT 0 1000000
Les écritures sont désormais possibles sur cette instance.
Retour à la normale
Reconstruire l’instance initiale sur p1 comme nouvelle instance secondaire en repartant d’une copie complète de p2 en utilisant pg_basebackup.
Afin de rétablir la situation, nous pouvons réintégrer l’ancienne instance primaire en tant que nouveau secondaire (sur p1). Pour ce faire, nous devons re-synchroniser les données. Utilisons pg_basebackup comme précédemment après avoir mis de côté les fichiers de l’ancien primaire :
Exemple 3-e116Extrait de code ou sortie de commande du chapitre 3.
Vérifier que les processus adéquats sont bien présents, et que les données précédemment insérées dans les tables créées plus haut sont bien présentes dans l’instance reconstruite.
Les processus adéquats sont bien présents :
Exemple 3-e126Extrait de code ou sortie de commande du chapitre 3.
ps -o pid,cmd fx | egrep "(startup|walsender|walreceiver)"
Sur p1 :
Exemple 3-e127Extrait de code ou sortie de commande du chapitre 3.
En nous connectant à la nouvelle instance secondaire (p1), vérifions que les données précédemment insérées dans la table t2 sont bien présentes :
Exemple 3-e129Extrait de code ou sortie de commande du chapitre 3.
psql b1
Exemple 3-e130Extrait de code ou sortie de commande du chapitre 3.
psql (17.1)
Type "help" for help.
Exemple 3-e131Extrait de code ou sortie de commande du chapitre 3.
b1=#SELECTCOUNT(*)FROMt2;
Exemple 3-e132Extrait de code ou sortie de commande du chapitre 3.
count
---------
1000000
Inverser à nouveau les rôles des deux instances afin que p2 redevienne l’instance secondaire, cette fois-ci effectuer la remise en service de l’ancienne instance primaire sans reconstruction.
Afin que l’instance p1 redevienne primaire et celle sur p2 secondaire, on peut ré-appliquer la procédure de promotion vue précédemment dans l’autre sens.
Arrêt de l’instance primaire (p2) et vérification de son état :
Exemple 3-e133Extrait de code ou sortie de commande du chapitre 3.
sudo systemctl stop postgresql-17.service
Exemple 3-e134Extrait de code ou sortie de commande du chapitre 3.
Exemple 3-e139Extrait de code ou sortie de commande du chapitre 3.
waiting for server to promote.... done
server promoted
La configuration du fichier pg_hba.conf doit être adaptée pour autoriser les connexions depuis 10.0.0.22. Comme nous l’avons restauré précédemment, elle devrait convenir :
Exemple 3-e140Extrait de code ou sortie de commande du chapitre 3.
Afin que p2 redevienne l’instance secondaire, créer le fichier standby.signal, démarrer le service et vérifier que les processus adéquats sont bien présents :
Exemple 3-e142Extrait de code ou sortie de commande du chapitre 3.
touch /var/lib/pgsql/17/data/standby.signal
Exemple 3-e143Extrait de code ou sortie de commande du chapitre 3.
sudo systemctl start postgresql-17.service
Exemple 3-e144Extrait de code ou sortie de commande du chapitre 3.
ps -o pid,cmd fx | egrep "(startup|walsender|walreceiver)"
Sur p1 :
Exemple 3-e145Extrait de code ou sortie de commande du chapitre 3.
Stopper l’instance secondaire p2, créer une table t1, que se passe-t-il ?
Arrêter l’instance sur p2 :
Exemple 3-e154Extrait de code ou sortie de commande du chapitre 3.
sudo systemctl stop postgresql-17
Créer une table t1 sur p1 :
Exemple 3-e155Extrait de code ou sortie de commande du chapitre 3.
CREATETABLEt1();
La transaction implicite qui englobe la création de t1 est en attente. La vue pg_stat_activity nous apprend que l’instance est attente de la réplication synchrone.
Exemple 3-e156Extrait de code ou sortie de commande du chapitre 3.
Exemple 3-e157Extrait de code ou sortie de commande du chapitre 3.
-[ RECORD 1 ]---+-------------------
pid | 1882
usename | postgres
datname | postgres
wait_event_type | IPC
wait_event | SyncRep
state | active
query | CREATE TABLE t1();
À ce moment, la table n’est pas visible sur p1.
Exemple 3-e158Extrait de code ou sortie de commande du chapitre 3.
\dtt1
Exemple 3-e159Extrait de code ou sortie de commande du chapitre 3.
Did not find any relation named "t1".
Lorsque l’on redémarre le service PostgreSQL sur p2, la session en attente se termine et la table est visible.
Exemple 3-e160Extrait de code ou sortie de commande du chapitre 3.
sudo systemctl start postgresql-17
Stopper l’instance p2, créer une table t2. Annuler la requête bloquée sur p1 avec la commande pg_cancel_backend(), qu’observez-vous ?
Redémarrer p2.
Si l’on reproduit les mêmes étapes en créant une table t2 et que l’on annule la session avec pg_cancel_backend(), la réplication synchrone est débloquée et la table est visible :
Exemple 3-e161Extrait de code ou sortie de commande du chapitre 3.
Exemple 3-e162Extrait de code ou sortie de commande du chapitre 3.
-[ RECORD 1 ]---+-------------------
pid | 1882
usename | postgres
datname | postgres
wait_event_type | IPC
wait_event | SyncRep
state | active
query | CREATE TABLE t2();
cancelled | t
List of relations
Schema | Name | Type | Owner
--------+------+-------+----------
public | t2 | table | postgres
(1 row)
Sur p1, on observe :
Exemple 3-e163Extrait de code ou sortie de commande du chapitre 3.
WARNING: canceling wait for synchronous replication due to user request
DETAIL: The transaction has already committed locally, but might not have been replicated to the standby.
CREATE TABLE
À ce stade, si un incident provoquant la perte du serveur et de son stockage survient sur p1 et que les WAL ne sont pas archivées, p2 n’aura jamais reçus les modifications et elles seront perdues. Si l’on redémarre p2, elle rattrape son retard et la table t2 y est visible.
Suivre la méthode utilisée dans les TP précédant pour mettre en place une seconde instance standby sur p3.
Petit rappel des opérations :
sauvegarder la configuration (ici c’est inutile) et traces ;
nettoyer le répertoire de données existant et ses traces ;
effectuer le pg_basebackup ;
vérifier la configuration ;
fichier flag (standby.signal) ;
accès extérieur (pg_hba.conf) ;
configuration générale (postgresql.conf) ;
configuration de la connexion (postgresql.auto.conf) ;
mot de passe (.pgpass) ;
démarrer l’instance.
Configurer la réplication synchrone de p1 vers p2 et p3 en utilisant la syntaxe p2, p3. Observer les champs sync_priority et sync_state de pg_stat_replication.
La configuration a été mise à jour et prise en compte :
Exemple 3-e164Extrait de code ou sortie de commande du chapitre 3.
On voit que l’instance p3 est marquée comme potential.
Stopper p2 puis p3, redémarrer p3 puis p2. À chaque étape regarder l’état de la vue pg_stat_replication.
Voici le contenu de pg_stat_replication en fonction de l’état des instances :
p2 arrêtée : On note que sync priority est toujours à 2 sur p3, en effet cette valeur dépend de la position de l’instance dans le paramètre synchronous_standby_name.
Exemple 3-e168Extrait de code ou sortie de commande du chapitre 3.
D’un point de vue infrastructure, on observe que tant que p2 est disponible, c’est elle qui sera synchrone. Cela correspond bien à la syntaxe équivalente FRIST 1 (p2, p3) qui indique bien que p2 à la plus haute priorité.
D’un point de vue expérience utilisateur, tant qu’il y a au moins une instance synchrone disponible, les transactions ne sont pas bloquées en attente de commit.
Modifier la configuration de p1 pour demander deux standbys synchrones.
La configuration suivante a été mise à jour et prise en compte :
Exemple 3-e171Extrait de code ou sortie de commande du chapitre 3.
On observe que les deux standbys sont synchrones et que la priorité n’a pas changé. Si l’un des serveurs s’arrête toute transaction validée sur p1 restera en attente car la configuration exige la présence de deux instances.
Configurer synchronous_standby_names à *. Répéter les mêmes tests que précédemment. Qu’observez-vous ?
On observe que le réplica synchrone est p3 et que la priorité des deux instances est 1.
Exemple 3-e175Extrait de code ou sortie de commande du chapitre 3.
Il faut ensuite échanger les clés SSH entre les serveurs ou mettre en place le serveur TLS de pgBackRest. Pour ce TP, nous utiliserons ssh. Les clés ont déjà été déjà échangées.
Configurer pgBackRest sur le serveur de sauvegarde.
Le dépôt de sauvegarde sera créé par l’utilisateur postgres.
Exemple 3-e186Extrait de code ou sortie de commande du chapitre 3.
Exemple 3-e202Extrait de code ou sortie de commande du chapitre 3.
[…] INFO: restore command begin 2.54.2: --delta --exec-id=2446-9c0d5025 --log-level-console=info --log-level-file=detail --log-path=/var/log/pgbackrest --pg1-path=/var/lib/pgsql/17/data --recovery-option="primary_conninfo=user=repli host=10.0.0.21 port=5432 application_name=p3" --repo1-host=b1 --repo1-host-user=postgres --repo1-path=/var/lib/pgbackrest --stanza=main --type=standby
[…] WARN: --delta or --force specified but unable to find 'PG_VERSION' or 'backup.manifest' in '/var/lib/pgsql/17/data' to confirm that this is a valid $PGDATA directory. --delta and --force have been disabled and if any files exist in the destination directories the restore will be aborted.
[…] INFO: repo1: restore backup set 20250311-134434F, recovery will start at 2025-03-11 13:44:34
[…] INFO: write updated /var/lib/pgsql/17/data/postgresql.auto.conf
[…] INFO: restore global/pg_control (performed last to ensure aborted restores cannot be started)
[…] INFO: restore size = 64MB, file total = 1271
[…] INFO: restore command end: completed successfully (42960ms)
Dans le cas présent, on note que pgBackRest a détecté qu’il n’y avait pas de données dans le répertoire de données de l’instance. Il a donc désactivé l’option --delta de lui-même. Si un répertoire de données existe seul les fichiers qui ont changé sont restaurés.
Le fichier standby.signal a bien été créé :
Exemple 3-e203Extrait de code ou sortie de commande du chapitre 3.
file ~/17/data/standby.signal
Exemple 3-e204Extrait de code ou sortie de commande du chapitre 3.
/var/lib/pgsql/17/data/standby.signal: empty
La configuration a bien été mise à jour :
Exemple 3-e205Extrait de code ou sortie de commande du chapitre 3.
cat ~/17/data/postgresql.auto.conf
Exemple 3-e206Extrait de code ou sortie de commande du chapitre 3.
# Do not edit this file manually!
# It will be overwritten by the ALTER SYSTEM command.
# Recovery settings generated by pgBackRest restore on 2025-03-11 14:07:49
primary_conninfo = 'user=repli host=10.0.0.21 port=5432 application_name=p3'
restore_command = 'pgbackrest --stanza=main archive-get %f "%p"'
Adapter la configuration pour autoriser les connexions depuis p1 et p2 en cas de promotion :
Exemple 3-e207Extrait de code ou sortie de commande du chapitre 3.
Exemple 3-e234Extrait de code ou sortie de commande du chapitre 3.
sudo systemctl start postgresql-17
Qu’observez-vous ?
L’instance refuse de démarrer.
Les traces indiquent que les deux instances ont divergé au lsn 0/12FFF6D8 qui se trouve après le dernier checkpoint de la primaire : 0/19000028 sur la timeline 1.
Exemple 3-e235Extrait de code ou sortie de commande du chapitre 3.
LOG: database system is shut down
LOG: starting PostgreSQL 17.4 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 11.5.0 20240719 (Red Hat 11.5.0-5), 64-bit
LOG: listening on IPv4 address "0.0.0.0", port 5432
LOG: listening on IPv6 address "::", port 5432
LOG: listening on Unix socket "/run/postgresql/.s.PGSQL.5432"
LOG: listening on Unix socket "/tmp/.s.PGSQL.5432"
LOG: database system was shut down at 2025-03-11 15:45:42 UTC
LOG: restored log file "00000002.history" from archive
LOG: entering standby mode
FATAL: requested timeline 2 is not a child of this server's history
DETAIL: Latest checkpoint is at 0/19000028 on timeline 1, but in the history of the requested timeline, the server forked off from that timeline at 0/12FFF6D8.
LOG: startup process (PID 8973) exited with exit code 1
LOG: aborting startup due to startup process failure
LOG: database system is shut down
À ce stade, il faut reconstruire la primaire complètement ou partiellement.
Utiliser pg_rewind pour remettre l’instance p1 d’aplomb.
Les instances que nous avons créées utilisent les sommes de contrôle (checksums), ce qui inclut des hint nécessaires à l’utilisation de pg_rewind :
Exemple 3-e236Extrait de code ou sortie de commande du chapitre 3.
Les traces nous montrent les deux serveurs ont divergé au lsn 0/21BE14E8.
Exemple 3-e245Extrait de code ou sortie de commande du chapitre 3.
pg_rewind: connected to server
pg_rewind: servers diverged at WAL location 0/12FFF6D8 on timeline 1
pg_rewind lit les WAL présent sur p1 et liste tous les blocs modifiés entre le checkpoint précédant le point de divergence et le moment ou l’instance a été arrêtée. Cette liste permettra de ne récupérer que ce qui n’a pas bougé sur la nouvelle instance. Certains fichiers, qui ne sont pas protégés par les WAL devront être copiés entièrement. C’est notamment le cas de la configuration présente dans le répertoire de données de l’instance.
Exemple 3-e246Extrait de code ou sortie de commande du chapitre 3.
pg_rewind: rewinding from last common checkpoint at 0/10F3E788 on timeline 1
pg_rewind: reading source file list
pg_rewind: reading target file list
pg_rewind: reading WAL in target
Une fois la liste de ce qui doit être fait établie : la copie est lancée.
Exemple 3-e247Extrait de code ou sortie de commande du chapitre 3.
pg_rewind: need to copy 350 MB (total source directory size is 518 MB)
359043/359043 kB (100%) copied
Le control file est ensuite modifié et un fichier backup label généré.
Exemple 3-e248Extrait de code ou sortie de commande du chapitre 3.
pg_rewind: creating backup label and updating control file
pg_rewind: syncing target data directory
pg_rewind: Done!
Le test a fonctionné nous pouvons le relancer sans l’option --dry-run.
L’option --write-recovery-conf nous permet de :
créer le fichier standby.signal :
Exemple 3-e249Extrait de code ou sortie de commande du chapitre 3.
file /var/lib/pgsql/17/data/standby.signal
Exemple 3-e250Extrait de code ou sortie de commande du chapitre 3.
/var/lib/pgsql/17/data/standby.signal: empty
mettre à jour la configuration de la réplication :
Exemple 3-e251Extrait de code ou sortie de commande du chapitre 3.
cat /var/lib/pgsql/17/data/postgresql.auto.conf
Exemple 3-e252Extrait de code ou sortie de commande du chapitre 3.
# Do not edit this file manually!
# It will be overwritten by the ALTER SYSTEM command.
primary_conninfo = 'user=repli passfile=''/var/lib/pgsql/.pgpass'' channel_binding=prefer host=10.0.0.21 port=5432 sslmode=prefer sslnegotiation=postgres sslcompression=0 sslcertmode=allow sslsni=1 ssl_min_protocol_version=TLSv1.2 gssencmode=prefer krbsrvname=postgres gssdelegation=0 target_session_attrs=any load_balance_hosts=disable'
primary_conninfo = 'user=rewind_user passfile=''/var/lib/pgsql/.pgpass'' channel_binding=prefer host=10.0.0.22 port=5432 sslmode=prefer sslnegotiation=postgres sslcompression=0 sslcertmode=allow sslsni=1 ssl_min_protocol_version=TLSv1.2 gssencmode=prefer krbsrvname=postgres gssdelegation=0 target_session_attrs=any load_balance_hosts=disable'
Cette configuration est malheureusement incorrecte pour notre besoin. La première correspond au contenu du fichier p2, la seconde utilise l’utilisateur rewind_user. Nous allons donc la remplacer par ce qui nous intéresse :
Exemple 3-e253Extrait de code ou sortie de commande du chapitre 3.
La seconde instance secondaire a elle aussi décroché, à vous de jouer !
Sur Debian 12
Note :
Conseil :
Cette solution se base sur un système Debian 12 (ou supérieur), installé à minima depuis les paquets du PGDG, et en anglais.
Au préalable, nettoyer les instances précédemment créées sur le serveur.
Exemple 3-e257Extrait de code ou sortie de commande du chapitre 3.
# pg_dropcluster --stop 17 main
Note :
Conseil :
Dans ce TP, les ordres sont à entrer soit comme utilisateur root ou avec sudo, soit comme utilisateur postgres (qui n’a pas droit au sudo).
Réplication asynchrone en flux avec un seul secondaire
Ce TP suppose que les binaires de PostgreSQL sont présents mais qu’aucune instance n’est en place. - Vérifier avec pg_lsclusters que les binaires de PostgreSQL sont installés et qu’il n’existe pas d’autre instance. - Créer l’instance principale en utilisant pg_createcluster sur le serveur p1. Les sommes de contrôles devront être activées.
pg_lsclusters ne doit rien envoyer. Si la commande n’existe pas, installer les binaires par PostgreSQL depuis le PGDG, si possible sans créer d’instance (voir référence). Si elle existe, la supprimer avec pg_dropcluster.
Création de l’instance :
Exemple 3-e258Extrait de code ou sortie de commande du chapitre 3.
sudo pg_createcluster 17 main -- -k
Exemple 3-e259Extrait de code ou sortie de commande du chapitre 3.
Creating new PostgreSQL cluster 17/main ...
Ver Cluster Port Status Owner Data directory Log file
17 main 5432 down postgres /var/lib/postgresql/17/main /var/log/postgresql/postgresql-17-main.log
Le répertoire des données se trouvera sous /var/lib/postgresql/17/main.
Démarrer l’instance, soit avec :
Exemple 3-e260Extrait de code ou sortie de commande du chapitre 3.
sudo pg_ctlcluster start 17 main
soit explicitement via systemd :
Exemple 3-e261Extrait de code ou sortie de commande du chapitre 3.
sudo systemctl start postgresql@17-main
Vérifier la configuration de la réplication par streaming.
L’utilisateur dédié sera nommé repli.
Au sein du fichier /etc/postgresql/17/main/pg_hba.conf, ajouter les entrées ci-dessous pour que l’utilisateur repli (avec l’attribut REPLICATION) ait accès en réplication à l’instance depuis p2 et p3 :
Exemple 3-e262Extrait de code ou sortie de commande du chapitre 3.
Les entrées configurées par défaut qui permettent de faire de la réplication en local peuvent être supprimées :
Exemple 3-e263Extrait de code ou sortie de commande du chapitre 3.
# Allow replication connections from localhost, by a user with the# replication privilege.local replication all peerhost replication all 127.0.0.1/32 scram-sha-256host replication all ::1/128 scram-sha-256
Créer le rôle repli, qui sera dédié à la réplication, en lui affectant le mot de passe confidentiel :
Exemple 3-e264Extrait de code ou sortie de commande du chapitre 3.
Il faut adapter la configuration pour que PostgreSQL écoute sur l’interface réseau qui sera utilisé pour la réplication (listen_addresses = '*'). Afin d’éviter que la mise en réplication n’échoue car les WAL ont été recyclés par PostgreSQL, il faut dire à PostgreSQL de conserver des WAL (wal_keep_size = '256MB'). Cette modification est facultative mais elle permet d’avoir plus de confort dans les manipulations. Un effet similaire peut être obtenu en utilisant des slots de réplication. La mise à jour de la configuration doit être réalisée dans /etc/postgresql/17/main.
Exemple 3-e267Extrait de code ou sortie de commande du chapitre 3.
Exemple 3-e271Extrait de code ou sortie de commande du chapitre 3.
List of configuration parameters
Parameter | Value | Type | Context | Access privileges
------------------+-------+---------+------------+-------------------
listen_addresses | * | string | postmaster |
wal_keep_size | 256MB | integer | sighup |
(2 rows)
Créer la première instance secondaire sur le serveur p2, par copie à chaud du répertoire de données avec pg_basebackup.
Penser à copier les fichiers de configuration
Nous allons réutiliser le squelette de l’instance main pour créer l’instance secondaire.
Exemple 3-e272Extrait de code ou sortie de commande du chapitre 3.
sudo pg_createcluster 17 main -- -k
pg_lsclusters 17 main
Exemple 3-e273Extrait de code ou sortie de commande du chapitre 3.
Ver Cluster Port Status Owner Data directory Log file
17 main 5432 down postgres /var/lib/postgresql/17/main /var/log/postgresql/postgresql-17-main.log
Supprimer le répertoire de données :
Exemple 3-e274Extrait de code ou sortie de commande du chapitre 3.
rm -Rf /var/lib/postgresql/17/main
Créer le fichier ~/.pgpass :
Exemple 3-e275Extrait de code ou sortie de commande du chapitre 3.
Exemple 3-e277Extrait de code ou sortie de commande du chapitre 3.
23110/23110 kB (100%), 1/1 tablespace
L’option -R ou --write-recovery-conf de pg_basebackup a préparé la configuration de la mise en réplication en créant le fichier standby.signal ainsi qu’en configurant primary_conninfo dans le fichier postgresql.auto.conf (dans les versions antérieures à la 11, il renseignerait recovery.conf) :
Exemple 3-e278Extrait de code ou sortie de commande du chapitre 3.
file /var/lib/postgresql/17/main/standby.signal
Exemple 3-e279Extrait de code ou sortie de commande du chapitre 3.
/var/lib/postgresql/17/main/standby.signal: empty
Exemple 3-e280Extrait de code ou sortie de commande du chapitre 3.
Exemple 3-e281Extrait de code ou sortie de commande du chapitre 3.
# paramètre remis en forme pour le manuel
primary_conninfo = 'user=repli passfile=''/var/lib/postgresql/.pgpass''
channel_binding=prefer host=10.0.0.21 port=5432
sslmode=prefer sslcompression=0 sslcertmode=allow sslsni=1
ssl_min_protocol_version=TLSv1.2
gssencmode=prefer krbsrvname=postgres gssdelegation=0
target_session_attrs=any load_balance_hosts=disable'
Il faut répercuter les modifications du pg_hba.conf et du postgresql.conf en les adaptant si nécessaire sur la nouvelle instance :
Exemple 3-e282Extrait de code ou sortie de commande du chapitre 3.
L’instance principale est bien arrêtée, l’instance secondaire est bien en archive recovery et les deux sont bien synchronisées.
Promotion de l’instance secondaire sur p2 :
Exemple 3-e313Extrait de code ou sortie de commande du chapitre 3.
psql -c 'SELECT pg_promote()'
Exemple 3-e314Extrait de code ou sortie de commande du chapitre 3.
pg_promote
------------
t
(1 row)
Tenter de se connecter au serveur secondaire fraîchement promu.
Les écritures y sont-elles possibles ?
Connectons-nous à ce nouveau primaire et tentons d’y insérer des données :
Exemple 3-e315Extrait de code ou sortie de commande du chapitre 3.
psql b1
Exemple 3-e316Extrait de code ou sortie de commande du chapitre 3.
psql (17.1)
Type "help" for help.
Exemple 3-e317Extrait de code ou sortie de commande du chapitre 3.
b1=#CREATETABLEt2(idinteger);
Exemple 3-e318Extrait de code ou sortie de commande du chapitre 3.
CREATE TABLE
Exemple 3-e319Extrait de code ou sortie de commande du chapitre 3.
b1=#INSERTINTOt2SELECTgenerate_series(1,1000000);
Exemple 3-e320Extrait de code ou sortie de commande du chapitre 3.
INSERT 0 1000000
Les écritures sont désormais possibles sur cette instance.
Retour à la normale
Reconstruire l’instance initiale sur p1 comme nouvelle instance secondaire en repartant d’une copie complète de p2 en utilisant pg_basebackup.
Afin de rétablir la situation, nous pouvons réintégrer l’ancienne instance primaire en tant que nouveau secondaire (sur p1). Pour ce faire, nous devons re-synchroniser les données. Utilisons pg_basebackup comme précédemment après avoir mis de côté les fichiers de l’ancien primaire :
Exemple 3-e321Extrait de code ou sortie de commande du chapitre 3.
Exemple 3-e327Extrait de code ou sortie de commande du chapitre 3.
# paramètre remis en forme pour le manuel
primary_conninfo = 'user=repli passfile=''/var/lib/postgresql/.pgpass''
channel_binding=prefer host=10.0.0.22 port=5432
sslmode=prefer sslcompression=0 sslcertmode=allow sslsni=1
ssl_min_protocol_version=TLSv1.2
gssencmode=prefer krbsrvname=postgres gssdelegation=0
target_session_attrs=any load_balance_hosts=disable'
Les fichiers de configuration de l’instance n’ayant quant à eux pas été modifiés, il n’est pas nécessaire de remodifier la configuration.
Supprimer les traces :
Exemple 3-e328Extrait de code ou sortie de commande du chapitre 3.
rm /var/log/postgresql/postgresql-17-main.log
Démarrer cette nouvelle instance.
Démarrer le service :
Exemple 3-e329Extrait de code ou sortie de commande du chapitre 3.
Vérifier que les processus adéquats sont bien présents, et que les données précédemment insérées dans les tables créées plus haut sont bien présentes dans l’instance reconstruite.
Les processus adéquats sont bien présents :
Exemple 3-e330Extrait de code ou sortie de commande du chapitre 3.
ps -o pid,cmd fx | egrep "(startup|walsender|walreceiver)"
Sur p1 (secondaire) :
Exemple 3-e331Extrait de code ou sortie de commande du chapitre 3.
En nous connectant à la nouvelle instance secondaire (p1), vérifions que les données précédemment insérées dans la table t2 sont bien présentes :
Exemple 3-e333Extrait de code ou sortie de commande du chapitre 3.
psql b1
Exemple 3-e334Extrait de code ou sortie de commande du chapitre 3.
psql (17.1)
Type "help" for help.
Exemple 3-e335Extrait de code ou sortie de commande du chapitre 3.
b1=#SELECTCOUNT(*)FROMt2;
Exemple 3-e336Extrait de code ou sortie de commande du chapitre 3.
count
---------
1000000
Inverser à nouveau les rôles des deux instances afin que p2 redevienne l’instance secondaire, cette fois-ci effectuer la remise en service de l’ancienne instance primaire sans reconstruction.
Afin que l’instance p1 redevienne primaire et celle sur p2 secondaire, on peut ré-appliquer la procédure de promotion vue précédemment dans l’autre sens.
Arrêt de l’instance primaire (p2) et vérification de son état :
Exemple 3-e337Extrait de code ou sortie de commande du chapitre 3.
sudo systemctl stop postgresql@17-main
Exemple 3-e338Extrait de code ou sortie de commande du chapitre 3.
L’instance principale est bien arrêtée, l’instance secondaire est bien en archive recovery et les deux sont bien synchronisées.
Promotion de l’instance secondaire :
Exemple 3-e342Extrait de code ou sortie de commande du chapitre 3.
psql -c 'SELECT pg_promote()'
Exemple 3-e343Extrait de code ou sortie de commande du chapitre 3.
pg_promote
------------
t
(1 row)
Afin que p2 redevienne l’instance secondaire, créer le fichier standby.signal, démarrer le service et vérifier que les processus adéquats sont bien présents :
Exemple 3-e344Extrait de code ou sortie de commande du chapitre 3.
touch /var/lib/postgresql/17/main/standby.signal
Exemple 3-e345Extrait de code ou sortie de commande du chapitre 3.
sudo systemctl start postgresql@17-main
Exemple 3-e346Extrait de code ou sortie de commande du chapitre 3.
ps -o pid,cmd fx | egrep "(startup|walsender|walreceiver)"
Sur p1 :
Exemple 3-e347Extrait de code ou sortie de commande du chapitre 3.
Stopper l’instance secondaire p2, créer une table t1, que se passe-t-il ?
Arrêter l’instance sur p2 :
Exemple 3-e356Extrait de code ou sortie de commande du chapitre 3.
sudo systemctl stop postgresql@17-main
Créer une table t1 sur p1 :
Exemple 3-e357Extrait de code ou sortie de commande du chapitre 3.
CREATETABLEt1();
La transaction implicite qui englobe la création de t1 est en attente. La vue pg_stat_activity nous apprend que l’instance est attente de la réplication synchrone.
Exemple 3-e358Extrait de code ou sortie de commande du chapitre 3.
Exemple 3-e359Extrait de code ou sortie de commande du chapitre 3.
-[ RECORD 1 ]---+-------------------
pid | 35409
usename | postgres
datname | postgres
wait_event_type | IPC
wait_event | SyncRep
state | active
query | CREATE TABLE t1();
À ce moment, la table n’est pas visible sur p1.
Exemple 3-e360Extrait de code ou sortie de commande du chapitre 3.
\dtt1
Exemple 3-e361Extrait de code ou sortie de commande du chapitre 3.
Did not find any relation named "t1".
Lorsque l’on redémarre le service PostgreSQL sur p2, la session en attente se termine et la table est visible.
Exemple 3-e362Extrait de code ou sortie de commande du chapitre 3.
sudo systemctl start postgresql@17-main
Stopper l’instance p2, créer une table t2. Annuler la requête bloquée sur p1 avec la commande pg_cancel_backend(), qu’observez-vous ?
Redémarrer p2.
Si l’on reproduit les mêmes étapes en créant une table t2 et que l’on annule la session avec pg_cancel_backend(), la réplication synchrone est débloquée et la table est visible :
Exemple 3-e363Extrait de code ou sortie de commande du chapitre 3.
Exemple 3-e364Extrait de code ou sortie de commande du chapitre 3.
-[ RECORD 1 ]---+-------------------
pid | 35409
usename | postgres
datname | postgres
wait_event_type | IPC
wait_event | SyncRep
state | active
query | CREATE TABLE t2();
cancelled | t
List of relations
Schema | Name | Type | Owner
--------+------+-------+----------
public | t2 | table | postgres
(1 row)
Sur p1, on observe :
Exemple 3-e365Extrait de code ou sortie de commande du chapitre 3.
WARNING: canceling wait for synchronous replication due to user request
DETAIL: The transaction has already committed locally, but might not have been replicated to the standby.
CREATE TABLE
À ce stade, si un incident provoquant la perte du serveur et de son stockage survient sur p1 et que les WAL ne sont pas archivées, p2 n’aura jamais reçus les modifications et elles seront perdues. Si l’on redémarre p2, elle rattrape son retard et la table t2 y est visible.
Suivre la méthode utilisée dans les TP précédant pour mettre en place une seconde instance standby sur p3.
Petit rappel des opérations :
sauvegarder la configuration (ici c’est inutile) et traces ;
nettoyer le répertoire de données existant et ses traces ;
effectuer le pg_basebackup ;
vérifier la configuration ;
fichier flag (standby.signal) ;
accès extérieur (pg_hba.conf) ;
configuration générale (postgresql.conf) ;
configuration de la connexion (postgresql.auto.conf) ;
mot de passe (.pgpass) ;
démarrer l’instance.
Configurer la réplication synchrone de p1 vers p2 et p3 en utilisant la syntaxe p2, p3. Observer les champs sync_priority et sync_state de pg_stat_replication.
La configuration a été mise à jour et prise en compte :
Exemple 3-e366Extrait de code ou sortie de commande du chapitre 3.
Exemple 3-e367Extrait de code ou sortie de commande du chapitre 3.
synchronous_standby_names='p2, p3'
Note: deux syntaxes équivalentes existent : FIRST 1 (p2, p3) ou FIRST (p2, p3). Elles ne seront pas utilisées dans ce TP, parce que Patroni ne les utilise pas.
La vue pg_stat_replication contient désormais les informations suivantes :
Exemple 3-e368Extrait de code ou sortie de commande du chapitre 3.
On voit que l’instance p3 est marquée comme potential.
Stopper p2 puis p3, redémarrer p3 puis p2. À chaque étape regarder l’état de la vue pg_stat_replication.
Voici le contenu de pg_stat_replication en fonction de l’état des instances :
p2 arrêtée : On note que sync priority est toujours à 2 sur p3, en effet cette valeur dépend de la position de l’instance dans le paramètre synchronous_standby_name.
Exemple 3-e370Extrait de code ou sortie de commande du chapitre 3.
D’un point de vue infrastructure, on observe que tant que p2 est disponible, c’est elle qui sera synchrone. Cela correspond bien à la syntaxe équivalente FRIST 1 (p2, p3) qui indique bien que p2 à la plus haute priorité.
D’un point de vue expérience utilisateur, tant qu’il y a au moins une instance synchrone disponible, les transactions ne sont pas bloquées en attente de commit.
Modifier la configuration de p1 pour demander deux standbys synchrones.
La configuration suivante a été mise à jour et prise en compte :
Exemple 3-e373Extrait de code ou sortie de commande du chapitre 3.
On observe que les deux standbys sont synchrones et que la priorité n’a pas changé. Si l’un des serveurs s’arrête toute transaction validée sur p1 restera en attente car la configuration exige la présence de deux instances.
Configurer synchronous_standby_names à *. Répéter les mêmes tests que précédemment. Qu’observez-vous ?
On observe que le réplica synchrone est p3 et que la priorité des deux instances est 1.
Exemple 3-e377Extrait de code ou sortie de commande du chapitre 3.
Optionnel: Log shipping et initialisation d’instance avec pgBackRest
Installer pgBackRest sur tous les serveurs (p1, p2, p3, b1).
Il est préférable d’installer les paquets provenant de pgdg. Il faut donc configurer le dépôt associé sur le serveur. Dans notre cas, c’est déjà fait.
L’installation se fait ensuite simplement.
Exemple 3-e387Extrait de code ou sortie de commande du chapitre 3.
sudo apt-get install -y pgbackrest
Il faut ensuite échanger les clés SSH entre les serveurs ou mettre en place le serveur TLS de pgBackRest. Pour ce TP, nous utiliserons ssh. Les clés ont déjà été déjà échangées.
Configurer pgBackRest sur le serveur de sauvegarde.
Le dépôt de sauvegarde sera créé par l’utilisateur postgres.
Exemple 3-e388Extrait de code ou sortie de commande du chapitre 3.
Exemple 3-e404Extrait de code ou sortie de commande du chapitre 3.
[…] INFO: restore command begin 2.54.1: --delta --exec-id=88667-6cd35e15 --log-level-console=info --log-level-file=detail --pg1-path=/var/lib/postgresql/17/main --recovery-option="primary_conninfo=user=repli host=10.0.0.21 port=5432 application_name=p3" --repo1-host=10.0.0.1 --repo1-host-user=dalibo --repo1-path=/var/lib/pgbackrest --stanza=main --type=standby
[…] WARN: --delta or --force specified but unable to find 'PG_VERSION' or 'backup.manifest' in '/var/lib/postgresql/17/main' to confirm that this is a valid $PGDATA directory. --delta and --force have been disabled and if any files exist in the destination directories the restore will be aborted.
[…] INFO: repo1: restore backup set 20250114-174941F, recovery will start at 2025-01-14 16:49:41
[…] INFO: write updated /var/lib/postgresql/17/main/postgresql.auto.conf
[…] INFO: restore global/pg_control (performed last to ensure aborted restores cannot be started)
[…] INFO: restore size = 22MB, file total = 966
[…] INFO: restore command end: completed successfully (20187ms)
Dans le cas présent, on note que pgBackRest a détecté qu’il n’y avait pas de données dans le répertoire de données de l’instance. Il a donc désactivé l’option --delta de lui-même. Si un répertoire de données existe seul les fichiers qui ont changé sont restaurés.
Le fichier standby.signal a bien été créé :
Exemple 3-e405Extrait de code ou sortie de commande du chapitre 3.
file ~/17/main/standby.signal
Exemple 3-e406Extrait de code ou sortie de commande du chapitre 3.
/var/lib/postgresql/17/main/standby.signal: empty
La configuration a bien été mise à jour :
Exemple 3-e407Extrait de code ou sortie de commande du chapitre 3.
cat ~/17/main/postgresql.auto.conf
Exemple 3-e408Extrait de code ou sortie de commande du chapitre 3.
# Do not edit this file manually!
# It will be overwritten by the ALTER SYSTEM command.
primary_conninfo = 'user=repli host=10.0.0.22 port=5432 application_name=p1'
# Recovery settings generated by pgBackRest restore on 2025-01-14 17:05:40
primary_conninfo = 'user=repli host=10.0.0.21 port=5432 application_name=p3'
restore_command = 'pgbackrest --stanza=main archive-get %f "%p"'
Démarrer l’instance :
Exemple 3-e409Extrait de code ou sortie de commande du chapitre 3.
sudo systemctl start postgresql@17-main
L’instance est bien démarrée et la réplication fonctionne :
Exemple 3-e410Extrait de code ou sortie de commande du chapitre 3.
ps -u postgres -o cmd
Exemple 3-e411Extrait de code ou sortie de commande du chapitre 3.
Exemple 3-e434Extrait de code ou sortie de commande du chapitre 3.
sudo systemctl start postgresql@17-main
Qu’observez-vous ?
L’instance refuse de démarrer.
Les traces indiquent que les deux instances ont divergé au lsn 0/21BE14E8 qui se trouve après le dernier checkpoint de la primaire : 0/23000028 sur la timeline 3.
Exemple 3-e435Extrait de code ou sortie de commande du chapitre 3.
LOG: database system was shut down at 2025-01-17 15:12:50 UTC
LOG: restored log file "00000004.history" from archive
FATAL: the database system is starting up
LOG: restored log file "00000004.history" from archive
LOG: entering standby mode
FATAL: requested timeline 4 is not a child of this server's history
DETAIL: Latest checkpoint is at 0/23000028 on timeline 3, but in the history of the requested timeline, the server forked off from that timeline at 0/21BE14E8.
LOG: startup process (PID 8971) exited with exit code 1
LOG: aborting startup due to startup process failure
LOG: database system is shut down
À ce stade, il faut reconstruire le secondaire p1 complètement ou partiellement.
Utiliser pg_rewind pour remettre l’instance p1 d’aplomb.
Les instances que nous avons créées utilisent les sommes de contrôle (checksums), ce qui inclut des hint nécessaires à l’utilisation de pg_rewind :
Exemple 3-e436Extrait de code ou sortie de commande du chapitre 3.
Ces droits sont appliqués uniquement dans la base de données courante. pg_rewind devra donc se connecter à cette même base pour disposer des autorisations nécessaires.
Ajouter le mot de passe du nouvel utilisateur aux fichiers .pgpass de tous les serveurs :
Exemple 3-e439Extrait de code ou sortie de commande du chapitre 3.
Les traces nous montrent les deux serveurs ont divergé au lsn 0/21BE14E8.
Exemple 3-e445Extrait de code ou sortie de commande du chapitre 3.
pg_rewind: connected to server
pg_rewind: servers diverged at WAL location 0/21BE14E8 on timeline 3
pg_rewind lit les WAL présents sur p1 et liste tous les blocs modifiés entre le checkpoint précédent, le point de divergence et le moment où l’instance a été arrêtée. Cette liste permettra de ne récupérer que ce qui n’a pas bougé sur la nouvelle instance. Certains fichiers, qui ne sont pas protégés par les WAL, devront être copiés entièrement. C’est notamment le cas de la configuration présente dans le répertoire de données de l’instance.
Exemple 3-e446Extrait de code ou sortie de commande du chapitre 3.
pg_rewind: rewinding from last common checkpoint at 0/1B0027D8 on timeline 3
pg_rewind: reading source file list
pg_rewind: reading target file list
pg_rewind: reading WAL in target
Une fois la liste de ce qui doit être fait établie : la copie est lancée.
Exemple 3-e447Extrait de code ou sortie de commande du chapitre 3.
pg_rewind: need to copy 507 MB (total source directory size is 526 MB)
519619/519619 kB (100%) copied
Le control file est ensuite modifié et un fichier backup label est généré.
Exemple 3-e448Extrait de code ou sortie de commande du chapitre 3.
pg_rewind: creating backup label and updating control file
pg_rewind: syncing target data directory
pg_rewind: Done!
Le test a fonctionné nous pouvons le relancer sans l’option --dry-run.
L’option --write-recovery-conf nous permet de :
créer le fichier standby.signal :
Exemple 3-e449Extrait de code ou sortie de commande du chapitre 3.
file /var/lib/postgresql/17/main/standby.signal
Exemple 3-e450Extrait de code ou sortie de commande du chapitre 3.
/var/lib/postgresql/17/main/standby.signal: empty
mettre à jour la configuration de la réplication :
Exemple 3-e451Extrait de code ou sortie de commande du chapitre 3.
Exemple 3-e452Extrait de code ou sortie de commande du chapitre 3.
# Do not edit this file manually!
# It will be overwritten by the ALTER SYSTEM command.
primary_conninfo = 'user=repli passfile=''/var/lib/postgresql/.pgpass'' host=10.0.0.21 port=5432 application_name=p2'
primary_conninfo = 'user=rewind_user passfile=''/var/lib/postgresql/.pgpass'' channel_binding=prefer host=10.0.0.22 port=5432 sslmode=prefer sslnegotiation=postgres sslcompression=0 sslcertmode=allow sslsni=1 ssl_min_protocol_version=TLSv1.2 gssencmode=prefer krbsrvname=postgres gssdelegation=0 target_session_attrs=any load_balance_hosts=disable'
Cette configuration est malheureusement incorrecte pour notre besoin. La première correspond au contenu du fichier p2, la seconde utilise l’utilisateur rewind_user. Nous allons donc la remplacer par ce qui nous intéresse :
Exemple 3-e453Extrait de code ou sortie de commande du chapitre 3.