Quand les down migrations de base de données sont sûres et quand elles deviennent dangereuses

Vous venez de déployer une migration qui ajoute une colonne phone_number à votre table users. Quelques heures plus tard, quelqu'un remarque que le nom de la colonne aurait dû être phone pour correspondre au reste du code. Votre premier réflexe est d'exécuter la down migration, de supprimer la colonne et de redéployer avec le bon nom. Simple, non ?

En début de développement, cela fonctionne parfaitement. En production, cette même action pourrait vous coûter des données clients, déclencher des erreurs applicatives et créer un désordre qui mettra des jours à se démêler.

Les down migrations sont l'équivalent du "annuler" pour la base de données. Si votre migration a ajouté une colonne, la down migration la supprime. Si votre migration a créé une table, la down migration la détruit. Le concept semble simple, mais les conséquences sont tout sauf simples dès que de vrais utilisateurs et de vraies données sont impliqués.

Les down migrations sont sûres en début de développement

Lorsque votre équipe construit une nouvelle fonctionnalité sur une branche, le schéma peut changer plusieurs fois par jour. Vous écrivez une migration, la testez, réalisez que l'approche est erronée et exécutez la down migration. Personne n'est affecté car il n'y a pas d'utilisateurs. Aucun autre code ne dépend de ce schéma car vous travaillez en isolation.

C'est là que les down migrations brillent. Elles vous permettent d'expérimenter rapidement sans vous soucier du nettoyage. Vous pouvez essayer différents types de colonnes, tester des structures de table et itérer rapidement. Le coût des erreurs est nul car rien de permanent n'existe encore.

La recette (staging) introduit les premiers vrais risques

Les environnements de recette se situent dans une zone grise. Les down migrations y fonctionnent encore, mais elles commencent à montrer leurs angles dangereux.

Le problème, ce sont les données. Les environnements de recette contiennent souvent des données proches de la production, provenant de sauvegardes anonymisées ou d'une utilisation réelle lors des tests. Si votre down migration supprime une colonne, vous perdez toutes les données qui s'y trouvaient. En recette, vous pouvez généralement recharger les données, mais le processus prend du temps. Une table avec des millions de lignes peut mettre des heures à être reconstruite.

Plus important encore, la recette crée des habitudes. Si votre équipe se sent à l'aise pour exécuter des down migrations en recette tous les jours, cette mémoire musculaire se transfère en production. La même action, inoffensive en recette, devient destructrice en production, et personne ne s'arrête pour y réfléchir parce que "nous faisons toujours comme ça".

Production : là où les down migrations deviennent dangereuses

La production est l'endroit où le simple concept d'"annuler" s'effondre. Trois problèmes spécifiques rendent les down migrations risquées dans les environnements de production.

Le diagramme d'états suivant illustre comment la sécurité des down migrations évolue selon les environnements :

Considérez cette migration qui a ajouté une colonne phone_number à la table users :

-- Up migration
ALTER TABLE users ADD COLUMN phone_number varchar(20);

-- Down migration
ALTER TABLE users DROP COLUMN phone_number;

Si des utilisateurs ont déjà saisi leur numéro de téléphone, exécuter la down migration détruit ces données instantanément. Pas d'avertissement, pas de confirmation, pas d'annulation. La colonne et toutes ses valeurs disparaissent.

flowchart TD Dev[Développement] -->|Pas d'utilisateurs, pas de risque de données| Staging[Recette] Staging -->|Données proches de la production, formation d'habitudes| Prod[Production] Prod -->|Perte de données| DataLoss[La perte de données est permanente] Prod -->|Décalage code-schéma| Sync[Code et schéma désynchronisés] Prod -->|Modifications irréversibles| Irreversible[Certaines modifications ne peuvent pas être annulées]

La perte de données est permanente

Lorsque vous exécutez une down migration qui supprime une colonne, chaque valeur de cette colonne est perdue. Il n'y a pas de corbeille pour les colonnes de base de données. Si votre migration a ajouté une colonne phone_number et que des utilisateurs ont déjà saisi leurs numéros, ces numéros disparaissent lorsque la colonne est supprimée.

Vous pourriez penser : "Je vais restaurer à partir d'une sauvegarde." Mais les sauvegardes effectuées après l'exécution de la migration contiennent déjà la nouvelle colonne avec les nouvelles données. Restaurer à partir d'une sauvegarde effectuée avant la migration signifie perdre toutes les modifications effectuées après la migration. Dans les deux cas, des données sont perdues.

La seule approche sûre consiste à restaurer à partir d'une sauvegarde effectuée avant la migration, puis à rejouer chaque modification survenue après la migration, en excluant la migration problématique elle-même. Ce processus est complexe, long et sujet aux erreurs. La plupart des équipes ne disposent pas des outils ni de la discipline opérationnelle nécessaires pour le faire de manière fiable.

Le code et le schéma deviennent désynchronisés

C'est le modèle de défaillance en production le plus courant avec les down migrations. Imaginez que votre migration a ajouté une colonne status à la table orders avec une valeur par défaut pending. Votre nouveau code applicatif lit cette colonne. Lorsque vous exécutez la down migration, la colonne disparaît.

Mais vos instances applicatives exécutent toujours le nouveau code. Elles commencent immédiatement à générer des erreurs car la colonne qu'elles attendent n'existe plus. Même si vous commencez à annuler le déploiement du code applicatif, l'annulation n'est pas instantanée. Vous avez plusieurs instances, chacune avec son propre cycle de déploiement. Certaines instances peuvent encore exécuter le nouveau code pendant que d'autres sont revenues en arrière. Pendant cette fenêtre, les erreurs se propagent en cascade dans votre système.

Le problème fondamental est que les annulations de déploiement applicatif et les annulations de base de données ne peuvent pas être parfaitement synchronisées. Il y aura toujours une période où le code s'attend à un schéma qui n'existe plus, ou le schéma a une colonne que l'ancien code ne sait pas gérer.

Certaines modifications ne peuvent pas être annulées

Certaines migrations sont destructrices par nature. Prenons une migration qui combine first_name et last_name en une seule colonne full_name. Les données d'origine ont été transformées. Exécuter une down migration peut recréer les colonnes first_name et last_name, mais les données qu'elles contiennent ne correspondront pas à ce qui existait avant la migration. La séparation d'origine est perdue.

Autre exemple : une migration qui supprime une colonne encore utilisée par des requêtes héritées. Une fois cette colonne supprimée, les données sont perdues. Aucune magie de down migration ne les ramènera. La seule voie de récupération est la restauration à partir d'une sauvegarde, ce qui ramène tous les problèmes mentionnés précédemment.

Quand les down migrations sont acceptables en production

Les down migrations ne sont pas universellement interdites en production. Il existe des conditions spécifiques où elles peuvent être utilisées en toute sécurité :

  • La migration ajoute uniquement de nouvelles tables ou colonnes qui n'ont jamais été remplies de données.
  • Aucun code applicatif dépendant du nouveau schéma n'a été déployé.
  • Vous pouvez vérifier qu'aucun processus en cours d'exécution, tâche planifiée ou worker en arrière-plan ne référence le schéma modifié.

Même dans ces cas, l'approche la plus sûre consiste à traiter la down migration comme une nouvelle migration forward. Écrivez une migration qui annule explicitement la modification, déployez-la et laissez-la s'exécuter via votre pipeline normal. Cela vous donne le même résultat qu'une down migration, mais avec une visibilité, des tests et une capacité d'annulation complets.

Une checklist pratique avant d'exécuter une down migration en production

Avant d'exécuter cette down migration, posez-vous ces questions :

  • Y a-t-il des données utilisateur dans les colonnes ou tables supprimées ?
  • Existe-t-il des instances applicatives exécutant encore du code qui dépend de ce schéma ?
  • Y a-t-il des jobs en arrière-plan, des tâches planifiées ou des pipelines de données qui référencent les objets modifiés ?
  • La modification peut-elle être annulée sans perdre les informations saisies après la migration ?
  • Disposez-vous d'une sauvegarde vérifiée effectuée avant l'exécution de la migration ?
  • Pouvez-vous vous permettre le temps d'arrêt pendant que la down migration s'exécute sur de grandes tables ?

Si vous répondez "oui" à l'une des trois premières questions, n'exécutez pas la down migration. Écrivez plutôt une migration forward.

L'alternative plus sûre : avancer, pas reculer

La stratégie la plus fiable pour corriger une mauvaise migration de base de données en production n'est pas de l'annuler, mais de la corriger en avançant. Écrivez une nouvelle migration qui corrige le problème. Si le nom de la colonne est erroné, ajoutez la colonne correcte, copiez les données et dépréciez l'ancienne. Si la modification du schéma a introduit un bug, ajoutez une migration qui ajuste le schéma à l'état correct.

Les migrations forward sont plus sûres car elles préservent les données existantes, maintiennent la compatibilité avec le code en cours d'exécution et suivent le même processus de déploiement que toutes les autres modifications. Elles ne nécessitent pas une synchronisation parfaite entre les annulations applicatives et les annulations de base de données. Elles ne créent pas de fenêtres d'incohérence où les erreurs se propagent dans votre système.

Les down migrations sont un outil de développement. En production, avancer est toujours plus sûr que reculer.