Quand les migrations de base de données tournent mal : pourquoi avancer vaut mieux que reculer

Vous venez de déployer une migration qui ajoute une colonne phone_number à la table users. La migration s'est exécutée avec succès. Puis votre équipe réalise que le code applicatif qui utilise cette colonne n'a pas encore été déployé. Chaque nouvelle inscription échoue désormais, car l'ancien code tente d'insérer une ligne sans fournir de valeur pour la nouvelle colonne NOT NULL.

Votre système de production est cassé. Que faites-vous ?

La plupart des équipes se tournent instinctivement vers la down migration — le script qui annule le changement et supprime la colonne. Mais cet instinct peut causer plus de dégâts que le problème initial. Il existe une meilleure approche : avancer (roll-forward).

Le problème des down migrations

Les down migrations semblent propres sur le papier. Vous écrivez une migration up qui ajoute une colonne, et une migration down qui la supprime. Si quelque chose tourne mal, vous exécutez la down migration et tout revient à l'état initial.

En pratique, les down migrations sont dangereuses pour plusieurs raisons.

D'abord, la perte de données est quasi certaine. Si des lignes ont été insérées ou mises à jour après l'exécution de la up migration, ces valeurs disparaissent lorsque vous supprimez la colonne. Vous risquez de perdre des données clients irrécupérables.

Ensuite, les down migrations créent un décalage entre votre code applicatif et votre schéma de base de données. Si votre code applicatif s'attend déjà à ce que la nouvelle colonne existe, la supprimer provoque des erreurs d'exécution. Vous devriez aussi déployer l'ancien code applicatif, ce qui implique de coordonner plusieurs rollbacks simultanément.

Enfin, les down migrations sont rarement testées. Les équipes les écrivent comme une réflexion après coup, souvent avec des bugs qui ne se révèlent que lors d'une véritable urgence. Exécuter un script non testé en production pendant une panne est une recette pour encore plus de temps d'arrêt.

Qu'est-ce que le roll-forward ?

Le roll-forward est une stratégie où vous ne revenez jamais en arrière sur une migration. Au lieu de cela, lorsqu'une migration pose problème, vous écrivez une nouvelle migration qui corrige le problème. La base de données avance vers un état corrigé, et non en arrière vers un état précédent.

Reprenons notre exemple : au lieu d'exécuter une down migration pour supprimer phone_number, vous écrivez une nouvelle migration qui rend la colonne nullable ou lui ajoute une valeur par défaut. La colonne reste, mais la contrainte qui causait les échecs est supprimée. Les nouvelles inscriptions fonctionnent à nouveau, et toutes les données déjà stockées dans phone_number sont préservées.

Voici à quoi ressemble cette migration de correction en SQL :

-- version_002 : correction de la contrainte phone_number
-- Cette migration rend phone_number nullable pour que l'ancien code applicatif
-- puisse insérer des lignes sans fournir de valeur.
ALTER TABLE users
ALTER COLUMN phone_number DROP NOT NULL;

Chaque migration devient un changement cumulatif. La première migration a ajouté la colonne. La deuxième migration a corrigé la contrainte de la colonne. Le suivi des migrations enregistre les deux changements dans l'ordre, vous permettant de voir que version_002 a corrigé version_001 sans effacer son historique.

Pourquoi les équipes préfèrent le roll-forward

Le plus grand avantage est l'absence de perte de données. Si votre première migration a stocké 500 numéros de téléphone avant que vous ne découvriez le problème, ces numéros survivent à la correction. Une down migration les aurait supprimés définitivement.

Le roll-forward maintient également l'alignement entre votre code applicatif et votre schéma de base de données. Comme la colonne existe toujours, tout code applicatif qui lit phone_number continue de fonctionner. Vous n'avez pas besoin de coordonner un rollback simultané du code. Vous corrigez la base de données, puis vous corrigez le code applicatif à votre rythme.

Cette approche reflète la façon dont les équipes gèrent les bugs dans le code applicatif. Lorsqu'un bug atteint la production, vous ne revenez pas à la version de la semaine dernière. Vous poussez une correction. Les migrations de base de données devraient fonctionner de la même manière : la correction est une nouvelle migration, pas un retour en arrière.

Quand le roll-forward devient complexe

Toutes les corrections en roll-forward ne sont pas aussi simples que la modification d'une contrainte de colonne. Prenons une migration qui a changé le type de données d'une colonne de VARCHAR à INTEGER. Si la conversion a tronqué ou corrompu des données existantes, votre migration de correction pourrait devoir :

  1. Ajouter une nouvelle colonne avec le type de données d'origine
  2. Copier les données de la colonne corrompue, en appliquant des transformations pour récupérer les valeurs
  3. Mettre à jour les références du code applicatif pour utiliser la nouvelle colonne
  4. Supprimer la colonne corrompue dans une migration ultérieure

C'est plus de travail qu'une simple down migration. Mais c'est aussi plus sûr. Vous pouvez exécuter la migration de correction dans un environnement de staging d'abord, vérifier la logique de récupération des données, et seulement ensuite l'appliquer en production. Une down migration n'offre pas un tel filet de sécurité — elle supprime simplement la colonne et espère que tout va bien.

Le point clé est que le roll-forward ne vous oblige pas à prédire chaque échec possible avant le déploiement. Vous avez juste besoin d'avoir confiance que si quelque chose tourne mal, votre équipe peut écrire une correction. Cela déplace le risque de la prévention (qui est impossible à perfectionner) vers la récupération (qui est une compétence que vous pouvez pratiquer).

Checklist pratique pour le roll-forward

Avant de vous engager dans le roll-forward comme stratégie d'équipe, assurez-vous que ces pratiques sont en place :

Utilisez cet arbre de décision lorsqu'une migration pose problème :

flowchart TD A[Une migration pose problème] --> B{Risque élevé de perte de données ?} B -->|Oui| C[Roll-forward] B -->|Non| D{Code et schéma désynchronisés ?} D -->|Oui| E[Roll-forward] D -->|Non| F[Envisager un rollback] C --> G[Écrire une migration de correction] E --> G F --> H[Exécuter la down migration avec prudence]
  • Chaque migration doit être réversible en théorie, mais pas nécessairement en code. Comprenez ce que ferait une down migration, mais n'en écrivez pas sauf si vous avez une raison spécifique d'en avoir besoin.
  • Testez vos migrations de correction en staging d'abord. Exécutez la migration d'origine, introduisez le scénario d'échec, puis appliquez la migration de correction. Vérifiez l'intégrité des données après.
  • Gardez votre suivi des migrations fiable. Ne modifiez ni ne supprimez jamais manuellement les enregistrements de migration. Le suivi est votre piste d'audit pour comprendre ce qui a changé et quand.
  • Documentez le mode d'échec. Lorsque vous écrivez une migration de correction, ajoutez un commentaire expliquant ce qui a mal tourné et pourquoi la correction fonctionne. Cela aide les futurs membres de l'équipe qui rencontreraient des schémas similaires.
  • Pratiquez des scénarios de roll-forward. Organisez un exercice trimestriel où quelqu'un introduit délibérément une mauvaise migration, et l'équipe s'entraîne à écrire et déployer une correction sous pression.

L'essentiel à retenir

Les down migrations sont un piège. Elles promettent la simplicité mais apportent perte de données, maux de tête de coordination et scripts d'urgence non testés. Le roll-forward traite les changements de base de données comme des changements de code : quand quelque chose casse, vous corrigez en avant, pas en arrière.

La prochaine fois qu'une migration tourne mal, résistez à l'envie de revenir en arrière. Écrivez plutôt une migration de correction. Vos données, votre application et la santé mentale de votre équipe vous remercieront.