PostgreSQL offre la possibilité d'effectuer des sauvegardes incrémentielles et une restauration ponctuelle prête à l'emploi. Lisez la suite pour en savoir plus sur les paramètres et les procédures pour y parvenir.
Ça commence avec les fichiers WAL
WAL signifie Write Ahead Log . Les WAL sont utilisés dans presque tous les systèmes RDBMS modernes pour fournir des transactions durables et atomiques.
Les modifications apportées aux données contenues dans un cluster de bases de données PostgreSQL géré par un processus serveur PostgreSQL unique ne sont possibles que via des transactions. Les modifications apportées aux données par les transactions sont enregistrées sous la forme d'une séquence ordonnée d'enregistrements WAL . Ces enregistrements sont écrits dans des fichiers de longueur fixe appelés fichiers de segment WAL , ou simplement des fichiers WAL .
Les fichiers WAL vivent dans $PGDATA/pg_wal
, où $PGDATA
est le répertoire de données du cluster de bases de données. Sur une installation Debian par défaut par exemple, le répertoire de fichiers WAL pour le cluster principal est /var/lib/postgresql/10/main/pg_wal
. Voici à quoi cela ressemble :
# pwd
/var/lib/postgresql/10/main/pg_wal
# ls -l
total 278532
-rw------- 1 postgres postgres 16777216 May 7 08:48 00000001000000000000000B
-rw------- 1 postgres postgres 16777216 May 7 10:08 00000001000000000000000C
-rw------- 1 postgres postgres 16777216 May 7 10:08 00000001000000000000000D
-rw------- 1 postgres postgres 16777216 May 7 10:08 00000001000000000000000E
-rw------- 1 postgres postgres 16777216 May 7 10:08 00000001000000000000000F
-rw------- 1 postgres postgres 16777216 May 7 10:08 000000010000000000000010
-rw------- 1 postgres postgres 16777216 May 7 10:08 000000010000000000000011
-rw------- 1 postgres postgres 16777216 May 7 10:08 000000010000000000000012
-rw------- 1 postgres postgres 16777216 May 7 10:08 000000010000000000000013
-rw------- 1 postgres postgres 16777216 May 7 10:08 000000010000000000000014
-rw------- 1 postgres postgres 16777216 May 7 10:08 000000010000000000000015
-rw------- 1 postgres postgres 16777216 May 7 10:08 000000010000000000000016
-rw------- 1 postgres postgres 16777216 May 7 10:08 000000010000000000000017
-rw------- 1 postgres postgres 16777216 May 16 20:52 000000010000000000000018
-rw------- 1 postgres postgres 16777216 May 16 20:56 000000010000000000000019
-rw------- 1 postgres postgres 16777216 May 26 08:52 00000001000000000000001A
-rw------- 1 postgres postgres 16777216 Jun 2 09:59 00000001000000000000001B
drwx------ 2 postgres postgres 4096 Mar 30 10:06 archive_status
Les fichiers WAL sont générés de manière incrémentielle, en séquence, à partir de la création du cluster. Ils continuent d'être générés tant que des modifications sont apportées au cluster. Le mécanisme de fichier WAL est essentiel au fonctionnement de PostgreSQL et ne peut pas être désactivé.
Une fois que les modifications ont d'abord été écrites sous forme d'enregistrements WAL, elles doivent être appliquées à la représentation sur disque des données elles-mêmes. Ce processus est appelépoint de contrôle , et se produit automatiquement en arrière-plan (il peut également être forcé manuellement). Le point jusqu'au point de contrôle est appelé le point REDO . Les points de contrôle sont également une partie essentielle de l'architecture Postgres et ne peuvent pas être désactivés.
Rétention de fichiers WAL
Dans le cours normal du fonctionnement du serveur PostgreSQL, les fichiers WAL continueront d'être écrits dans le pg_wal
annuaire. Mais pourquoi les avoir autour ?
L'une des raisons est la récupération après un crash. Si le serveur PostgreSQL plante et redémarre, il commence à appliquer les modifications des enregistrements WAL dans les fichiers de données (points de contrôle) depuis le dernier point REDO. Cela garantit que les fichiers de données sont cohérents avec la dernière transaction effectuée.
Une autre raison est liée à la réplication en continu. La réplication en continu fonctionne en envoyant les enregistrements WAL en veille serveurs, qui les stockent localement et effectuent des points de contrôle. Les serveurs de secours peuvent être en retard sur le serveur à partir duquel ils répliquent (appelé le serveur principal ). Par exemple, si le primaire a généré 100 enregistrements WAL et que le standby a reçu et appliqué les 80 premiers, les 20 plus récents doivent être disponibles pour que le standby puisse recevoir et appliquer à partir de l'enregistrement 81.
Mais les très anciens fichiers WAL peuvent sûrement être supprimés ? Oui. PostgreSQL peut être chargé de conserver les fichiers WAL les plus récents et de supprimer les plus anciens. Il existe trois options de configuration pertinentes :
- wal_keep_segments - définit le nombre minimum de fichiers WAL les plus récents à conserver dans le répertoire de fichiers WAL
- max_wal_size - spécifie la taille totale maximale des fichiers WAL dans le répertoire de fichiers WAL. Si celle-ci est dépassée, les plus anciennes sont supprimées. Cependant, il peut y avoir des raisons (y compris une valeur élevée pour
wal_keep_segments
) qui peut empêcher le respect de ce paramètre. - min_wal_size - spécifie une taille totale minimale pour les fichiers WAL. Tant que la taille réelle reste inférieure à cette valeur, aucun fichier ne sera supprimé.
Dans la vraie vie, il n'est pas possible, ni obligatoire, de stocker tous les fichiers WAL précédents sous le pg_wal
répertoire.
Archivage de fichiers WAL
La valeur réelle des fichiers WAL est qu'ils sont un flux de modifications qui peuvent être enregistrées et rejouées pour obtenir une réplique cohérente d'un cluster PostgreSQL. PostgreSQL fournit un moyen par lequel nous pouvons copier (ou « archiver ») chaque fichier WAL après son obtention créé - la archive_command option de configuration.
Cette option spécifie une chaîne de commande shell qui est invoquée après la création de chaque fichier WAL. Voici quelques exemples :
# Copy the file to a safe location (like a mounted NFS volume)
archive_command = 'cp %p /mnt/nfs/%f'
# Not overwriting files is a good practice
archive_command = 'test ! -f /mnt/nfs/%f && cp %p /mnt/nfs/%f'
# Copy to S3 bucket
archive_command = 's3cmd put %p s3://BUCKET/path/%f'
# Copy to Google Cloud bucket
archive_command = 'gsutil cp %p gs://BUCKET/path/%f'
# An external script
archive_command = '/opt/scripts/archive_wal %p'
Il existe également 2 autres options, qui doivent être définies :
# this must be "on" to enable WAL archiving
archive_mode = on
# has to be "replica" (default) or "logical" for WAL archiving
wal_level = replica
Compression WAL
Vous pouvez compresser les fichiers WAL avant de les copier dans un emplacement de stockage à long terme/sûr. Cependant, il existe une option appelée wal_compression . Activer cela forcera PostgreSQL à compresser les enregistrements WAL individuels dans les fichiers WAL. Les fichiers WAL eux-mêmes auront la même taille (généralement 16 Mo), mais contiendront une séquence d'enregistrements compressés plutôt que des enregistrements simples.
Archivage continu
L'archivage WAL est aussi appelé archivage continu et est en vigueur,sauvegarde incrémentielle .
Avant de commencer ce processus de sauvegarde incrémentielle, une sauvegarde complète est requise. Cela établit une ligne de base sur laquelle les fichiers WAL peuvent être restaurés de manière incrémentielle. Une sauvegarde complète peut être effectuée soit par :
- arrêter le processus du serveur Postgres et copier le répertoire de données du cluster (tout en préservant les autorisations), ou
- en utilisant
pg_basebackup
sur un serveur Postgres en cours d'exécution.
Récupération ponctuelle (PITR)
PITR fait référence à la capacité de PostgreSQL à démarrer à partir de la restauration d'une sauvegarde complète, puis à récupérer et à appliquer progressivement les fichiers WAL archivés jusqu'à un horodatage spécifié.
Pour ce faire, nous devons créer un fichier appelé "recovery.conf" dans le répertoire de données du cluster restauré et démarrer un serveur Postgres pour ce répertoire de données. Le fichier recovery.conf contient l'horodatage cible et ressemble à ceci :
restore_command = 'cp /tmp/demo/archive/%f "%p"'
recovery_target_time = '2019-06-04 14:10:00'
La restore_command spécifie comment récupérer un fichier WAL requis par PostgreSQL. C'est l'inverse de archive_command. Le recovery_target_time spécifie le moment jusqu'au moment où nous avons besoin des modifications.
Lorsqu'un processus serveur PostgreSQL démarre et découvre un recovery.conf filedans le répertoire de données, il démarre dans un mode spécial appelé "mode de récupération". En mode de récupération, les connexions client sont refusées. Postgres récupère les fichiers WAL et les applique jusqu'à ce que la cible de récupération (dans ce cas, les modifications jusqu'à l'horodatage spécifié) soit atteinte. Lorsque l'objectif est atteint, le serveur met par défaut en pause la relecture des WAL (d'autres actions sont possibles). À ce stade, vous êtes censé examiner l'état de la restauration et si tout semble correct, réactivez la pause pour quitter le mode de récupération et poursuivre le fonctionnement normal.
Tout mettre ensemble
Tout cela était un tas de théorie et de texte, essayons-le pour voir comment tout cela fonctionne dans la pratique.
Commençons par initialiser un nouveau cluster :
/tmp/demo$ pg_ctl -D clus1 initdb
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.
The database cluster will be initialized with locale "C.UTF-8".
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".
Data page checksums are disabled.
creating directory clus1 ... ok
creating subdirectories ... ok
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting dynamic shared memory implementation ... posix
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok
WARNING: enabling "trust" authentication for local connections
You can change this by editing pg_hba.conf or using the option -A, or
--auth-local and --auth-host, the next time you run initdb.
Success. You can now start the database server using:
/usr/lib/postgresql/10/bin/pg_ctl -D clus1 -l logfile start
Nous allons également créer un répertoire qui servira d'emplacement de stockage sécurisé. Appelons cela "archive".
/tmp/demo$ mkdir archive
/tmp/demo$ ls -l
total 8
drwxr-xr-x 2 postgres postgres 4096 Jun 4 14:02 archive
drwx------ 19 postgres postgres 4096 Jun 4 14:02 clus1
Nous devons configurer les paramètres d'archivage dont nous avons parlé précédemment, avant de pouvoir démarrer le serveur. Ajoutons donc ce qui suit à la fin de clus1/postgres.conf
:
port = 6000
wal_level = logical
archive_mode = on
archive_command = 'cp %p /tmp/demo/archive/%f'
archive_timeout = 60
Notre commande archive copie simplement le fichier WAL dans le répertoire d'archive que nous avons créé précédemment.
Nous avons également ajouté le archive_timeout paramètre. Habituellement, un fichier WAL est créé uniquement lorsqu'il y a suffisamment d'enregistrements WAL pour remplir un fichier WAL de 16 Mo. Cela signifie que pour les serveurs avec peu d'écritures, vous devrez peut-être attendre longtemps avant qu'un fichier WAL soit créé. Le paramètre archive_timeout indique à Postgres qu'il doit créer un fichier WAL toutes les secondes, qu'il soit plein ou non.
Ici, nous l'avons réglé sur 60 (secondes), mais ce n'est que pour la démo ! Vous ne voudriez généralement jamais le garder aussi bas.
Faisons également une copie de "clus1". C'est l'équivalent d'une sauvegarde complète.
/tmp/demo$ cp -Rp clus1 clus2
/tmp/demo$ ls -l
total 12
drwxr-xr-x 2 postgres postgres 4096 Jun 4 14:02 archive
drwx------ 19 postgres postgres 4096 Jun 4 14:03 clus1
drwx------ 19 postgres postgres 4096 Jun 4 14:03 clus2
Nous pouvons maintenant démarrer le cluster :
/tmp/demo$ pg_ctl -D clus1 -l log1 start
waiting for server to start.... done
server started
Ajoutons quelques données.
/tmp/demo$ psql -h /var/run/postgresql -p 6000 postgres
psql (10.8 (Ubuntu 10.8-0ubuntu0.18.04.1))
Type "help" for help.
postgres=# create database demo;
CREATE DATABASE
postgres=# \c demo
You are now connected to database "demo" as user "postgres".
demo=# create table tbl1 (col1 int);
CREATE TABLE
demo=# insert into tbl1 (col1) select generate_series(1, 10000);
INSERT 0 10000
demo=# select count(*) from tbl1;
count
-------
10000
(1 row)
demo=# select now();
now
-------------------------------
2019-06-04 14:05:05.657871+00
(1 row)
demo=# \q
Notez qu'il est maintenant 14h05. Vérifions si notre commande archive fonctionne :
/tmp/demo$ ls -l archive/
total 16384
-rw------- 1 postgres postgres 16777216 Jun 4 14:04 000000010000000000000001
Oui, nous avons un seul fichier d'archive. Notre dernier changement était à 14h05, attendons maintenant quelques minutes puis apportons d'autres modifications.
/tmp/demo$ psql -h /var/run/postgresql -p 6000 demo
psql (10.8 (Ubuntu 10.8-0ubuntu0.18.04.1))
Type "help" for help.
demo=# select now();
now
-------------------------------
2019-06-04 14:16:06.093859+00
(1 row)
demo=# select count(*) from tbl1;
count
-------
10000
(1 row)
demo=# insert into tbl1 (col1) select generate_series(1, 100);
INSERT 0 100
demo=# select count(*) from tbl1;
count
-------
10100
(1 row)
demo=# \q
Alors maintenant, nous avons ajouté 100 lignes supplémentaires, à 14:16. Arrêtons le serveur :
/tmp/demo$ pg_ctl -D clus1 stop
waiting for server to shut down.... done
server stopped
/tmp/demo$
et consultez à nouveau nos archives :
/tmp/demo$ ls -l archive/
total 65536
-rw------- 1 postgres postgres 16777216 Jun 4 14:04 000000010000000000000001
-rw------- 1 postgres postgres 16777216 Jun 4 14:05 000000010000000000000002
-rw------- 1 postgres postgres 16777216 Jun 4 14:09 000000010000000000000003
-rw------- 1 postgres postgres 16777216 Jun 4 14:16 000000010000000000000004
Cela semble bon. Nous allons maintenant essayer de faire une récupération PITR de clus2 jusqu'à l'heure 14:10.
Modifions d'abord le fichier postgres.conf de clus2 et ajoutons ces lignes à la fin :
port = 6001
archive_mode = off
Afin de rejouer les fichiers WAL, nous devons mettre le serveur PostgreSQL pour clus2 (que nous n'avons pas encore démarré) en mode de récupération. Pour cela, créez le fichier nommé "recovery.conf" dans clus2 :
/tmp/demo$ cat clus2/recovery.conf
restore_command = 'cp /tmp/demo/archive/%f "%p"'
recovery_target_time = '2019-06-04 14:10:00'
Celui-ci contient la restore_command qui fait le contraire de la précédentearchive_command , à savoir copier le fichier demandé du répertoire archive vers le répertoire pg_wal.
Nous avons également défini le recovery_target_time à 14h10.
Maintenant, nous commençons clus2 :
/tmp/demo$ pg_ctl -D clus2 -l log2 start
waiting for server to start.... done
server started
Pour voir ce qui s'est passé, examinons le fichier journal :
/tmp/demo$ cat log2
2019-06-04 14:19:10.862 UTC [10513] LOG: listening on IPv4 address "127.0.0.1", port 6001
2019-06-04 14:19:10.864 UTC [10513] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.6001"
2019-06-04 14:19:10.883 UTC [10514] LOG: database system was shut down at 2019-06-04 14:02:31 UTC
2019-06-04 14:19:10.883 UTC [10514] LOG: starting point-in-time recovery to 2019-06-04 14:10:00+00
2019-06-04 14:19:10.903 UTC [10514] LOG: restored log file "000000010000000000000001" from archive
2019-06-04 14:19:10.930 UTC [10514] LOG: consistent recovery state reached at 0/16383E8
2019-06-04 14:19:10.930 UTC [10514] LOG: redo starts at 0/16383E8
2019-06-04 14:19:10.931 UTC [10513] LOG: database system is ready to accept read only connections
2019-06-04 14:19:11.037 UTC [10514] LOG: restored log file "000000010000000000000002" from archive
2019-06-04 14:19:11.079 UTC [10514] LOG: restored log file "000000010000000000000003" from archive
2019-06-04 14:19:11.122 UTC [10514] LOG: restored log file "000000010000000000000004" from archive
2019-06-04 14:19:11.141 UTC [10514] LOG: recovery stopping before commit of transaction 559, time 2019-06-04 14:16:24.875517+00
2019-06-04 14:19:11.141 UTC [10514] LOG: recovery has paused
2019-06-04 14:19:11.141 UTC [10514] HINT: Execute pg_wal_replay_resume() to continue.
La récupération a été rapide (dans la vraie vie, cela peut prendre des heures ou des jours) et le journal indique qu'il s'est arrêté avant une transaction particulière (qui a un horodatage> 14:10). Il indique également que la récupération est en pause et doit être poursuivie manuellement.
Examinons les données :
/tmp/demo$ psql -h /var/run/postgresql -p 6001 demo
psql (10.8 (Ubuntu 10.8-0ubuntu0.18.04.1))
Type "help" for help.
demo=# select count(*) from tbl1;
count
-------
10000
(1 row)
Nous voyons qu'il n'y a que 10000 lignes. À 14h16, nous en avions ajouté 100 de plus, qui n'apparaissaient pas dans le tableau.
Ça a l'air bien, alors reprenons :
demo=# select pg_wal_replay_resume();
pg_wal_replay_resume
----------------------
(1 row)
Le fichier journal signale maintenant que la récupération est terminée et que les opérations normales sont restaurées :
2019-06-04 14:20:26.219 UTC [10514] LOG: redo done at 0/4002160
2019-06-04 14:20:26.219 UTC [10514] LOG: last completed transaction was at log time 2019-06-04 14:05:28.813325+00
cp: cannot stat '/tmp/demo/archive/00000002.history': No such file or directory
2019-06-04 14:20:26.228 UTC [10514] LOG: selected new timeline ID: 2
2019-06-04 14:20:26.272 UTC [10514] LOG: archive recovery complete
cp: cannot stat '/tmp/demo/archive/00000001.history': No such file or directory
2019-06-04 14:20:26.388 UTC [10513] LOG: database system is ready to accept connections
Et nous avons réussi à récupérer le cluster jusqu'à une heure spécifiée !
Lectures complémentaires
Voici quelques points de départ pour en savoir plus sur l'archivage WAL, le recoverymode et le PITR :
- Documents :RecoveryConfiguration
- Docs :Archivage continu et PITR
- Chapitre 9 du livre « The Internals ofPostgreSQL »
- Outils :WAL-E, WAL-G, Barman