Sauvegardes physiques avec pg_basebackup

Nous allons dérouler le processus de sauvegarde avec pg_basebackup. Cette étape doit être bien maitrisée pour aborder la réplication.

Configuration de l'instance

Avant de manipuler pg_basebackup, il faut configurer notre instance :

  • Activation de l'archivage
  • Configuration des WAL
  • Configuration des checkpoints

Configurer l'instance avec les paramètres suivants :

  • Mode archive : activé
  • Niveau WAL : replica
  • Durée max entre les checkpoints : 1 minute
  • Commande d'archivage à définir pour stocker les WAL dans /opt/wal_archives

La commande d'archivage sera exécutée par PostgreSQL à chaque fois qu'un journal doit être archivé. PostgreSQL fournit deux paramètres qui peuvent être utilisés dans cette commande : %f pour le nom du fichier et %p pour le chemin complet vers le fichier.

Après avoir configuré ces paramètres, il faudrait redémarrer l'instance. Nous la redémarrerons au dernier moment pour éviter de générer trop de journaux.

Générer des données avec pgbench

Nous allons simuler une activité sur la base en utilisant l'outil pgbench. Cet outil exécute des transactions aléatoires en boucle pendant une durée donnée.

Il s'utilise en deux phases, une phase d'initialisation et une phase d'exécution.

Initialisation

  • Créer une base bench dédiée à pgbench
  • Exécuter la commande d'initialisation
pgbench -i -s 100 bench

Exécution

Attention : ne pas exécuter tout de suite

La commande à lancer pour générer des données sera :

pgbench bench -n -P 5 -T 360

Cette commande provoque une insertion de données en continu pendant 360 secondes.

Sauvegarde de l'instance avec pg_basebackup

pg_basebackup simplifie grandement la sauvegarde physique d'une instance en automatisant les appels à pg_start_backup et pg_stop_backup, la copie des fichiers etc.

Les options les plus importantes de pg_basebackup sont les suivantes :

-D répertoire de réception de la sauvegarde
-F p|t format de la sauvegarde (p pour plain, t pour tar)
-r taux de transfert maximum des fichiers (important pour éviter de saturer les I/O du serveur)
--checkpoint vitesse du checkpoint. 'fast' pour le réaliser le plus vite possible, 'spread' pour étaler la consommation d'I/O dans le temps
--progress afficher la progression

Avant de lancer la sauvegarde, nous devons créer le répertoire cible, faire le ménage et démarrer la génération de données :

  • Créer le répertoire /opt/base_backup et définir postgres comme son propriétaire
  • Lancer un vacuum
  • Faire un checkpoint
  • Lancer la génération de données avec pgbench

Une fois que la génération de données est bien en cours, nous pouvons faire la sauvegarde :

  • pg_basebackup -D /opt/base_backup -Fp --checkpoint=fast --progress -r 16M

Pendant la sauvegarde, on peut suivre la progression de la génération de données en regardant le contenu de la table pgbench_history dans la base bench, à intervalle régulier.

SELECT max(mtime) FROM pgbench_history;

Quand la sauvegarde est terminée :

  • Vérifier que les fichiers sont bien présents dans /opt/base_backup
  • Noter la dernière date (max(mtime)) de transaction dans pgbench_history
  • Remarquer la création du fichier backup_label dans le répertoire de sauvegarde, observer son contenu

Nous disposons maintenant :

  • D'une sauvegarde de l'instance dans /opt/base_backup
  • Les archives des WAL dans /opt/wal_archives

Restauration dans une nouvelle instance

Nous allons créer une nouvelle instance de base de données pour restaurer notre backup.

Ceci peut être fait facilement avec l'outil pg_createcluster :

mkdir /opt/postgres_restore
chown postgres:postgres /opt/postgres_restore
pg_createcluster -d /opt/postgres_restore -p 5433 13 postgres_restore

Le répertoire de données de cette nouvelle instance sera dans /opt/postgres-restore, sa configuration se trouve dans /etc/postgresql

Nous allons vider le répertoire de données de cette nouvelle instance pour le remplacer par notre backup.

rm -rf /opt/postgres_restore/*
cp -R /opt/base_backup /opt/postgres_restore

Il faut ensuite remplacer le fichier de configuration de la nouvelle instance, par celui de l'ancienne :

cp /opt/base_backup/postgresql.conf 
/etc/postgresql/13/postgres_restore/postgresql.conf

De l'importance des WAL

Avant de restaurer l'instance complètement, nous allons tenter de la démarrer sans aucun fichier WAL.

Il faut avant cela modifier la configuration de l'instance :

  • Modifier data_directory pour pointer vers le répertoire de la nouvelle instance
  • Désactiver l'archivage

Ensuite, supprimons tous les logs présents dans notre instance restaurée

rm -rf /opt/postgres_restore/pg_wal/*

Placer le marquer de restauration et démarrer l'instance :

pg_ctlcluster 13 postgres_restore start

Consulter les logs dans /var/log/postgresql/. Que s'est-il passé ?

Arrêter l'instance :

pg_ctlcluster 13 postgres_restore stop

Restauration avec les journaux

Pour restaurer correctement notre instance, nous avons besoin des journaux. Pour les obtenir, nous allons à nouveau modifier la configuration de l'instance. Ouvrir le fichier de configuration (dans /etc/postgresql/) :

  • Définir une restore_command

La commande spécifiée dans restore_command est exécutée par PostgreSQL pour récupérer les archives nécessaires à la restauration. Elle s'écrit avec les mêmes paramètres que archive_command

Nous pouvons maintenant placer le marqueur de restauration et démarrer l'instance :

touch /opt/postgres_restore/recovery.signal
pg_ctlcluster 13 postgres_restore start

Consulter les logs. L'instance devrait dérouler les journaux WAL et terminer par un message indiquant qu'elle est disponible pour les connexions.

2021-06-29 10:49:33.346 UTC [1251] LOG:  starting PostgreSQL 13.1 (Debian 13.1-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
2021-06-29 10:49:33.347 UTC [1251] LOG:  listening on IPv4 address "0.0.0.0", port 5433
2021-06-29 10:49:33.347 UTC [1251] LOG:  listening on IPv6 address "::", port 5433
2021-06-29 10:49:33.351 UTC [1251] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5433"
2021-06-29 10:49:33.360 UTC [1252] LOG:  database system was interrupted; last known up at 2021-06-29 09:27:53 UTC
2021-06-29 10:49:34.094 UTC [1252] LOG:  restored log file "00000002.history" from archive
cp: cannot stat '/opt/wal_archives/00000003.history': No such file or directory
2021-06-29 10:49:34.100 UTC [1252] LOG:  starting archive recovery
2021-06-29 10:49:34.106 UTC [1252] LOG:  restored log file "00000002.history" from archive
2021-06-29 10:49:34.149 UTC [1252] LOG:  restored log file "00000001000000010000003A" from archive
2021-06-29 10:49:34.188 UTC [1252] LOG:  redo starts at 1/3A000028
2021-06-29 10:49:34.190 UTC [1252] LOG:  consistent recovery state reached at 1/3A000138
2021-06-29 10:49:34.191 UTC [1251] LOG:  database system is ready to accept read only connections
2021-06-29 10:49:34.231 UTC [1252] LOG:  restored log file "00000002000000010000003B" from archive
2021-06-29 10:49:34.365 UTC [1252] LOG:  restored log file "00000002000000010000003C" from archive
2021-06-29 10:49:34.479 UTC [1252] LOG:  restored log file "00000002000000010000003D" from archive
2021-06-29 10:49:34.594 UTC [1252] LOG:  restored log file "00000002000000010000003E" from archive
2021-06-29 10:49:34.703 UTC [1252] LOG:  restored log file "00000002000000010000003F" from archive
2021-06-29 10:49:34.803 UTC [1252] LOG:  restored log file "000000020000000100000040" from archive
2021-06-29 10:49:34.895 UTC [1252] LOG:  restored log file "000000020000000100000041" from archive
2021-06-29 10:49:35.014 UTC [1252] LOG:  restored log file "000000020000000100000042" from archive
2021-06-29 10:49:35.121 UTC [1252] LOG:  restored log file "000000020000000100000043" from archive
cp: cannot stat '/opt/wal_archives/000000020000000100000044': No such file or directory
2021-06-29 10:49:35.191 UTC [1252] LOG:  redo done at 1/436FC6B8
2021-06-29 10:49:35.191 UTC [1252] LOG:  last completed transaction was at log time 2021-06-29 10:46:34.30952+00
2021-06-29 10:49:35.224 UTC [1252] LOG:  restored log file "000000020000000100000043" from archive
cp: cannot stat '/opt/wal_archives/00000003.history': No such file or directory
2021-06-29 10:49:35.264 UTC [1252] LOG:  selected new timeline ID: 3
2021-06-29 10:49:35.311 UTC [1252] LOG:  archive recovery complete
2021-06-29 10:49:35.315 UTC [1252] LOG:  restored log file "00000002.history" from archive
2021-06-29 10:49:40.755 UTC [1251] LOG:  database system is ready to accept connections

Restauration PITR

Pour terminer, nous allons simuler une opération malencontreuse en base de données et effectuer une restauration à une date donnée avec le mécanisme PITR.

  • Réaliser un checkpoint et noter l'heure (avec NOW())
  • Supprimer une table importante : DROP TABLE customer CASCADE;
  • Repartir de la sauvegarde précédente, et restaurer l'instance juste avant la suppression de la table

Pour ce faire, il faut modifier le paramètre recovery_target_time et indiquer la date souhaitée de restauration (format Y-M-d H:m:s.t)

Bonus :

Nous pouvons consulter le contenu d'un fichier WAL avec la commande /usr/lib/postgresql/13/bin/pg_waldump /opt/wal_archives/[nom d'un fichier WAL]

Grâce à cela, il est possible de récupérer l'identifiant de la transaction qui a provoqué la suppression de la table, et ainsi utiliser le paramètre recovery_target_xid pour spécifier jusqu'où nous souhaitons restaurer.