Éprouver une restauration : la procédure et les trois pièges
Ma procédure de restauration rendait [OK] Restauration terminée après 223
erreurs. Elle tournait depuis des mois, le script sortait avec le code 0, et
personne n’avait eu besoin de s’en servir. Voici ce qui n’allait pas, et la
procédure qui en est sortie.
Pourquoi un script peut mentir
Deux valeurs par défaut suffisent.
psql continue après une erreur. Par défaut, il exécute chaque instruction,
signale celles qui échouent, et sort avec le code 0. Un cliché rejoué sur une base
qui contient déjà les tables produit une erreur par table, et le script conclut
que tout s’est bien passé.
psql -v ON_ERROR_STOP=1 -U "$USER" -d "$BASE"
pg_dump n’efface pas ce qu’il va recréer. Sans --clean --if-exists, le
cliché contient des CREATE TABLE mais aucun DROP. Sur une base vide, tout va
bien. Sur une base existante, chaque instruction échoue.
Les deux réglages sont nécessaires ensemble. Avec l’un sans l’autre, on obtient soit un mensonge, soit un échec sur la première table.
Piège 1 : l’application écrit pendant la restauration
Un psql qui repose les tables pendant qu’un utilisateur ouvre un ticket produit
une base incohérente, et sans aucune erreur. La moitié des lignes vient du
cliché, l’autre de l’activité en cours, et les clés étrangères peuvent tenir
malgré tout.
Il faut arrêter l’application avant, et la redémarrer après. L’indisponibilité est assumée et annoncée, pas subie.
Piège 2 : la base n’est pas vide
C’est le cas normal. On restaure parce qu’on veut remplacer ce qui est là.
La seule façon propre est de recréer la base, ce qui suppose de fermer les
sessions ouvertes, faute de quoi DROP DATABASE échoue sur « is being accessed by
other users » et l’instance reste arrêtée sans base.
SELECT pg_terminate_backend(pid) FROM pg_stat_activity
WHERE datname = 'labase' AND pid <> pg_backend_pid();
DROP DATABASE IF EXISTS "labase";
CREATE DATABASE "labase";
Piège 3 : les fichiers ne sont pas dans la base
Logos, pièces jointes, images déposées vivent dans un répertoire. Une base restaurée sans eux rend un site dont toutes les images sont cassées et dont les pièces jointes renvoient des 404.
Les deux archives doivent porter le même horodatage et se restaurer ensemble. L’ancien répertoire se déplace au lieu d’être supprimé : une pièce jointe déposée après la sauvegarde s’y trouve encore, et c’est parfois tout ce qui en reste.
Un détail qui coûte une heure quand on l’oublie : tar lancé en root réapplique
les propriétaires de l’archive. Sans ça, l’application ne peut plus écrire dans
le répertoire, et les envois échouent en silence.
Le filet avant le filet
Restaurer la mauvaise sauvegarde est au moins aussi fréquent que l’incident qu’on répare. La procédure prend donc un cliché de l’état actuel avant de l’écraser, et elle dit où il se trouve.
Si le cliché de sécurité échoue, on s’arrête. Restaurer à l’aveugle une base dont on n’a pas de copie récente transforme un incident en perte.
La preuve
Le test qui compte n’est pas « le script s’exécute », c’est « les données sont revenues ». Sur la dernière vérification : 24 tickets supprimés, restaurés, recomptés. 28 tables. Un membre reconnecté qui retrouve ses pièces jointes.
À répéter au moins une fois par an, et après chaque changement de version majeure du moteur.
La version applicative n’est pas restaurée
Elle ne remet pas la version applicative d’alors. Au redémarrage, le point d’entrée applique les migrations manquantes : un cliché ancien se remet à niveau sous l’image courante. C’est le bon sens de marche, l’inverse ne l’est pas.
Elle ne couvre pas la perte du serveur entier. Une sauvegarde qui vit sur la machine qu’elle sauvegarde ne sauvegarde rien. La copie hors site est un sujet à part, et elle a ses propres pièges.