PostgreSQL + dbSQWare : sauvegarde et restauration Point-In-Time (1/2)

de | 2017-07-07

Introduction

Aujourd’hui, nous prenons notre courage à deux mains et allons nous attaquer aux mécanismes de sauvegardes PITR (« Point-In-Time Recovery », c’est à dire des sauvegardes pouvant ramener votre base à une date précise, à la seconde près) tel que cela se fait sous PostgreSQL. Comme nous ne faisons pas les choses à moitié, nous allons en profiter pour vous expliquer comment dbSQWare gère ce type de sauvegarde et même – sans supplément de prix – comment vous en servir pour faire renaître de ses cendres une base sinistrée.

Cela mérite bien un article en deux parties. Dans la première, après un rappel du fonctionnement des journaux de transactions PostgreSQL, nous allons montrer comment ils sont archivés. Puis nous sortirons du champ PostgreSQL pour vous expliquer comment dbSQWare va plus loin en archivant les archives (sic) afin d’en assurer la continuité.

Il est important de noter que dbSQWare se base sur des commandes Unix et PostgreSQL standards, donc qu’il ne fait rien que vous deviez savoir faire. Le code est ouvert, les journaux de traces sont verbeux… C’est une mine d’informations.

Assez tourné autour du pot (‘sgreSQL’) : au travail !

Les W.A.L (« Write Ahead Logs »)

Au début étaient les transactions…

"The Wall", le fim qui raconte comment le pg_writer lutte pour la célébrité et l'écriture sur disque

« The Wall », le fim qui raconte comment le pg_writer lutte pour une écriture sur disque (vous noterez la faute sur l’orthographe des W.A.L. Erreur surprenante de la part d’un perfectionniste comme Alan Parker)

Commençons par enfoncer une porte ouverte comme une évidence baillant sur le B.A.BA du débutant béotien : PostgreSQL gère les données suivant le modèle MVCC (« Multiversion Concurrency Control »), lequel permet des écritures ne bloquant pas les lectures. C’est un modèle utilisé dans d’autres moteurs (Oracle, Microsoft SQL Server, MySQL + innoDB…) dont l’implémentation peut varier.

Dans notre cas, la ligne est copiée avant d’être modifiée (« copy_on_write »). Le principal avantage de cette approche est d’éviter une modification de la ligne d’origine, donc d’économiser des accès disques. Mais c’est au prix d’une perte sur le stockage et donc d’une maintenance spécifique pour le récupérer. D’où le « vacuum » dont vous avez sans doute déjà entendu parler.

Pour assurer la consistance et l’atomicité des données, chaque session se voit attribuer un numéro de transaction courant et chaque table possède deux colonnes invisibles. La première (« xmin ») contient le numéro de transaction minimal pour voir la ligne. La seconde (« xmax »), le numéro maximum pour la voir.

Si le numéro de transaction de votre session est entre xmin et xmax, vous verrez la ligne, sinon non. Faites l’essai avec une requête sur une table, en demandant explicitement les colonnes cachées :

select xmin, xmax, * from table_de_test limit1\G;

Votre numéro de transaction courant est appelé « txid_current ». Vous pouvez le voir grâce à la requête :

select txid_current();

De son coté, PostgreSQL écrit les transactions dans un journal de transactions avant qu’elles ne prennent effet. D’où le nom « Write-Ahead Log » puisque c’est une écriture avant action. Chaque écriture est repérée grâce à un numéro de séquence, le LSN (« Log Sequence Number »). Ceux d’entre vous qui pratiquent Oracle peuvent l’assimiler au SCN. C’est le même usage.

 

… puis vinrent les fichiers

Le processus wal_writer écrit les journaux. Leur nombre est directement lié aux modifications des données. Avec MVCC, un méga-octet de données modifiées se traduit par un méga-octet de journal de transaction car il faut conserver l’image de la donnée avant modification. On a rien sans rien.

Pour une sauvegarde PITR, vous aurez besoin de tous les journaux écrits sur disque donc il faut que la base soit « archive_mode = on » et qu’une archive_command soit définie. Nous allons y revenir.


wal_level = archive                     # Si >= 9.6, il y a "replica"
archive_mode = on                       # Indispensable

"The Wall", le jeu télévisé dont reposant sur des paris à propos de la valeur de min_wal_size (notez la faute d'orthographe sur le nom "W.A.L", surprenante de la part d'une chaîne au professionnalisme inattaquable)

« The Wall », le jeu télévisé dont reposant sur des paris à propos de la valeur de min_wal_size (notez la faute d’orthographe sur le nom « W.A.L », surprenante de la part d’une chaîne au professionnalisme inattaquable)

Toujours au niveau configuration, les paramètres wal_min_size et wal_max_size déterminent un espace tampon pour le volume qu’ils peuvent représenter sur disque. Pour éviter qu’ils ne deviennent trop nombreux, PostgreSQL les recycle par effacement ou archivage via la archive_command, selon la configuration.

Intermède superfétatoire : si vous êtes d’un naturel curieux et que vous allez dans le sous-répertoire $PGDATA/pg_xlog/archive_status, vous verrez des fichiers de 0 Ko (de simples « touch ») portant le même nom que les xlogs et dont l’extension est « .done » (donc archivés) ou « .ready » (à archiver. Si vous en avez beaucoup, l’archive_command n’arrive pas à faire face et une analyse doit être faite).

Bref… « archive_command » offre une grande souplesse : ce peut être une commande shell (« cp xlog vers dossier_archive_xlog« ) ou un script maison.

Quelle coïncidence ! Il existe dans dbSQWare un script d’archivage près à l’emploi :

« archive_command = ‘$HOME/SQWareProduction/postgres/bin/sqwpg_ArchiveXlog.ksh -f %p -I CLUSTER_NAME’
« %p » est une variable désignant le chemin complet vers le fichier à archiver depuis pg_xlog. Cela signifie que la commande s’appliquera à chaque fichier trouvé.

Une commande minimum serait :

archive_command = 'cp %p /répertoire/de/stockage/%f'

Comme le précise la doc, il faut que la commande sorte avec un code retour de « 0 ».

Nous avons donc le chemin suivant :

Changement -> pg_xlogs -> archive_xlogs

 

Recette pour un bon P.I.T.R traditionnel

Pour la PITR, il faut que les fichiers dans archive_xlog soient conservés aussi longtemps que l’exige votre politique de rétention. La solution facile est de tout laisser dans un répertoire avec suffisamment d’espace et de supprimer les fichiers plus anciens qu’une limite que vous aurez fixé.

dbSQWare, lui, permet de configurer une rétention par génération  (une ou plusieurs sauvegardes complètes) ou par période. Il ne conserve que les archive_logs nécessaires à une restauration.

Il a besoin qu’un script soit présent : sqwArchmon. A la manière de son homologue sur Oracle, il surveille le remplissage du dossier archive_logs et déplace les fichiers vers son répertoire de gestion de sauvegardes. Ce peut être selon un pourcentage de remplissage du disque ou d’un temps d’intervalle maximum ou les deux.

Nous avons donc maintenant le chemin suivant :

Changement -> pg_xlogs -> archive_xlogs -> $gvsqw_RootPitr

« gvsqw_RootPitr » : le répertoire final de la PITR, directement accessible par l’alias shell « pitr ».

Avant d’aller plus avant dans le détail, un résumé des opérations qui permettent à PosgreSQL de faire une sauvegarde cohérente et une restauration Point-In-Time, après laquelle seules les transactions non-validées (« commitées », disent les indécis linguistes).

– Signaler au postmaster qu’un backup est en cours :

psql -c "SELECT pg_start_backup('Starting Base Backup for PITR', true)"

– copier à chaud (base ouverte) le dossier $PGDATA vers son dossier de sauvegarde (dbSQWare le compresse aussi mais ce n’est pas obligatoire. C’est configurable et dépend de la puissance de votre machine. Si vous manquez de CPU mais que vous avez beaucoup de disque, ça vaut la peine de copier tel quel).

– Marquer la fin du backup :

psql -c "SELECT pg_stop_backup()"

Et c’est tout. Ce procédé sert souvent, par exemple avec pg_basebackup.

« Start / stop backup » : crée un fichier contenant les informations de début et de fin de la sauvegarde. Comme nous vous savons curieux comme des petits chatons remplis de malice devant les merveilles du monde, voici de qu’il contient :

START WAL LOCATION: EFB/65000028 (file 0000000100000EFB00000065)
STOP WAL LOCATION: EFB/670000B8 (file 0000000100000EFB00000067)
CHECKPOINT LOCATION: EFB/65000028
BACKUP METHOD: pg_start_backup
BACKUP FROM: master
START TIME: 2017-06-25 21:00:27 CEST
LABEL: dbSQWare_20170625_210019
STOP TIME: 2017-06-25 22:27:03 CEST

Avec ça, PostgreSQL possède toutes les informations nécessaires pour une restauration réussie à la date de fin de sauvegarde.

En résumé : un dossier de données, un fichier « *.backup » contenant les informations de checkpoint et de LSN, les fichiers de configuration et un répertoire où sont collectés les archive_logs au fur et à mesure… Vous êtes paré.

… et pour un P.I.T.R par dbSQWare

Revenons au dossier PITR de dbSQWare, celui accessible via le raccourci du shell.

Dans ce répertoire, vous trouvez :

  •    BackupArchiveXlog -> contient tous les archive_logs nécessaires pour remonter au plus près.
  •    LastArchLogBck.log -> heure et durée d’exécution du sqwArchMon, les noms des fichiers déplacés…
  •    PITR_20170624_210010_tar    -> dossier contenant les fichiers de la PITR J-1
  •   PITR_20170625_210019_tar -> dossier contenant la PITR du jour
  •   sqwArchmon.log      -> info d’exécution du sqwArchMon, dont les raisons de son déclenchement. Exemple : « threshold reached 100 m 26/06/2017 09:32:18″ signifie que 100 Mo d’archives ont été accumulés alors que « timeout reached 26/06/2017 15:28:40″ signifie que le délais maximum d’inactivité a été atteint.

En entrant dans les dossiers PITR, on trouve :

  • Les fichiers de configuration de la base sauvegardée. Ils sont déjà inclus dans l’archive du $PGDATA (on y vient) mais celle-ci pouvant peser des centaines de Go, mieux vaut avoir ces informations sous la main. postgresql.conf, pg_hba.conf, pg_ident.conf.
  • data_dir.tar.gz :  le répertoire désigné par $PGDATA. Celui qui a été copié lors de l’étape de backup. La compression est optionnelle. Parfois, vous préférerez l’éviter pour gagner du temps lors d’une restauration.
  • « recoverySimple.conf » : contient un exemple de « restore_command » simple. Normalement, vous n’en avez pas besoin.
  • « recovery.conf » : contient la commande de restauration proprement dite, celle à mettre dans le $PGDATA une fois qu’il a été restauré. Il indique comment récupérer les archive xlogs et jusqu’à quelle date ramener la base.
    Exemple version dbSQWare :

     restore_command = 'gunzip </pgbackup/$PG_SID/PITR/BackupArchiveXlog/%f.gz > %p'
    
     recovery_target_time = '2017-07-02 23:02:59'
    
     pause_at_recovery_target = true
  • « restoreArchivesCommands.txt » : regroupe les commandes de copie des logs du répertoire « BackupArchiveXlog » vers celui de « ArchiveXlog ». Cela peut vous être utile lorsque que vous avez des miliers de fichiers à déplacer et décompresser.
  • « StartBackupWal.txt » et « StopBackupWal.txt » : comme leur nom l’indique, ils contiennent le nom du premier et du dernier fichier nécessaire à la consistance du backup simple (celui initié par « pg_start_backup() »).
  • « ArchivesNeededForPITR.txt » : sans surprise, contient la liste des fichiers d’archives nécessaire à la restauration de la base. Leur nombre peut varier de 1 (s’il n’y a pas d’activité) à beaucoup (s’il y avait de l’activité lors de la sauvegarde). Ce fichier permet de retrouver plus facilement les archives nécessaires pour restaurer uniquement le répertoire de données (et donc ramener la base à l’heure de fin de backup, pas plus loin).
  • « RestoreCommands.hlp » : le dernier et le plus important en cas de restauration. Il contient les instructions auto-générées par dbSQWare lors de la sauvegarde afin de vous faciliter la tâche pour la restauration.

Ce fichier contient une suite d’instructions allant de la re-création des répertoires d’origine jusqu’au premier démarrage.

Comme il est long et mérite d’être détaillé, nous allons nous en servir pour faire la transition avec la deuxième partie de cet article : « Restauration de la sauvegarde PITR avec dbSQWare « .

Si vous nous lisez en temps réel, vous allez devoir patienter sept longs jours. Si vous nous lisez à J+7, vous pouvez enchaîner directement.

Des problèmes ? des questions ? Exprimez-vous ! Les commentaires sont ouverts. Coquilles et fautes de grammaires sont notre lot quotidien : signalez-les nous à m.capello@dbsqware.com