Dalibo SCOP
Formation | Module R56 |
Titre | Réplication Physique : fondamentaux |
Révision | 25.03 |
https://dali.bo/r56_pdf | |
EPUB | https://dali.bo/r56_epub |
HTML | https://dali.bo/r56_html |
Slides | https://dali.bo/r56_slides |
TP | https://dali.bo/r56_tp |
TP (solutions) | https://dali.bo/r56_solutions |
Vous trouverez en ligne les différentes versions complètes de ce document.
Cette formation est sous licence CC-BY-NC-SA. Vous êtes libre de la redistribuer et/ou modifier aux conditions suivantes :
Vous n’avez pas le droit d’utiliser cette création à des fins commerciales.
Si vous modifiez, transformez ou adaptez cette création, vous n’avez le droit de distribuer la création qui en résulte que sous un contrat identique à celui-ci.
Vous devez citer le nom de l’auteur original de la manière indiquée par l’auteur de l’œuvre ou le titulaire des droits qui vous confère cette autorisation (mais pas d’une manière qui suggérerait qu’ils vous soutiennent ou approuvent votre utilisation de l’œuvre). À chaque réutilisation ou distribution de cette création, vous devez faire apparaître clairement au public les conditions contractuelles de sa mise à disposition. La meilleure manière de les indiquer est un lien vers cette page web. Chacune de ces conditions peut être levée si vous obtenez l’autorisation du titulaire des droits sur cette œuvre. Rien dans ce contrat ne diminue ou ne restreint le droit moral de l’auteur ou des auteurs.
Le texte complet de la licence est disponible sur http://creativecommons.org/licenses/by-nc-sa/2.0/fr/legalcode
Cela inclut les diapositives, les manuels eux-mêmes et les travaux pratiques. Cette formation peut également contenir quelques images et schémas dont la redistribution est soumise à des licences différentes qui sont alors précisées.
PostgreSQL® Postgres® et le logo Slonik sont des marques déposées par PostgreSQL Community Association of Canada.
Ce document ne couvre que les versions supportées de PostgreSQL au moment de sa rédaction, soit les versions 13 à 17.
Sur les versions précédentes susceptibles d’être encore rencontrées en production, seuls quelques points très importants sont évoqués, en plus éventuellement de quelques éléments historiques.
Sauf précision contraire, le système d’exploitation utilisé est Linux.
—
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.
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.
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 :
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.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.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.
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 :
host replication user_repli 10.0.0.2/32 scram-sha-256
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 :
CREATE ROLE user_repli LOGIN REPLICATION ;
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).
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.
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.Une copie manuelle implique que les journaux sont archivés par ailleurs.
Rappelons les trois étapes essentielles :
pg_backup_start()
;rsync --whole-file
, ou tout moyen permettant une copie
fiable et rapide ;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.
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.
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.
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 :
primary_conninfo = 'user=postgres host=prod passfile=/var/lib/postgresql/.pgpass'
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
:
primary_conninfo = 'host=prod user=postgres passfile=''/var/lib/postgresql/.pgpass'' channel_binding=prefer port=5436 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
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.
primary_conninfo = 'user=postgres host=prod passfile=/var/lib/postgresql/.pgpass application_name=secondaire2 '
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.
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
.
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 :
postgres: 16/secondaire1: walsender postgres [local] streaming 15/6A6EF408 postgres: 16/secondaire2: walsender postgres [local] streaming 15/6A6EF408
Symétriquement, sur chaque secondaire, un process
walreceiver
apparaît.
postgres: 16/secondaire2: walreceiver streaming 0/DD73C218
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…
Quelques histoires « d’horreur » à ce sujet :
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é.
Il existe plusieurs méthodes pour promouvoir un serveur PostgreSQL en mode standby. Les méthodes les plus appropriées sont :
promote
de l’outil pg_ctl
, ou de
son équivalent dans les scripts des paquets d’installation, comme
pg_ctlcluster
sous Debian ; 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.
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é.
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 :
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.
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.
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.
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 :
ERROR: requested WAL segment 000000010000009900000045 has already been removed
On peut forcer le primaire à conserver des journaux pour un serveur secondaire :
wal_keep_size
, pour que le primaire conserve toujours
une certaine quantité de journaux, inutiles pour lui, au cas où un
secondaire le demande.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.
La robustesse de la réplication physique est éprouvée depuis longtemps. C’est à cette base solide que Patroni amène l’automatisation de :
Ces mécanismes sont abordé dans le module consacré à Patroni.
L’installation est détaillée ici pour Rocky Linux 8 et 9 (similaire à Red Hat et à d’autres variantes comem Oracle Linux et Fedora), et Debian/Ubuntu.
Elle ne dure que quelques minutes.
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.
Installation du dépôt communautaire :
Les dépôts de la communauté sont sur https://yum.postgresql.org/. Les commandes qui suivent sont inspirées de celles générées par l’assistant sur https://www.postgresql.org/download/linux/redhat/, en précisant :
Les commandes sont à lancer sous root :
# dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms\
/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm
# dnf -qy module disable postgresql
Installation de PostgreSQL 17 (client, serveur, librairies, extensions) :
# dnf install -y postgresql17-server postgresql17-contrib
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é.
# dnf install postgresql17-llvmjit
Création d’une première instance :
Il est conseillé de déclarer PG_SETUP_INITDB_OPTIONS
,
notamment pour mettre en place les sommes de contrôle et forcer les
traces en anglais :
# export PGSETUP_INITDB_OPTIONS='--data-checksums --lc-messages=C'
# /usr/pgsql-17/bin/postgresql-17-setup initdb # cat /var/lib/pgsql/17/initdb.log
Ce dernier fichier permet de vérifier que tout s’est bien passé et doit finir par :
Success. You can now start the database server using:
/usr/pgsql-17/bin/pg_ctl -D /var/lib/pgsql/17/data/ -l logfile start
Chemins :
Objet | Chemin |
---|---|
Binaires | /usr/pgsql-17/bin |
Répertoire de l’utilisateur postgres | /var/lib/pgsql |
PGDATA par défaut |
/var/lib/pgsql/17/data |
Fichiers de configuration | dans PGDATA/ |
Traces | dans PGDATA/log |
Configuration :
Modifier postgresql.conf
est facultatif pour un premier
lancement.
Commandes d’administration habituelles :
Démarrage, arrêt, statut, rechargement à chaud de la configuration, redémarrage :
# systemctl start postgresql-17
# systemctl stop postgresql-17
# systemctl status postgresql-17
# systemctl reload postgresql-17 # systemctl restart postgresql-17
Test rapide de bon fonctionnement et connexion à psql :
# systemctl --all |grep postgres # sudo -iu postgres psql
Démarrage de l’instance au lancement du système d’exploitation :
# systemctl enable postgresql-17
Ouverture du firewall pour le port 5432 :
Voir si le firewall est actif :
# systemctl status firewalld
Si c’est le cas, autoriser un accès extérieur :
# firewall-cmd --zone=public --add-port=5432/tcp --permanent
# firewall-cmd --reload # firewall-cmd --list-all
(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.
/var/lib/pgsqsl/17/
(ou
l’équivalent pour d’autres versions majeures).Pour créer une seconde instance, nommée par exemple infocentre :
# cp /lib/systemd/system/postgresql-17.service \ /etc/systemd/system/postgresql-17-infocentre.service
Environment=PGDATA=/var/lib/pgsql/17/infocentre
# export PGSETUP_INITDB_OPTIONS='--data-checksums --lc-messages=C' # /usr/pgsql-17/bin/postgresql-17-setup initdb postgresql-17-infocentre
Option 2 : restauration d’une sauvegarde : la procédure dépend de votre outil.
Adaptation de
/var/lib/pgsql/17/infocentre/postgresql.conf
(port
surtout).
Commandes de maintenance de cette instance :
# systemctl [start|stop|reload|status] postgresql-17-infocentre # systemctl [enable|disable] postgresql-17-infocentre
Sauf précision, tout est à effectuer en tant qu’utilisateur root.
Référence : https://apt.postgresql.org/
Installation du dépôt communautaire :
L’installation des dépôts du PGDG est prévue dans le paquet Debian :
# apt update
# apt install -y gnupg2 postgresql-common # /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh
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 17 :
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 sommes de contrôle et les traces en
anglais :
initdb_options = '--data-checksums --lc-messages=C'
Puis installer les paquets serveur et clients et leurs dépendances :
# apt install postgresql-17 postgresql-client-17
La première instance est automatiquement créée, démarrée et déclarée
comme service à lancer au démarrage du système. Elle porte un nom (par
défaut main
).
Elle est immédiatement accessible par l’utilisateur système postgres.
Chemins :
Objet | Chemin |
---|---|
Binaires | /usr/lib/postgresql/17/bin/ |
Répertoire de l’utilisateur postgres | /var/lib/postgresql |
PGDATA de l’instance par défaut | /var/lib/postgresql/17/main |
Fichiers de configuration | dans
/etc/postgresql/17/main/ |
Traces | dans
/var/log/postgresql/ |
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 :
# pg_ctlcluster 17 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/17/main/start.conf
.
Ouverture du firewall :
Debian et Ubuntu n’installent pas de firewall par défaut.
Statut des instances du serveur :
# pg_lsclusters
Test rapide de bon fonctionnement et connexion à psql :
# systemctl --all |grep postgres # sudo -iu postgres psql
Destruction d’une instance :
# pg_dropcluster 17 main
Création d’autres instances :
Ce qui suit est valable pour remplacer l’instance par défaut par une autre, par exemple pour mettre les checksums en place :
/etc/postgresql-common/createcluster.conf
permet de mettre
en place tout d’entrée les checksums, les messages en anglais,
le format des traces ou un emplacement séparé pour les journaux :initdb_options = '--data-checksums --lc-messages=C'
log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '
waldir = '/var/lib/postgresql/wal/%v/%c/pg_wal'
# pg_createcluster 17 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) :
# pg_createcluster 17 infocentre \
--port=12345 \
--datadir=/PGDATA/17/infocentre \
--pgoption shared_buffers='8GB' --pgoption work_mem='50MB' \ -- --data-checksums --waldir=/ssd/postgresql/17/infocentre/journaux
adapter au besoin
/etc/postgresql/17/infocentre/postgresql.conf
;
démarrage :
# pg_ctlcluster 17 infocentre start
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 :
$ sudo -iu postgres psql
psql (17.0)
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
:
local all postgres trust
La connexion en tant qu’utilisateur postgres
(ou tout
autre) n’est alors plus sécurisée :
dalibo:~$ psql -U postgres
psql (17.0)
Type "help" for help. postgres=#
Une authentification par mot de passe est plus sécurisée :
pg_hba.conf
, paramétrer une authentification par
mot de passe pour les accès depuis localhost
(déjà en place
sous Debian) :
# 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.)
postgres
de
l’instance :
dalibo:~$ sudo -iu postgres psql
psql (17.0)
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 (17.0)
Type "help" for help. postgres=#
.pgpass
dans le répertoire personnel doit contenir
les informations sur cette connexion :localhost:5432:*:postgres:motdepassetrèslong
Ce fichier doit être protégé des autres utilisateurs :
$ chmod 600 ~/.pgpass
psql
, on peut définir ces
variables d’environnement dans la session voire dans
~/.bashrc
:export PGUSER=postgres
export PGDATABASE=postgres
export PGHOST=localhost
Rappels :
/var/lib/pgsql/17/data/log
ou
/var/log/postgresql/
) ;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 : root:~# systemctl reload postgresql-17
root:~# pg_ctlcluster 17 main reload
postgres:~$ psql -c 'SELECT pg_reload_conf()'
La version en ligne des solutions de ces TP est disponible sur https://dali.bo/r56_solutions.
Ce TP utilise plusieurs VM : p1, p2, p3 et b1.
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
:
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.
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.
- 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.
- 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 ?
- 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.
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 commandepg_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
poursynchronous_standby_names
. Observer les champssync_priority
etsync_state
depg_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.
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 ?
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.
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.
- 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.
- 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 ?
- 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.
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 commandepg_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
poursynchronous_standby_names
. Observer les champssync_priority
etsync_state
depg_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.
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 ?
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.
La version de PostgreSQL est la version 17. Adapter au besoin pour une version ultérieure.
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.
- Créer l’instance principale dans
/var/lib/pgsql/17/main
sur le serveur p1. Les sommes de contrôles devront être activées.
sudo PGSETUP_INITDB_OPTIONS='--data-checksums' \
/usr/pgsql-17/bin/postgresql-17-setup initdb
Initializing database ... OK
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 :
# Allow replication
host replication repli 10.0.0.22/32 scram-sha-256
host replication repli 10.0.0.23/32 scram-sha-256
Les entrées configurées par défaut qui permettent de faire de la réplication en local peuvent être supprimées :
# Allow replication connections from localhost, by a user with the
# replication privilege.
local replication all peer
host replication all 127.0.0.1/32 scram-sha-256
host 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
:
createuser --no-superuser --no-createrole --no-createdb --replication -P repli
Enter password for new role:
Enter it again:
Configurer ensuite le fichier .pgpass
de l’utilisateur
système postgres
:
echo '*:*:*:repli:confidentiel' >> ~/.pgpass
chmod 600 ~/.pgpass
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
.
psql -c "\dconfig+ (listen_addresses|wal_keep_size)"
List of configuration parameters
Parameter | Value | Type | Context | Access privileges
------------------+-----------+---------+------------+-------------------
listen_addresses | localhost | string | postmaster |
wal_keep_size | 0 | integer | sighup |
(2 rows)
Comme vous pouvez le voir dans la colonne context
,
listen_addresses
nécessite un redémarrage pour être pris en
compte.
sudo systemctl restart postgresql-17.service # redémarage
sudo systemctl status postgresql-17.service # contrôle
psql -c "\dconfig+ (listen_addresses|wal_keep_size)"
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
Créer un squelette d’instance sur le serveur p2 :
sudo /usr/pgsql-17/bin/postgresql-17-setup initdb
Supprimer le répertoire de données :
rm -Rf /var/lib/pgsql/17/data
Créer le fichier ~/.pgpass
:
echo '*:*:*:repli:confidentiel' >> ~/.pgpass
chmod 600 ~/.pgpass
Utiliser pg_basebackup
pour créer l’instance
secondaire :
pg_basebackup --pgdata /var/lib/pgsql/17/data \
--progress \
--write-recovery-conf \
--checkpoint fast \
--host 10.0.0.21 \
--username repli
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
) :
file /var/lib/pgsql/17/data/standby.signal
/var/lib/pgsql/17/data/standby.signal: empty
cat /var/lib/pgsql/17/data/postgresql.auto.conf
# 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 :
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 :
grep -E "host.*replication" /var/lib/pgsql/17/data/pg_hba.conf
host replication repli 10.0.0.21/32 scram-sha-256
host replication repli 10.0.0.23/32 scram-sha-256
- Démarrer l’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.
Il ne reste désormais plus qu’à démarrer l’instance secondaire :
sudo systemctl start postgresql-17.service # redémarrage
sudo systemctl status postgresql-17.service # contrôle
La commande ps
suivante permet de voir que les deux
serveurs sont lancés :
ps -o pid,cmd fx
Voici le résultat de la commande sur le seveur p2 :
PID CMD
12895 /usr/pgsql-17/bin/postgres -D /var/lib/pgsql/17/data/
12896 \_ postgres: logger
12897 \_ postgres: checkpointer
12898 \_ postgres: background writer
12899 \_ postgres: startup recovering 000000010000000000000003
12900 \_ postgres: walreceiver streaming 0/3000148
La même commande exécutée sur le serveur p1 :
PID CMD
12443 /usr/pgsql-17/bin/postgres -D /var/lib/pgsql/17/data/
12444 \_ postgres: logger
12445 \_ postgres: checkpointer
12446 \_ postgres: background writer
12448 \_ postgres: walwriter
12449 \_ postgres: autovacuum launcher
12450 \_ postgres: logical replication launcher
12940 \_ postgres: walsender repli 10.0.0.22(50520) streaming 0/3000148
Nous avons bien les deux processus de réplication en flux
wal sender
et wal receiver
.
Créons quelques données sur le principal et assurons-nous qu’elles soient transmises au secondaire :
createdb b1
psql b1
psql (17.1)
Type "help" for help.
=# CREATE TABLE t1(id integer); b1
CREATE TABLE
=# INSERT INTO t1 SELECT generate_series(1, 1000000); b1
INSERT 0 1000000
En exécutant la commande suivante sur les serveurs p1 et p2, on constate que le flux a été transmis :
ps -o pid,cmd fx | egrep "(startup|walsender|walreceiver)"
Sur p1 :
12940 \_ postgres: walsender repli 10.0.0.22(50520) streaming 0/71AFD20
Sur p2 :
12899 \_ postgres: startup recovering 000000010000000000000007
12900 \_ postgres: walreceiver streaming 0/71AFD20
Essayons de nous connecter au secondaire et d’exécuter quelques requêtes :
psql b1
psql (17.1)
Type "help" for help.
=# SELECT COUNT(*) FROM t1; b1
count
---------
1000000
=# CREATE TABLE t2(id integer); b1
ERROR: cannot execute CREATE TABLE in a read-only transaction
On peut se connecter, lire des données, mais pas écrire.
Le comportement est visible dans le log de l’instance secondaire dans
le fichier pointé par
/var/lib/pgsql/17/data/current_logfiles
:
cat /var/lib/pgsql/17/data/current_logfiles
stderr log/postgresql-Thu.log
grep "database system is ready" /var/lib/pgsql/17/data/log/postgresql-Thu.log
[…] LOG: database system is ready to accept read only connections
PostgreSQL indique bien qu’il accepte des connexions en lecture seule.
- En respectant les étapes de vérification de l’état des instances, effectuer une promotion contrôlée de l’instance secondaire.
Sur p1, arrêt de l’instance primaire et vérification de son état :
sudo systemctl stop postgresql-17.service
/usr/pgsql-17/bin/pg_controldata -D /var/lib/pgsql/17/data/ \
| grep -E '(cluster)|(REDO)'
Database cluster state: shut down
Latest checkpoint's REDO location: 0/71AFE08
Latest checkpoint's REDO WAL file: 000000010000000000000007
Vérification de l’instance secondaire sur p2 :
psql -c 'CHECKPOINT;'
/usr/pgsql-17/bin/pg_controldata -D /var/lib/pgsql/17/data/ \
| grep -E '(cluster)|(REDO)'
Database cluster state: in archive recovery
Latest checkpoint's REDO location: 0/71AFE08
Latest checkpoint's REDO WAL file: 000000010000000000000007
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 :
/usr/pgsql-17/bin/pg_ctl promote -w -D /var/lib/pgsql/17/data
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 :
psql b1
psql (17.1)
Type "help" for help.
=# CREATE TABLE t2(id integer); b1
CREATE TABLE
=# INSERT INTO t2 SELECT generate_series(1, 1000000); b1
INSERT 0 1000000
Les écritures sont désormais possibles sur cette instance.
- 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 :
mv /var/lib/pgsql/17/data /var/lib/pgsql/17/data.old
pg_basebackup --pgdata /var/lib/pgsql/17/data \
--progress \
--write-recovery-conf \
--checkpoint fast \
--host 10.0.0.22 \
--username repli
101603/101603 kB (100%), 1/1 tablespace
Vérifier la présence du fichier standby.signal
.
file /var/lib/pgsql/17/data/standby.signal
/var/lib/pgsql/17/data/standby.signal: empty
Contrôler postgresql.auto.conf
(qui contient
potentiellement deux lignes primary_conninfo
!).
cat /var/lib/pgsql/17/data/postgresql.auto.conf
# paramètres remis en forme pour le manuel
primary_conninfo = 'user=repli 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'
Supprimer les traces :
rm /var/lib/pgsql/17/data/log/*
Les fichiers de configuration de l’instance ayant été copiés depuis
p2 par le pg_basebackup
, il faut les
recopier depuis notre sauvegarde.
cp /var/lib/pgsql/17/data.old/postgresql.conf /var/lib/pgsql/17/data
cp /var/lib/pgsql/17/data.old/pg_hba.conf /var/lib/pgsql/17/data
- Démarrer cette nouvelle instance.
sudo systemctl start postgresql-17.service # démarrage
sudo systemctl status postgresql-17.service # contrôle
- 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 :
ps -o pid,cmd fx | egrep "(startup|walsender|walreceiver)"
Sur p1 :
13666 \_ postgres: startup recovering 00000002000000000000000C
13667 \_ postgres: walreceiver streaming 0/C000060
Sur p2 :
13676 \_ postgres: walsender repli 10.0.0.21(46610) streaming 0/C000060
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 :
psql b1
psql (17.1)
Type "help" for help.
=# SELECT COUNT(*) FROM t2; b1
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 :
sudo systemctl stop postgresql-17.service
/usr/pgsql-17/bin/pg_controldata -D /var/lib/pgsql/17/data/ \
| grep -E '(cluster)|(REDO)'
Database cluster state: shut down
Latest checkpoint's REDO location: 0/C000148
Latest checkpoint's REDO WAL file: 00000002000000000000000C
Vérification de l’instance secondaire p1 :
psql -c 'CHECKPOINT;'
/usr/pgsql-17/bin/pg_controldata -D /var/lib/pgsql/17/data/ \
| grep -E '(cluster)|(REDO)'
Database cluster state: in archive recovery
Latest checkpoint's REDO location: 0/C000148
Latest checkpoint's REDO WAL file: 00000002000000000000000C
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 :
/usr/pgsql-17/bin/pg_ctl promote -w -D /var/lib/pgsql/17/data
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 :
grep "^host.*replication" /var/lib/pgsql/17/data/pg_hba.conf
host replication repli 10.0.0.22/32 scram-sha-256
host replication repli 10.0.0.23/32 scram-sha-256
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 :
touch /var/lib/pgsql/17/data/standby.signal
sudo systemctl start postgresql-17.service
ps -o pid,cmd fx | egrep "(startup|walsender|walreceiver)"
Sur p1 :
14254 \_ postgres: walsender repli 10.0.0.22(53776) streaming 0/C0002D8
Sur p2 :
13996 \_ postgres: startup recovering 00000003000000000000000C
14172 \_ postgres: walreceiver streaming 0/C0002D8
- Configurer la réplication synchrone de p1 vers p2.
Sur l’instance primaire éditer le paramétrage de l’instance :
grep -E "^synchronous_standby_names" /var/lib/pgsql/17/data/postgresql.conf
synchronous_standby_names = 'p2'
Sur l’instance secondaire vérifier que
l’application_name
fourni dans le paramètre
primary_conninfo
est bien p2
.
grep -E "^primary_conninfo" /var/lib/pgsql/17/data/postgresql.auto.conf
primary_conninfo = 'user=repli host=10.0.0.21 port=5432 application_name=p2'
Recharger la configuration sur les deux serveurs :
SELECT pg_reload_conf();
Lorsque l’on liste les connexions de réplication depuis p1, on voit la connexion synchrone :
SELECT pid, application_name, sync_priority, sync_state
FROM pg_stat_replication;
pid | application_name | sync_priority | sync_state
------+------------------+---------------+------------
1623 | p2 | 0 | async
(1 row)
- Stopper l’instance secondaire p2, créer une table
t1
, que se passe-t-il ?
Arrêter l’instance sur p2 :
sudo systemctl stop postgresql-17
Créer une table t1
sur p1 :
CREATE TABLE t1();
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.
SELECT pid, usename, datname, wait_event_type, wait_event, state, query
FROM pg_stat_activity WHERE query LIKE 'CREATE TABLE t1%' \gx
-[ 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.
\dt t1
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.
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 :
SELECT pid, usename, datname, wait_event_type, wait_event, state, query,
as cancelled
pg_cancel_backend(pid) FROM pg_stat_activity WHERE query LIKE 'CREATE TABLE t2%' \gx
\dt t2
-[ 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 :
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 :
pg_basebackup
;standby.signal
) ;pg_hba.conf
) ;postgresql.conf
) ;postgresql.auto.conf
) ;.pgpass
) ;
- Configurer la réplication synchrone de p1 vers p2 et p3 en utilisant la syntaxe
p2, p3
. Observer les champssync_priority
etsync_state
depg_stat_replication
.
La configuration a été mise à jour et prise en compte :
grep -E "^synchronous_standby_names" /var/lib/pgsql/17/data/postgresql.conf
synchronous_standby_names = 'p2, p3'
Note: une syntaxe équivalente est FIRST 1 (p2, p3)
ou
FIRST (p2, p3)
, elle ne sera pas utilisée parce que Patroni
ne l’utilise pas.
La vue pg_stat_replication
contient désormais les
informations suivantes :
SELECT pid, application_name, sync_priority, sync_state
FROM pg_stat_replication
ORDER BY application_name;
pid | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
2025 | p2 | 1 | sync
1664 | p3 | 2 | potential
(2 rows)
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
.
pid | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
1664 | p3 | 2 | sync
(1 row)
p3 stoppée : la vue est vide.
p3 redémarrée : La vue est revenue dans son état
précédent, seul le pid
à changer.
pid | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
2035 | p3 | 2 | sync
(1 row)
p2 redémarrée : La vue est revenue dans son état initial. p2 est synchrone.
pid | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
2036 | p2 | 1 | sync
2035 | p3 | 2 | potential
(2 rows)
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 :
grep -E "^synchronous_standby_names" /var/lib/pgsql/17/data/postgresql.conf
synchronous_standby_names = '2 (p2, p3)'
Note: une syntaxe équivalente est FIRST 2 (p2, p3)
.
La vue pg_stat_replication
contient désormais les
informations suivantes :
SELECT pid, application_name, sync_priority, sync_state
FROM pg_stat_replication
ORDER BY application_name;
pid | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
2036 | p2 | 1 | sync
2035 | p3 | 2 | sync
(2 rows)
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.
SELECT pid, application_name, sync_priority, sync_state
FROM pg_stat_replication ORDER BY application_name;
pid | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
2036 | p2 | 1 | potential
2035 | p3 | 1 | sync
(2 rows)
Si l’on arrête p2 et p3 et que l’on redémarre p2 avant p3, on obtient :
pid | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
2104 | p2 | 1 | sync
2105 | p3 | 1 | potential
(2 rows)
- Mettre en place la réplication synchrone par quorum avec un serveur synchrone minimum.
La configuration a été mise à jour et prise en compte par un
pg_reload_conf()
:
grep -E "^synchronous_standby_names" /var/lib/pgsql/17/data/postgresql.conf
synchronous_standby_names = 'ANY 1 (p2, p3)'
Dans pg_stat_replication
, on observe que le statut des
deux instances est quorum
et que leur priorité est
identique :
pid | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
2104 | p2 | 1 | quorum
2105 | p3 | 1 | quorum
(2 rows)
Dans cet état, après un commit, la réplication synchrone rend la main dès que les données sont répliquées sur n’importe lequel des deux serveurs.
- Désactiver la réplication synchrone.
Modifier le paramètre synchronous_standby_names
sur
p1 :
grep -E "^synchronous_standby_names" /var/lib/pgsql/17/data/postgresql.conf
synchronous_standby_names = ''
Recharger la configuration.
SELECT pg_reload_conf();
Contrôler le contenu de la vue pg_stat_replication
:
pid | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
2104 | p2 | 0 | async
2105 | p3 | 0 | async
(2 rows)
- 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.
sudo dnf install -y epel-release
sudo dnf 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.
sudo install -o postgres -g postgres -m 0755 -d /var/log/pgbackrest
sudo install -o postgres -g postgres -m 0755 -d /var/lib/pgbackrest
sudo chown postgres:postgres /etc/pgbackrest.conf
cat << _EOF_ > /etc/pgbackrest.conf
[global]
repo1-path=/var/lib/pgbackrest
repo1-retention-full=2
log-level-console=info
log-level-file=detail
[main]
pg1-host=p1
pg1-path=/var/lib/pgsql/17/data
pg2-host=p2
pg2-path=/var/lib/pgsql/17/data
pg3-host=p3
pg3-path=/var/lib/pgsql/17/data
_EOF_
Note: toutes les instances doivent exister et être accessible pour
que la commande stanza-create
fonctionne.
- Configurer pgBackRest sur les serveurs de base de données.
Sur les serveurs p1, p2 et p3 :
sudo install -o postgres -g postgres -m 0755 -d /var/log/pgbackrest
sudo chown postgres:postgres /etc/pgbackrest.conf
cat << _EOF_ > /etc/pgbackrest.conf
[global]
repo1-host=b1
repo1-host-user=postgres
repo1-path=/var/lib/pgbackrest
repo1-retention-full=2
log-path=/var/log/pgbackrest
log-level-console=info
log-level-file=detail
[main]
pg1-path=/var/lib/pgsql/17/data
_EOF_
Tester la connexion entre b1 et p1, p2, p3 et inversement.
Depuis p1, p2, p3 :
ssh -o "StrictHostKeyChecking=no" b1 hostname
Depuis b1 :
for i in $(seq 1 3); do ssh -o "StrictHostKeyChecking=no" p$i hostname; done
La configuration de PostgreSQL doit aussi être mise à jour :
cat << _EOF_ >> /var/lib/pgsql/17/data/postgresql.conf
archive_mode = 'on'
archive_command = 'pgbackrest --stanza=main archive-push %p'
restore_command = 'pgbackrest --stanza=main archive-get %f "%p"'
_EOF_
L’activation de l’archivage nécessitant encore un redémarrage en version 17 :
sudo systemctl restart postgresql-17
- Créer la stanza, tester l’archivage.
Sur le serveur de sauvegarde b1 :
pgbackrest --stanza=main stanza-create
[…] INFO: stanza-create command begin 2.54.2: --exec-id=1624-fb78780f --log-level-console=info --log-level-file=detail --pg1-host=p1 --pg2-host=p2 --pg3-host=p3 --pg1-path=/var/lib/pgsql/17/data --pg2-path=/var/lib/pgsql/17/data --pg3-path=/var/lib/pgsql/17/data --repo1-path=/var/lib/pgbackrest --stanza=main
[…] INFO: stanza-create for stanza 'main' on repo1
[…] INFO: stanza-create command end: completed successfully (7730ms)
pgbackrest --stanza=main check
[…] INFO: check command begin 2.54.2: --exec-id=2135-d604f794 --log-level-console=info --log-level-file=detail --pg1-host=p1 --pg2-host=p2 --pg3-host=p3 --pg1-path=/var/lib/pgsql/17/data --pg2-path=/var/lib/pgsql/17/data --pg3-path=/var/lib/pgsql/17/data --repo1-path=/var/lib/pgbackrest --stanza=main
[…] INFO: check repo1 (standby)
[…] INFO: switch wal not performed because this is a standby
[…] INFO: check repo1 configuration (primary)
[…] INFO: check repo1 archive for WAL (primary)
[…] INFO: WAL segment 00000003000000000000000C successfully archived to '/var/lib/pgbackrest/archive/main/17-1/0000000300000000/00000003000000000000000C-92677832c9edd5ea66f0c498f66274c82d1868b8.gz' on repo1
[…] INFO: check command end: completed successfully (3007ms)
- Lancer une sauvegarde.
pgbackrest --stanza=main backup --type=full --start-fast
[…] INFO: backup command begin 2.54.2: --exec-id=2171-3198a97f --log-level-console=info --log-level-file=detail --pg1-host=p1 --pg2-host=p2 --pg3-host=p3 --pg1-path=/var/lib/pgsql/17/data --pg2-path=/var/lib/pgsql/17/data --pg3-path=/var/lib/pgsql/17/data --repo1-path=/var/lib/pgbackrest --repo1-retention-full=2 --stanza=main --start-fast --type=full
[…] INFO: execute non-exclusive backup start: backup begins after the requested immediate checkpoint completes
[…] INFO: backup start archive = 00000003000000000000000E, lsn = 0/E000028
[…] INFO: check archive for prior segment 00000003000000000000000D
[…] INFO: execute non-exclusive backup stop and wait for all WAL segments to archive
[…] INFO: backup stop archive = 00000003000000000000000E, lsn = 0/E000158
[…] INFO: check archive for segment(s) 00000003000000000000000E:00000003000000000000000E
[…] INFO: new backup label = 20250311-134434F
[…] INFO: full backup size = 64MB, file total = 1271
[…] INFO: backup command end: completed successfully (48106ms)
[…] INFO: expire command begin 2.54.2: --exec-id=2171-3198a97f --log-level-console=info --log-level-file=detail --repo1-path=/var/lib/pgbackrest --repo1-retention-full=2 --stanza=main
[…] INFO: expire command end: completed successfully (204ms)
pgbackrest --stanza=main info
stanza: main
status: ok
cipher: none
db (current)
wal archive min/max (17): 00000003000000000000000C/00000003000000000000000E
full backup: 20250311-134434F
timestamp start/stop: 2025-03-11 13:44:34+00 / 2025-03-11 13:45:18+00
wal start/stop: 00000003000000000000000E / 00000003000000000000000E
database size: 64MB, database backup size: 64MB
repo1: backup set size: 7.8MB, backup size: 7.8MB
- Si p3 existe, le stopper et détruire le répertoire de données.
sudo systemctl stop postgresql-17
rm -Rf /var/lib/pgsql/17/data
- Créer l’instance à partir de la sauvegarde et s’assure qu’elle est bien en réplication.
pgbackrest --stanza=main restore \
--delta --type=standby \
--recovery-option=primary_conninfo="user=repli host=10.0.0.21 port=5432 application_name=p3"
[…] 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éé :
file ~/17/data/standby.signal
/var/lib/pgsql/17/data/standby.signal: empty
La configuration a bien été mise à jour :
cat ~/17/data/postgresql.auto.conf
# 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 :
grep -E "host.*replication" /var/lib/pgsql/17/data/pg_hba.conf
host replication repli 10.0.0.21/32 scram-sha-256
host replication repli 10.0.0.22/32 scram-sha-256
Démarrer l’instance :
sudo systemctl start postgresql-17
L’instance est bien démarrée et la réplication fonctionne :
ps -u postgres -o cmd
CMD
/usr/pgsql-17/bin/postgres -D /var/lib/pgsql/17/data/
postgres: logger
postgres: checkpointer
postgres: background writer
postgres: startup waiting for 000000030000000000000010
- Interdire la connexion à p1 depuis p3 dans le
pg_hba.conf
, générer de l’activité, que se passe-t-il sur p3 ?
Ajouter une ligne reject
au pg_hba.conf
avant les lignes existantes (ici ligne 118) sur
p1 :
grep -nE "10\.0\.0\.23" /var/lib/pgsql/17/data/pg_hba.conf
121:host replication repli 10.0.0.23/32 reject
122:host replication repli 10.0.0.23/32 scram-sha-256
Recharger la configuration sur p1.
SELECT pg_reload_conf();
Redémarrer p3, cela permet de ré-initialiser la connexion de réplication.
sudo systemctl restart postgresql-17
On voit que la réplication a décroché.
ps -u postgres -o cmd
/usr/pgsql-17/bin/postgres -D /var/lib/pgsql/17/data/
postgres: logger
postgres: checkpointer
postgres: background writer
postgres: startup recovering 000000010000000000000009
Générer de l’activité sur p1 :
createdb pgbench
/usr/pgsql-17/bin/pgbench -i -s10 pgbench
On observe que l’instance a continué de consommer les WAL en provenance de p1 via le dépôt de sauvegarde de pgBackRest.
ps -u postgres -o cmd
/usr/pgsql-17/bin/postgres -D /var/lib/pgsql/17/data/
postgres: logger
postgres: checkpointer
postgres: background writer
postgres: startup waiting for 000000010000000000000019
- Faire marche arrière sur la modification, que se passe-t-il sur p3 ?
Retire la ligne suivante au pg_hba.conf
avant les lignes
existantes :
grep -nE "10\.0\.0\.23" /var/lib/pgsql/17/data/pg_hba.conf
134:host replication repli 10.0.0.23/32 scram-sha-256
Recharger la configuration sur p1.
SELECT pg_reload_conf();
La connexion de réplication est de nouveau en place.
ps -u postgres -o cmd
/usr/pgsql-17/bin/postgres -D /var/lib/pgsql/17/data/
postgres: logger
postgres: checkpointer
postgres: background writer
postgres: startup recovering 000000010000000000000010
postgres: walreceiver streaming 0/10F3E2C0
Dans les traces, on voit que le log shipping a été abandonné au profil de la réplication :
grep -A20 -B1 -E "started streaming WAL" /var/lib/pgsql/17/data/log/postgresql-Wed.log
INFO: archive-get command end: completed successfully (412ms)
LOG: started streaming WAL from primary at 0/10000000 on timeline 1
- Promouvoir l’instance p2. Générer de l’activité sur p1.
Promouvoir l’instance p2 :
psql -c "SELECT pg_promote();"
Sur l’instance p1, exécuter pgbench
pour simuler que des écritures ont continué à arriver après la
promotion :
dropdb pgbench
createdb pgbench
/usr/pgsql-17/bin/pgbench -i -s10 pgbench
- Stopper p1 et configurer la réplication pour raccrocher p1 à p2.
Stopper p1 :
sudo systemctl stop postgresql-17
Créer un fichier standby.signal
:
touch /var/lib/pgsql/17/data/standby.signal
Vérifier que le paramètre primary_conninfo
est
correctement configuré :
grep -E "^primary_conninfo" /var/lib/pgsql/17/data/postgresql.auto.conf
primary_conninfo = 'user=repli host=10.0.0.22 port=5432 application_name=p1'
Démarrer l’instance :
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.
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 checkpoints, ce qui inclus des hint nécessaires à l’utilisation de pg_rewind :
/usr/pgsql-17/bin/pg_controldata /var/lib/pgsql/17/data \
| grep "Data page checksum version"
Data page checksum version: 1
Un autre pré-requis est que l’instance a été arrêtée proprement, chose que nous avons fait précédemment.
Créer un utilisateur dédié à pg_rewind sur p2 :
CREATE USER rewind_user LOGIN;
GRANT EXECUTE ON function pg_catalog.pg_ls_dir(text, boolean, boolean) TO rewind_user;
GRANT EXECUTE ON function pg_catalog.pg_stat_file(text, boolean) TO rewind_user;
GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text) TO rewind_user;
GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean) TO rewind_user;
password rewind_user \
Ajouter le mot de passe du nouvel utilisateur aux fichiers
.pgpass
de tous les serveurs :
echo "*:*:*:rewind_user:rewind_password" >> ~/.pgpass
Mettre a jour le pg_hba
des serveurs (ici pour le
serveur p2) :
grep rewind_user /var/lib/pgsql/17/data/pg_hba.conf
host postgres rewind_user 10.0.0.21/32 scram-sha-256
host postgres rewind_user 10.0.0.23/32 scram-sha-256
Recharger la configuration sur p2 :
psql -c "SELECT pg_reload_conf();"
pg_reload_conf
----------------
t
(1 row)
Nous pouvons désormais lancer pg_rewind
(ici en mode à
blanc) :
/usr/pgsql-17/bin/pg_rewind \
--source-server="host=10.0.0.22 port=5432 user=rewind_user dbname=postgres" \
--target-pgdata=/var/lib/pgsql/17/data \
--progress \
--write-recovery-conf \
--dry-run
Les traces nous montrent les deux serveurs ont divergé au lsn
0/21BE14E8
.
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.
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.
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é.
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
:
file /var/lib/pgsql/17/data/standby.signal
/var/lib/pgsql/17/data/standby.signal: empty
mettre à jour la configuration de la réplication :
cat /var/lib/pgsql/17/data/postgresql.auto.conf
# 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 :
primary_conninfo = 'user=repli host=10.0.0.22 port=5432 application_name=p1'
L’instance peut ensuite être démarrée :
sudo systemctl start postgresql-17
L’instance est bien démarrée en tant que secondaire et réplique :
ps -u postgres -o cmd
/usr/pgsql-17/bin/postgres -D /var/lib/pgsql/17/data/
postgres: logger
postgres: checkpointer
postgres: background writer
postgres: startup recovering 000000020000000000000013
postgres: walreceiver
La seconde instance secondaire a elle aussi décroché, à vous de jouer !
Cette solution se base sur un système Debian 12, installé à minima depuis les paquets du PGDG, et en anglais.
Au préalable, nettoyer les instances précédemment créées sur le serveur.
# pg_dropcluster --stop 17 main
- Créer l’instance principale en utilisant
pg_createcluster
sur le serveur p1. Les sommes de contrôles devront être activées.
sudo pg_createcluster 17 main -- -k
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 :
sudo pg_ctlcluster start 17 main
soit explicitement via systemd :
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 :
# Allow replication
host replication repli 10.0.0.22/32 scram-sha-256
host replication repli 10.0.0.23/32 scram-sha-256
Les entrées configurées par défaut qui permettent de faire de la réplication en local peuvent être supprimées :
# Allow replication connections from localhost, by a user with the
# replication privilege.
local replication all peer
host replication all 127.0.0.1/32 scram-sha-256
host 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
:
createuser --no-superuser --no-createrole --no-createdb --replication -P repli
Enter password for new role:
Enter it again:
Configurer ensuite le fichier .pgpass
de l’utilisateur
système postgres
:
echo '*:*:*:repli:confidentiel' >> ~/.pgpass
chmod 600 ~/.pgpass
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
.
psql -c "\dconfig+ (listen_addresses|wal_keep_size)"
List of configuration parameters
Parameter | Value | Type | Context | Access privileges
------------------+-----------+---------+------------+-------------------
listen_addresses | localhost | string | postmaster |
wal_keep_size | 0 | integer | sighup |
(2 rows)
Comme vous pouvez le voir dans la colonne context
,
listen_addresses
nécessite un redémarrage.
sudo systemctl restart postgresql@17-main # redémarage
sudo systemctl status postgresql@17-main # contrôle
psql -c "\dconfig+ (listen_addresses|wal_keep_size)"
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.
sudo pg_createcluster 17 main -- -k
pg_lsclusters 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
Supprimer le répertoire de données :
rm -Rf /var/lib/postgresql/17/main
Créer le fichier ~/.pgpass
:
echo '*:*:*:repli:confidentiel' >> ~/.pgpass
chmod 600 ~/.pgpass
Utiliser pg_basebackup
pour créer l’instance
secondaire :
pg_basebackup --pgdata /var/lib/postgresql/17/main \
--progress \
--write-recovery-conf \
--checkpoint fast \
--host 10.0.0.21 \
--username repli
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
) :
file /var/lib/postgresql/17/main/standby.signal
/var/lib/postgresql/17/main/standby.signal: empty
cat /var/lib/postgresql/17/main/postgresql.auto.conf
# 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 :
grep -E "host.*replication" /etc/postgresql/17/main/pg_hba.conf
host replication repli 10.0.0.21/32 scram-sha-256
host replication repli 10.0.0.23/32 scram-sha-256
grep -E "^(listen_addresses|wal_keep_size)" /etc/postgresql/17/main/postgresql.conf
listen_addresses = '*'
wal_keep_size = '256MB'
- Démarrer l’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.
Il ne reste désormais plus qu’à démarrer l’instance secondaire :
sudo systemctl start postgresql@17-main # redémarrage
sudo systemctl status postgresql@17-main # contrôle
La commande ps
suivante permet de voir que les deux
serveurs sont lancés :
ps -o pid,cmd fx
Voici le résultat de la commande sur le seveur p2 :
PID CMD
4247 /usr/lib/postgresql/17/bin/postgres -D /var/lib/postgresql/17/main -c config_file=/etc/postgresql/17/main/postgresql.conf
4248 \_ postgres: 17/main: checkpointer
4249 \_ postgres: 17/main: background writer
4250 \_ postgres: 17/main: startup recovering 000000010000000000000003
4251 \_ postgres: 17/main: walreceiver streaming 0/3000148
La même commande exécutée sur le serveur p1 :
PID CMD
4084 /usr/lib/postgresql/17/bin/postgres -D /var/lib/postgresql/17/main -c config_file=/etc/postgresql/17/main/postgresql.conf
4085 \_ postgres: 17/main: checkpointer
4086 \_ postgres: 17/main: background writer
4088 \_ postgres: 17/main: walwriter
4089 \_ postgres: 17/main: autovacuum launcher
4090 \_ postgres: 17/main: logical replication launcher
4252 \_ postgres: 17/main: walsender repli 10.0.0.22(48570) streaming 0/3000148
Nous avons bien les deux processus de réplication en flux
wal sender
et wal receiver
.
Créons quelques données sur le principal et assurons-nous qu’elles soient transmises au secondaire :
createdb b1
psql b1
psql (17.1)
Type "help" for help.
=# CREATE TABLE t1(id integer); b1
CREATE TABLE
=# INSERT INTO t1 SELECT generate_series(1, 1000000); b1
INSERT 0 1000000
En exécutant la commande suivante sur les serveurs p1 et p2, on constate que le flux a été transmis :
ps -o pid,cmd fx | egrep "(startup|walsender|walreceiver)"
Sur p1 :
4252 \_ postgres: 17/main: walsender repli 10.0.0.22(48570) streaming 0/71A9E50
Sur p2 :
4250 \_ postgres: 17/main: startup recovering 000000010000000000000007
4251 \_ postgres: 17/main: walreceiver streaming 0/71A9E50
Essayons de nous connecter au secondaire et d’exécuter quelques requêtes :
psql b1
psql (17.1)
Type "help" for help.
=# SELECT COUNT(*) FROM t1; b1
count
---------
1000000
=# CREATE TABLE t2(id integer); b1
ERROR: cannot execute CREATE TABLE in a read-only transaction
On peut se connecter, lire des données, mais pas écrire.
Le comportement est visible dans le log de l’instance secondaire dans
le fichier /var/log/postgresql/postgresql-17-main.log
:
grep "database system is ready" /var/log/postgresql/postgresql-17-main.log
[…] LOG: database system is ready to accept read only connections
PostgreSQL indique bien qu’il accepte des connexions en lecture seule.
- En respectant les étapes de vérification de l’état des instances, effectuer une promotion contrôlée de l’instance secondaire.
Arrêt de l’instance primaire et vérification de son état sur p1 :
sudo systemctl stop postgresql@17-main
/usr/lib/postgresql/17/bin/pg_controldata -D /var/lib/postgresql/17/main \
| grep -E '(cluster)|(REDO)'
Database cluster state: shut down
Latest checkpoint's REDO location: 0/71A9F38
Latest checkpoint's REDO WAL file: 000000010000000000000007
Vérification de l’instance secondaire sur p2 :
psql -c 'CHECKPOINT'
/usr/lib/postgresql/17/bin/pg_controldata -D /var/lib/postgresql/17/main \
| grep -E '(cluster)|(REDO)'
Database cluster state: in archive recovery
Latest checkpoint's REDO location: 0/71A9F38
Latest checkpoint's REDO WAL file: 000000010000000000000007
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 :
psql -c 'SELECT pg_promote()'
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 :
psql b1
psql (17.1)
Type "help" for help.
=# CREATE TABLE t2(id integer); b1
CREATE TABLE
=# INSERT INTO t2 SELECT generate_series(1, 1000000); b1
INSERT 0 1000000
Les écritures sont désormais possibles sur cette instance.
- 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 :
mv /var/lib/postgresql/17/main /var/lib/postgresql/17/main.old
pg_basebackup --pgdata /var/lib/postgresql/17/main \
--progress \
--write-recovery-conf \
--checkpoint fast \
--host 10.0.0.22 \
--username repli
101537/101537 kB (100%), 1/1 tablespace
Vérifier la présence du fichier standby.signal
.
file /var/lib/postgresql/17/main/standby.signal
/var/lib/postgresql/17/main/standby.signal: empty
Contrôler postgresql.auto.conf
(qui contient
potentiellement deux lignes primary_conninfo
!).
cat /var/lib/postgresql/17/main/postgresql.auto.conf
# 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 :
rm /var/log/postgresql/postgresql-17-main.log
- Démarrer cette nouvelle instance.
Démarrer le service :
sudo systemctl start postgresql@17-main # Démarrage
sudo systemctl status postgresql@17-main # Contrôle
- 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 :
ps -o pid,cmd fx | egrep "(startup|walsender|walreceiver)"
Sur p1 (secondaire) :
27488 \_ postgres: 17/main: startup recovering 00000002000000000000000C
27489 \_ postgres: 17/main: walreceiver streaming 0/C000060
Sur p2 (primaire) :
19319 \_ postgres: 17/main: walsender repli 10.0.0.21(45332) streaming 0/C000060
En nous connectant à la nouvelle instance secondaire
(p2), vérifions que les données précédemment insérées
dans la table t2
sont bien présentes :
psql b1
psql (17.1)
Type "help" for help.
=# SELECT COUNT(*) FROM t2; b1
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 :
sudo systemctl stop postgresql@17-main
/usr/lib/postgresql/17/bin/pg_controldata -D /var/lib/postgresql/17/main/ \
| grep -E '(cluster)|(REDO)'
Database cluster state: shut down
Latest checkpoint's REDO location: 0/C000148
Latest checkpoint's REDO WAL file: 00000002000000000000000C
Vérification de l’instance secondaire sur p1 :
psql -c 'CHECKPOINT;'
/usr/lib/postgresql/17/bin/pg_controldata -D /var/lib/postgresql/17/main/ \
| grep -E '(cluster)|(REDO)'
Database cluster state: in archive recovery
Latest checkpoint's REDO location: 0/C000148
Latest checkpoint's REDO WAL file: 00000002000000000000000C
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 :
psql -c 'SELECT pg_promote()'
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 :
touch /var/lib/postgresql/17/main/standby.signal
sudo systemctl start postgresql@17-main
ps -o pid,cmd fx | egrep "(startup|walsender|walreceiver)"
Sur p1 :
27562 \_ postgres: 17/main: walsender repli 10.0.0.22(34562) streaming 0/C0002D8
Sur p2 :
19371 \_ postgres: 17/main: startup recovering 00000003000000000000000C
19373 \_ postgres: 17/main: walreceiver streaming 0/C0002D8
- Configurer la réplication synchrone de p1 vers p2.
Sur l’instance primaire éditer le paramétrage de l’instance :
grep -E "^synchronous_standby_names" /etc/postgresql/17/main/postgresql.conf
synchronous_standby_names = 'p2'
Sur l’instance secondaire vérifier que
l’application_name
fourni dans le paramètre
synchronous_standby_names
est bien p2
.
grep -E "^primary_conninfo" /var/lib/postgresql/17/main/postgresql.auto.conf
primary_conninfo = 'user=repli host=10.0.0.21 port=5432 application_name=p2'
Recharger la configuration sur les deux serveurs :
SELECT pg_reload_conf();
Lorsque l’on liste les connexions de réplication depuis p1, on voit la connexion synchrone :
SELECT pid, application_name, sync_priority, sync_state
FROM pg_stat_replication;
pid | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
35258 | p2 | 1 | sync
(1 row)
- Stopper l’instance secondaire p2, créer une table
t1
, que se passe-t-il ?
Arrêter l’instance sur p2 :
sudo systemctl stop postgresql@17-main
Créer une table t1
sur p1 :
CREATE TABLE t1();
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.
SELECT pid, usename, datname, wait_event_type, wait_event, state, query
FROM pg_stat_activity WHERE query LIKE 'CREATE TABLE t1%' \gx
-[ 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.
\dt t1
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.
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 :
SELECT pid, usename, datname, wait_event_type, wait_event, state, query,
as cancelled
pg_cancel_backend(pid) FROM pg_stat_activity WHERE query LIKE 'CREATE TABLE t2%' \gx
\dt t2
-[ 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 :
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 :
pg_basebackup
;standby.signal
) ;pg_hba.conf
) ;postgresql.conf
) ;postgresql.auto.conf
) ;.pgpass
) ;
- Configurer la réplication synchrone de p1 vers p2 et p3 en utilisant la syntaxe
p2, p3
. Observer les champssync_priority
etsync_state
depg_stat_replication
.
La configuration a été mise à jour et prise en compte :
grep -E "^synchronous_standby_names" /etc/postgresql/17/main/postgresql.conf
synchronous_standby_names = 'p2, p3'
Note: une syntaxe équivalente est FIRST 1 (p2, p3)
ou
FIRST (p2, p3)
, elle ne sera pas utilisée parce que Patroni
ne l’utilise pas.
La vue pg_stat_replication
contient désormais les
informations suivantes :
SELECT pid, application_name, sync_priority, sync_state
FROM pg_stat_replication
ORDER BY application_name;
pid | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
35517 | p2 | 1 | sync
35260 | p3 | 2 | potential
(2 rows)
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
.
pid | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
35260 | p3 | 2 | sync
(1 row)
p3 stoppée : la vue est vide.
p3 redémarrée : La vue est revenue dans son état
précédent, seul le pid
à changer.
pid | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
35533 | p3 | 2 | sync
(1 row)
p2 redémarrée : La vue est revenue dans son état initial. p2 est synchrone.
pid | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
35534 | p2 | 1 | sync
35533 | p3 | 2 | potential
(2 rows)
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 :
grep -E "^synchronous_standby_names" /etc/postgresql/17/main/postgresql.conf
synchronous_standby_names = '2 (p2, p3)'
Note: une syntaxe équivalente est FIRST 2 (p2, p3)
.
La vue pg_stat_replication
contient désormais les
informations suivantes :
SELECT pid, application_name, sync_priority, sync_state
FROM pg_stat_replication
ORDER BY application_name;
pid | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
35534 | p2 | 1 | sync
35533 | p3 | 2 | sync
(2 rows)
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.
SELECT pid, application_name, sync_priority, sync_state
FROM pg_stat_replication ORDER BY application_name;
pid | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
35534 | p2 | 1 | potential
35533 | p3 | 1 | sync
(2 rows)
Si l’on arrête p2 et p3 et que l’on redémarre p2 avant p3, on obtient :
pid | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
35571 | p2 | 1 | sync
35572 | p3 | 1 | potential
(2 rows)
- Mettre en place la réplication synchrone par quorum avec un serveur synchrone minimum.
La configuration a été mise à jour et prise en compte par un
pg_reload_conf()
:
grep -E "^synchronous_standby_names" /etc/postgresql/17/main/postgresql.conf
synchronous_standby_names = 'ANY 1 (p2, p3)'
Dans pg_stat_replication
, on observe que le statut des
deux instances est quorum
et que leur priorité est
identique :
pid | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
35571 | p2 | 1 | quorum
35572 | p3 | 1 | quorum
Dans cet état, après un commit, la réplication synchrone rend la main dès que les données sont répliquées sur n’importe lequel des deux serveurs.
- Désactiver la réplication synchrone.
Modifier le paramètre synchronous_standby_names
sur
p1 :
grep -E "^synchronous_standby_names" /etc/postgresql/17/main/postgresql.conf
synchronous_standby_names = ''
Recharger la configuration.
SELECT pg_reload_conf();
Contrôler le contenu de la vue pg_stat_replication
:
pid | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
17411 | p2 | 0 | async
17412 | p3 | 0 | async
(2 rows)
- 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.
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.
sudo install -o postgres -g postgres -m 0755 -d /var/log/pgbackrest
sudo install -o postgres -g postgres -m 0755 -d /var/lib/pgbackrest
sudo chown postgres:postgres /etc/pgbackrest.conf
cat << _EOF_ > /etc/pgbackrest.conf
[global]
repo1-path=/var/lib/pgbackrest
repo1-retention-full=2
log-level-console=info
log-level-file=detail
[main]
pg1-host=p1
pg1-path=/var/lib/postgresql/17/main
pg2-host=p2
pg2-path=/var/lib/postgresql/17/main
pg3-host=p3
pg3-path=/var/lib/postgresql/17/main
_EOF_
Note: toutes les instances doivent exister et être accessible pour
que la commande stanza-create
fonctionne.
- Configurer pgBackRest sur les serveurs de base de données.
Sur chaque instance :
sudo install -o postgres -g postgres -m 0755 -d /var/log/pgbackrest
sudo chown postgres /etc/pgbackrest.conf
cat << _EOF_ > /etc/pgbackrest.conf
[global]
repo1-host=b1
repo1-host-user=postgres
repo1-path=/var/lib/pgbackrest
repo1-retention-full=2
log-path=/var/log/pgbackrest
log-level-console=info
log-level-file=detail
[main]
pg1-path=/var/lib/postgresql/17/main
_EOF_
Tester la connexion entre b1 et p1, p2, p3 et inversement.
Depuis p1, p2, p3 :
ssh -o "StrictHostKeyChecking=no" b1 hostname
Depuis b1 :
for i in $(seq 1 3); do ssh -o "StrictHostKeyChecking=no" p$i hostname; done
La configuration de PostgreSQL doit aussi être mise à jour :
cat << _EOF_ >> /etc/postgresql/17/main/postgresql.conf
archive_mode = 'on'
archive_command = 'pgbackrest --stanza=main archive-push %p'
restore_command = 'pgbackrest --stanza=main archive-get %f "%p"'
_EOF_
L’activation de l’archivage nécessitant encore un redémarrage en version 17 :
sudo systemctl restart postgresql@17-main
- Créer la stanza, tester l’archivage.
pgbackrest --stanza=main stanza-create
[…] 2025-01-14 17:47:18.459 P00 INFO: stanza-create command begin 2.54.1: --exec-id=129738-030db425 --log-level-console=info --log-level-file=detail --pg1-host=p1 --pg2-host=p2 --pg3-host=p3 --pg1-path=/var/lib/postgresql/17/main --pg2-path=/var/lib/postgresql/17/main --pg3-path=/var/lib/postgresql/17/main --repo1-path=/var/lib/pgbackrest --stanza=main
[…] 2025-01-14 17:47:19.785 P00 INFO: stanza-create for stanza 'main' on repo1
[…] 2025-01-14 17:47:20.154 P00 INFO: stanza-create command end: completed successfully (1697ms)
pgbackrest --stanza=main check
[…] INFO: check command begin 2.54.1: --exec-id=129755-e8809f38 --log-level-console=info --log-level-file=detail --pg1-host=p1 --pg2-host=p2 --pg3-host=p3 --pg1-path=/var/lib/postgresql/17/main --pg2-path=/var/lib/postgresql/17/main --pg3-path=/var/lib/postgresql/17/main --repo1-path=/var/lib/pgbackrest --stanza=main
[…] INFO: check repo1 (standby)
[…] INFO: switch wal not performed because this is a standby
[…] INFO: check repo1 configuration (primary)
[…] INFO: check repo1 archive for WAL (primary)
[…] INFO: WAL segment 000000030000000000000012 successfully archived to '/var/lib/pgbackrest/archive/main/17-1/0000000300000000/000000030000000000084b5808df7b2f49aa.gz' on repo1
[…] INFO: check command end: completed successfully (2793ms)
- Lancer une sauvegarde.
pgbackrest --stanza=main backup --type=full --start-fast
[…] INFO: backup command begin 2.54.1: --exec-id=129861-e77d17ba --log-level-console=info --log-level-file=detail --pg1-host=p1 --pg2-host=p2 --pg3-host=p3 --pg1-path=/var/lib/postgresql/17/main --pg2-path=/var/lib/postgresql/17/main --pg3-path=/var/lib/postgresql/17/main --repo1-path=/var/lib/pgbackrest --repo1-retention-full=2 --stanza=main --start-fast --type=full
[…] INFO: execute non-exclusive backup start: backup begins after the requested immediate checkpoint completes
[…] INFO: backup start archive = 000000030000000000000014, lsn = 0/14000028
[…] INFO: check archive for prior segment 000000030000000000000013
[…] INFO: execute non-exclusive backup stop and wait for all WAL segments to archive
[…] INFO: backup stop archive = 000000030000000000000014, lsn = 0/14000120
[…] INFO: check archive for segment(s) 000000030000000000000014:000000030000000000000014
[…] INFO: new backup label = 20250114-174941F
[…] INFO: full backup size = 22MB, file total = 966
[…] INFO: backup command end: completed successfully (16698ms)
[…] INFO: expire command begin 2.54.1: --exec-id=129861-e77d17ba --log-level-console=info --log-level-file=detail --repo1-path=/var/lib/pgbackrest --repo1-retention-full=2 --stanza=main
INFO: expire command end: completed successfully (202ms)
pgbackrest --stanza=main info
stanza: main
status: ok
cipher: none
db (current)
wal archive min/max (17): 000000010000000000000002/000000030000000000000014
full backup: 20250114-174941F
timestamp start/stop: 2025-01-14 17:49:41+01 / 2025-01-14 17:49:55+01
wal start/stop: 000000030000000000000014 / 000000030000000000000014
database size: 22MB, database backup size: 22MB
repo1: backup set size: 2.9MB, backup size: 2.9MB
- Si p3 existe, le stopper et détruire le répertoire de données.
sudo systemctl stop postgresql@17-main
rm -Rf /var/lib/postgresql/17/main
rm -Rf /var/log/postgresql/postgresql-17-main.log
- Créer l’instance à partir de la sauvegarde et s’assure qu’elle est bien en réplication.
pgbackrest --stanza=main restore \
--delta --type=standby \
--recovery-option=primary_conninfo="user=repli host=10.0.0.21 port=5432 application_name=p3"
[…] 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éé :
file ~/17/main/standby.signal
/var/lib/postgresql/17/main/standby.signal: empty
La configuration a bien été mise à jour :
cat ~/17/main/postgresql.auto.conf
# 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 :
sudo systemctl start postgresql@17-main
L’instance est bien démarrée et la réplication fonctionne :
ps -u postgres -o cmd
CMD
/usr/lib/postgresql/17/bin/postgres -D /var/lib/postgresql/17/main -c config_file=/etc/postgresql/17/main/postgresql.conf
postgres: 17/main p3: checkpointer
postgres: 17/main p3: background writer
postgres: 17/main p3: startup recovering 000000030000000000000015
postgres: 17/main p3: walreceiver streaming 0/15000168
- Interdire la connexion à p1 depuis p3 dans le
pg_hba.conf
, générer de l’activité, que se passe-t’il sur p3 ?
Ajouter une ligne reject
au pg_hba.conf
avant les lignes existantes (ici ligne 118) sur
p1 :
grep -nE "10\.0\.0\.23" /etc/postgresql/17/main/pg_hba.conf
118:host replication repli 10.0.0.23/32 reject
135:host replication repli 10.0.0.23/32 scram-sha-256
Recharger la configuration sur p1.
SELECT pg_reload_conf();
Redémarrer p3, cela permet de ré-initialiser la connexion de réplication.
sudo systemctl restart postgresql@17-main
On voit que la réplication a décroché.
ps -u postgres -o cmd | grep "17/main"
/usr/lib/postgresql/17/bin/postgres -D /var/lib/postgresql/17/main -c config_file=/etc/postgresql/17/main/postgresql.conf
postgres: 17/main p3: checkpointer
postgres: 17/main p3: background writer
postgres: 17/main p3: startup recovering 000000030000000000000015
Générer de l’activité sur p1 :
createdb pgbench
pgbench -i -s10 pgbench
On observe que l’instance a continué de consommer les WAL en provenance de p1 via le dépôt de sauvegarde de pgBackRest.
ps -u postgres -o cmd | grep "17/main"
/usr/lib/postgresql/17/bin/postgres -D /var/lib/postgresql/17/main -c config_file=/etc/postgresql/17/main/postgresql.conf
postgres: 17/main p3: checkpointer
postgres: 17/main p3: background writer
postgres: 17/main p3: startup waiting for 00000003000000000000001A
- Faire marche arrière sur la modification, que se passe-t-il sur p3 ?
Retire la ligne suivante au pg_hba.conf
avant les lignes
existantes :
grep -nE "10\.0\.0\.23" /etc/postgresql/17/main/pg_hba.conf
134:host replication repli 10.0.0.23/32 scram-sha-256
Recharger la configuration sur p1.
SELECT pg_reload_conf();
La connexion de réplication est de nouveau en place.
ps -u postgres -o cmd | grep "17/main"
/usr/lib/postgresql/17/bin/postgres -D /var/lib/postgresql/17/main -c config_file=/etc/postgresql/17/main/postgresql.conf
postgres: 17/main p3: checkpointer
postgres: 17/main p3: background writer
postgres: 17/main p3: startup recovering 00000003000000000000001B
postgres: 17/main p3: walreceiver streaming 0/1B002430
Dans les traces, on voit que le log shipping a été abandonné au profil de la réplication :
grep -A20 -B1 -E "started streaming WAL" /var/log/postgresql/postgresql-17-main.log
INFO: archive-get command end: completed successfully (436ms)
LOG: started streaming WAL from primary at 0/1A000000 on timeline 3
- Promouvoir l’instance p2. Générer de l’activité sur p1.
Promouvoir l’instance p2 :
psql -c "SELECT pg_promote();"
Sur l’instance p1, exécuter pgbench
pour simuler que des écritures ont continué à arriver après la
promotion :
dropdb pgbench
createdb pgbench
pgbench -i -s10 pgbench
- Stopper p1 et configurer la réplication pour raccrocher p1 à p2.
Stopper p1 :
sudo systemctl stop postgresql@17-main
Créer un fichier standby.signal
:
touch /var/lib/postgresql/17/main/standby.signal
Vérifier que le paramètre primary_conninfo
est
correctement configuré :
grep -E "^primary_conninfo" /var/lib/postgresql/17/main/postgresql.auto.conf
primary_conninfo = 'user=repli host=10.0.0.22 port=5432 application_name=p1'
Démarrer l’instance :
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.
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 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 checkpoints, ce qui inclus des hint nécessaires à l’utilisation de pg_rewind :
/usr/lib/postgresql/17/bin/pg_controldata /var/lib/postgresql/17/main \
| grep "Data page checksum version"
Data page checksum version: 1
Un autre pré-requis est que l’instance a été arrêtée proprement, chose que nous avons fait précédemment.
Créer un utilisateur dédié à pg_rewind sur p2 :
CREATE USER rewind_user LOGIN;
GRANT EXECUTE ON function pg_catalog.pg_ls_dir(text, boolean, boolean) TO rewind_user;
GRANT EXECUTE ON function pg_catalog.pg_stat_file(text, boolean) TO rewind_user;
GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text) TO rewind_user;
GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean) TO rewind_user;
password rewind_user \
Ajouter le mot de passe du nouvel utilisateur aux fichiers
.pgpass
de tous les serveurs :
echo "*:*:*:rewind_user:rewind_password" >> ~/.pgpass
Mettre a jour le pg_hba
des serveurs (ici pour le
serveur p2) :
grep rewind_user /etc/postgresql/17/main/pg_hba.conf
host postgres rewind_user 10.0.0.21/32 scram-sha-256
host postgres rewind_user 10.0.0.23/32 scram-sha-256
Recharger la configuration sur p2 :
psql -c "SELECT pg_reload_conf();"
pg_reload_conf
----------------
t
(1 row)
Nous pouvons désormais lancer pg_rewind
(ici en mode à
blanc) :
/usr/lib/postgresql/17/bin/pg_rewind \
--source-server="host=10.0.0.22 port=5432 user=rewind_user dbname=postgres" \
--target-pgdata=/var/lib/postgresql/17/main \
--progress \
--write-recovery-conf \
--dry-run
Les traces nous montrent les deux serveurs ont divergé au lsn
0/21BE14E8
.
pg_rewind: connected to server
pg_rewind: servers diverged at WAL location 0/21BE14E8 on timeline 3
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.
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.
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 généré.
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
:
file /var/lib/postgresql/17/main/standby.signal
/var/lib/postgresql/17/main/standby.signal: empty
mettre à jour la configuration de la réplication :
cat /var/lib/postgresql/17/main/postgresql.auto.conf
# 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 :
primary_conninfo = 'user=repli host=10.0.0.22 port=5432 application_name=p1'
L’instance peut ensuite être démarrée :
sudo systemctl start postgresql@17-main
L’instance est bien démarrée en tant que secondaire et réplique :
ps -u postgres -o cmd | grep "17/main"
/usr/lib/postgresql/17/bin/postgres -D /var/lib/postgresql/17/main -c config_file=/etc/postgresql/17/main/postgresql.conf
postgres: 17/main p1: checkpointer
postgres: 17/main p1: background writer
postgres: 17/main p1: startup recovering 000000040000000000000021
postgres: 17/main p1: walreceiver streaming 0/21BF0EE8
La seconde instance secondaire a elle aussi décroché, à vous de jouer !