Tester les migrations de base de données avant la mise en production

Vous avez écrit un script de migration. Il semble correct. La syntaxe est valide. La logique semble juste. Vous l'exécutez sur votre base locale, ça fonctionne. Puis vous le déployez en production, et tout casse.

La table contenait des millions de lignes. Votre base locale en avait douze. La migration ajoutait une colonne NOT NULL, mais la production avait des valeurs NULL existantes que vous ignoriez. L'ALTER TABLE a verrouillé les écritures pendant quinze minutes en plein pic de trafic.

Ce scénario n'est pas rare. Il se produit parce que l'environnement où vous testez votre migration ne correspond pas à celui où elle sera réellement exécutée. Une migration qui passe sur une base vide ou non représentative donne une fausse confiance. La solution n'est pas de tester plus soigneusement sur votre machine locale. La solution est de construire un environnement de test qui reflète la réalité de la production.

Pourquoi un schéma correspondant est important

Un script de migration repose sur des hypothèses. Il suppose que certaines colonnes existent, que certains index sont présents, que certaines contraintes sont en place. Si votre base de test a un schéma différent, vous testez sur un ensemble d'hypothèses différent.

Prenons une migration qui ajoute une colonne NOT NULL. Sur une base de test avec une table vide, la migration s'exécute instantanément. En production, la table contient des lignes avec des valeurs NULL dans cette colonne. La migration échoue, et vous avez un incident de production.

La façon la plus fiable de faire correspondre les schémas est de prendre un dump du schéma de production et de le restaurer dans votre environnement de test. Cela vous donne les structures de table exactes, les index, les contraintes et les types de données qui existent en production. Vous ne devinez plus si votre schéma de test correspond à la réalité.

Voici un exemple concret avec PostgreSQL :

# Dump du schéma uniquement (sans les données) depuis la production
pg_dump --schema-only --no-owner --no-acl production_db > schema.sql

# Restauration du schéma dans votre base de test
psql test_db < schema.sql

Des données qui révèlent les vrais problèmes

Une table vide ou une table avec des données de test aléatoires ne révélera pas les cas limites. Les échecs de migration viennent souvent de données qui existent en production mais pas dans votre jeu de test.

Si votre migration change un type de colonne, vos données de test doivent inclure des valeurs limites : des chaînes longues, des nombres décimaux avec beaucoup de chiffres, des valeurs NULL dans cette colonne. Si votre migration ajoute une contrainte d'unicité, vos données de test doivent inclure des lignes en double qui violent cette contrainte. Si votre migration supprime une colonne, vos données de test doivent inclure des requêtes qui référencent encore cette colonne.

Vous n'avez pas besoin de copier l'ensemble du jeu de données de production. Pour une table avec des millions de lignes, quelques milliers de lignes bien choisies suffisent pour que le moteur de base de données produise un plan d'exécution réaliste. L'objectif n'est pas de répliquer le volume de production. L'objectif est de répliquer les motifs de données qui pourraient faire que la migration se comporte différemment.

Le dry-run comme filet de sécurité

Un dry-run exécute le SQL de la migration sans modifier définitivement la base de données. Certains outils de migration ont un mode dry-run intégré. Si ce n'est pas le cas, vous pouvez encapsuler la migration dans une transaction et l'annuler à la fin.

Le but d'un dry-run n'est pas de confirmer que la migration réussit. C'est de voir les avertissements, les erreurs et les changements de plan d'exécution qui pourraient indiquer des problèmes. Une migration qui fonctionne parfaitement sur une petite table pourrait montrer un avertissement de parcours complet de table lorsqu'elle est exécutée sur un schéma réaliste. Un ALTER TABLE sur une grande table pourrait montrer un temps d'exécution estimé inacceptable pour votre fenêtre de maintenance.

Après le dry-run, examinez la sortie manuellement ou via des vérifications automatisées. Recherchez les opérations qui pourraient verrouiller les tables pendant de longues périodes. Recherchez les requêtes qui pourraient dégrader les performances. Recherchez tout comportement inattendu qui n'est pas apparu lors des tests locaux.

Simulation d'une charge légère pendant la migration

Certaines migrations sont sûres sur une base inactive mais causent des problèmes sous un trafic réel. Un ALTER TABLE qui acquiert un verrou peut se terminer en quelques secondes lorsque personne d'autre n'utilise la table. Sous charge de production, ce même verrou pourrait provoquer des timeouts de requêtes et des erreurs applicatives.

Vous n'avez pas besoin d'un test de charge complet. Un simple script qui exécute des requêtes SELECT et INSERT sur les tables concernées pendant la migration suffit. Si ces requêtes simulées échouent ou expirent, vous avez trouvé un problème qui aurait touché les utilisateurs de production.

Exécutez cette simulation pendant la phase de dry-run. Si la migration provoque des deadlocks ou des attentes excessives sous une charge légère, vous devez reconsidérer votre approche. Peut-être devez-vous utiliser une stratégie de verrouillage moins perturbatrice. Peut-être devez-vous décomposer la migration en étapes plus petites. Peut-être devez-vous la planifier pendant une fenêtre de moindre trafic.

Automatisation de l'environnement de test

Configurer manuellement un environnement de test pour chaque migration est lent et sujet aux erreurs. La meilleure approche est de l'automatiser dans le cadre de votre pipeline CI.

Lorsqu'une nouvelle migration est commitée, le pipeline crée un environnement de test à partir d'un instantané du schéma de production. Il charge les données de test pertinentes. Il exécute le dry-run. Il simule une charge légère. Puis il détruit l'environnement.

Cette automatisation garantit que chaque migration est testée sur une base de référence cohérente. Personne ne peut sauter le test parce qu'il était pressé. Personne ne peut prétendre que le test a réussi parce qu'il a utilisé un schéma différent. Le pipeline impose les mêmes conditions à chaque fois.

Une checklist pratique

Avant d'exécuter une migration en production, vérifiez que ces conditions sont remplies :

  • Le schéma de test correspond exactement au schéma de production
  • Les données de test incluent les cas limites pertinents pour la migration
  • Le dry-run s'est terminé sans avertissements ou erreurs inattendus
  • La simulation de charge légère n'a pas causé d'échecs ou de timeouts
  • L'environnement de test a été créé à partir d'un instantané récent de la production

Ce que cela signifie pour votre pipeline

Les migrations de base de données ne sont pas comme le code applicatif. Vous ne pouvez pas simplement déployer et observer. Une migration échouée peut corrompre des données, verrouiller des tables et provoquer des temps d'arrêt prolongés. Le coût d'une mauvaise migration est bien plus élevé que celui d'une mauvaise release applicative.

Tester les migrations dans un environnement réaliste n'est pas optionnel. C'est la différence entre savoir que votre migration fonctionnera et espérer qu'elle fonctionnera. L'environnement que vous construisez pour les tests n'a pas besoin d'être coûteux ou complexe. Il doit être représentatif.

Commencez par un dump du schéma de production. Ajoutez des données de test ciblées. Exécutez des dry-runs. Simulez une charge légère. Automatisez l'ensemble du processus. Votre base de production vous remerciera.