Écrire des scripts de migration de base de données qui ne feront pas exploser la production
Vous avez une nouvelle fonctionnalité prête. Le code est relu, testé et fusionné. Mais il reste un obstacle avant le déploiement : une modification de base de données. Peut-être devez-vous ajouter une colonne, renommer une table ou introduire un nouvel index. La question n'est pas de savoir si la modification fonctionne sur votre ordinateur portable. La question est de savoir si elle fonctionnera en production sans rien casser.
C'est là que les scripts de migration entrent en jeu. Ce ne sont pas de simples fichiers SQL. Ils représentent une méthode rigoureuse pour faire évoluer votre schéma de base de données sans approximations, sans étapes manuelles et sans cette sensation désagréable quand un déploiement tourne mal.
Le modèle de base : un fichier, une modification
L'idée centrale est simple. Chaque modification de base de données vit dans son propre fichier. Le fichier possède un identifiant unique, généralement un horodatage ou un numéro de séquence, et contient le SQL nécessaire pour appliquer la modification. Vous créez également un fichier de rollback correspondant pour pouvoir l'annuler.
Supposons que vous deviez ajouter une colonne phone à la table users. Au lieu de vous connecter à la production et d'exécuter ALTER TABLE directement, vous créez un fichier nommé 20241101_add_phone_to_users.sql. À l'intérieur, vous écrivez :
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
Puis vous créez 20241101_add_phone_to_users_rollback.sql :
ALTER TABLE users DROP COLUMN phone;
Les deux fichiers sont placés dans votre dépôt aux côtés de votre code applicatif. Ils passent par une revue de code. Ils sont fusionnés comme n'importe quelle autre modification.
Pourquoi des fichiers séparés ? Parce que chaque modification comporte son propre risque. En divisant les changements en fichiers individuels, vous pouvez appliquer une modification, observer l'impact, puis passer à la suivante. Si tout est regroupé dans un seul script géant, vous ne pouvez pas déterminer quelle partie a causé une défaillance. Pire, si la migration échoue en cours de route, vous ignorez à quelle étape elle s'est arrêtée.
L'ordre est plus important que vous ne le pensez
L'horodatage ou le numéro de séquence n'est pas qu'une simple convention de nommage. Il définit l'ordre d'exécution. Votre pipeline de migration lit tous les fichiers, les trie par cet identifiant et les exécute du plus ancien au plus récent. Cela garantit que chaque environnement – développement, recette, production – applique les modifications dans le même ordre.
Fini le « ça marche sur ma machine mais échoue sur le serveur » parce que l'ordre de migration était différent. Fini les incohérences silencieuses où un environnement a sauté une étape.
Rendez vos scripts idempotents
Idempotent est un terme savant pour une idée simple : exécuter le même script deux fois doit produire le même résultat et ne pas générer d'erreur.
Comparez ces deux instructions :
-- Non idempotent : générera une erreur si la colonne existe déjà
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- Idempotent : peut être exécuté plusieurs fois sans risque
ALTER TABLE users ADD COLUMN IF NOT EXISTS phone VARCHAR(20);
La deuxième version est plus sûre. Les pipelines doivent parfois réexécuter les migrations depuis le début, par exemple lors de la création d'une nouvelle base de données de recette. Si vos scripts ne sont pas idempotents, cette simple opération se transforme en session de débogage.
L'idempotence aide également pendant le développement. Les développeurs appliquent fréquemment une migration, inspectent le résultat et souhaitent recommencer. Avec des scripts idempotents, ils peuvent réexécuter sans avoir à supprimer et recréer l'intégralité de la base de données.
Le rollback n'est pas optionnel
Chaque migration forward doit avoir un rollback correspondant. Ce n'est pas réservé aux urgences en production. Les développeurs utilisent constamment les rollbacks pendant le développement local pour tester les modifications et itérer. Sans scripts de rollback, ils doivent supprimer toute la base de données et exécuter toutes les migrations depuis le début, ce qui est lent et frustrant.
Mais voici la vérité honnête : les rollbacks ne peuvent pas toujours restaurer les données. Si une migration supprime une colonne, le rollback peut recréer cette colonne, mais les données sont perdues. Si une migration renomme une table, le rollback peut la renommer à nouveau, mais toutes les écritures effectuées entre-temps sont perdues.
Cela ne signifie pas que les rollbacks sont inutiles. Cela signifie que vous devez comprendre ce que votre rollback restaure réellement. Parfois, il ne restaure que la structure du schéma, pas les données. C'est toujours précieux. Cela vous ramène à un état connu à partir duquel vous pouvez récupérer ou réappliquer les modifications.
Pour les modifications destructrices – suppression de colonnes, retrait de tables, changement de types de données – vous avez besoin d'une stratégie distincte. Nous aborderons cela plus tard. Pour l'instant, la règle est simple : chaque migration a un rollback, même s'il ne restaure que la structure.
Comment le travail parallèle reste sûr
Plusieurs développeurs travaillant sur différentes fonctionnalités ont souvent besoin de modifications de base de données. L'un ajoute une colonne pour la fonctionnalité A. Un autre crée une table pour la fonctionnalité B. Les deux créent des fichiers de migration avec des horodatages différents. Lorsque les deux branches sont fusionnées, le pipeline trie les fichiers par horodatage et les applique dans l'ordre.
Les conflits surviennent uniquement lorsque deux modifications touchent le même objet de schéma. C'est un conflit légitime qui nécessite une résolution humaine, tout comme un conflit de code. Le modèle de fichier de migration ne l'élimine pas, mais il rend le conflit visible et explicite.
La table de suivi
Comment votre pipeline sait-il quelles migrations ont déjà été exécutées ? Il utilise une table spéciale dans la base de données elle-même. Cette table enregistre chaque migration qui a été appliquée, avec un horodatage ou une somme de contrôle. Lorsque le pipeline s'exécute, il consulte cette table, la compare à la liste des fichiers de migration et n'applique que ceux qui manquent.
Ce mécanisme est intégré dans la plupart des outils de migration, mais le comprendre vous aide à déboguer en cas de problème. Si une migration a été partiellement appliquée, ou si quelqu'un a exécuté manuellement un script en dehors du pipeline, la table de suivi vous le dira.
Liste de contrôle pratique pour écrire des scripts de migration
Avant de fusionner ce fichier de migration, passez rapidement en revue cette liste :
- Le script a-t-il un identifiant unique (horodatage ou séquence) ?
- Existe-t-il un script de rollback correspondant ?
- Le script est-il idempotent ? Peut-il être exécuté deux fois sans erreur ?
- Le rollback restaure-t-il réellement l'état précédent ?
- Avez-vous testé à la fois la migration forward et le rollback sur une copie des données de production ?
- La migration verrouille-t-elle des tables ? Si oui, peut-elle être exécutée pendant une période de faible trafic ?
Ce qu'il faut retenir
Les migrations de base de données ne sont pas que du SQL. Ce sont un contrat entre votre équipe et vos données de production. Chaque fichier de migration représente une décision : quelles modifications, dans quel ordre, et comment les annuler si les choses tournent mal. Traitez chaque fichier avec le même soin que vous accordez à votre code applicatif. Relisez-le. Testez-le. Assurez-vous qu'il a un rollback. Votre base de données de production vous en remerciera.