Pourquoi le rollback d'une base de données est plus complexe que celui d'une application
Vous déployez une nouvelle version de votre application. Quelque chose se passe mal. Vous appuyez sur le bouton de rollback, l'ancienne version se remet à tourner, et en quelques minutes tout est revenu à la normale. Aucune donnée perdue, aucun effet secondaire persistant. Le processus semble propre et réversible.
Imaginez maintenant un scénario différent. Vous exécutez une migration de base de données qui ajoute une colonne status à la table orders. La migration se termine, la nouvelle colonne est remplie avec des valeurs par défaut, et votre application mise à jour commence à écrire des données réelles dans cette colonne. Quelques heures plus tard, vous découvrez un bug dans la logique applicative qui rend les valeurs de status peu fiables. Vous décidez de faire un rollback de l'application vers la version précédente. L'ancien code est maintenant de nouveau en cours d'exécution. Mais la colonne status est toujours là. Les données écrites dedans sont toujours là. Et votre ancien code applicatif peut ne pas savoir comment gérer cette colonne supplémentaire, ou pire, il peut planter parce qu'il rencontre une colonne qu'il n'attendait pas.
C'est le problème central : le rollback d'une application ne fait que restaurer le code. Le rollback d'une base de données doit restaurer à la fois la structure et les données dans leur état antérieur à la migration. Et cela ne se produit pas automatiquement.
Pourquoi le rollback d'application est simple
Quand vous effectuez un rollback d'application, vous échangez essentiellement un ensemble de code exécutable contre un autre. L'ancienne version prend le relais, commence à traiter les nouvelles requêtes, et le système continue. Aucun état persistant n'est modifié pendant le rollback lui-même. La base de données reste exactement comme elle était avant que le rollback ne soit déclenché. La seule chose qui change est la version du code en cours d'exécution.
Cette simplicité explique pourquoi de nombreuses équipes considèrent le rollback comme un filet de sécurité. Si quelque chose tourne mal, il suffit de revenir en arrière et de réessayer plus tard. Cela fonctionne bien pour les services sans état et pour les applications où le schéma de base de données ne change pas entre les versions.
Pourquoi le rollback de base de données est différent
Le rollback de base de données implique d'annuler des changements structurels sur un stockage de données en production. Cela signifie supprimer des colonnes, restaurer des tables supprimées, ou revenir sur des contraintes modifiées. Et contrairement au code applicatif, les bases de données contiennent des données qui ont pu être modifiées, ajoutées ou supprimées depuis l'exécution de la migration.
Considérez une migration qui supprime une colonne nommée legacy_flag de la table users. Si vous devez faire un rollback, vous devez rajouter cette colonne. Mais qu'en est-il des données qui se trouvaient dans cette colonne ? Si la migration l'a simplement supprimée, ces données sont perdues à moins que vous ne les ayez sauvegardées au préalable. Si la migration a renommé ou transformé la colonne, vous devez inverser cette transformation exactement, sans corrompre les nouvelles données qui ont pu être écrites entre-temps.
Voici un exemple concret qui illustre le problème. Une migration forward ajoute une colonne, et le rollback correspondant tente de la supprimer :
-- Migration forward : ajout d'une colonne NOT NULL avec une valeur par défaut
ALTER TABLE orders ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'pending';
-- Des heures plus tard, le nouveau code applicatif écrit des valeurs réelles de statut
-- Certaines lignes ont maintenant status = 'shipped', 'cancelled', etc.
-- Migration de rollback : suppression de la colonne
ALTER TABLE orders DROP COLUMN status;
-- Cela réussit, mais toutes les données de statut sont perdues définitivement.
Si la migration de rollback tentait plutôt de conserver les données en renommant ou transformant la colonne, elle devrait gérer les contraintes, les index et toutes les nouvelles données écrites par l'ancienne application après le rollback — un processus fragile et souvent non testé.
Ce n'est pas un problème théorique. Les équipes qui comptent sur les down migrations — des scripts qui inversent les modifications apportées par la migration forward — découvrent souvent que ces scripts sont rarement testés, parfois cassés, et presque toujours risqués à exécuter en production. Une down migration qui échoue à mi-chemin peut laisser la base de données dans un état incohérent, avec certaines modifications annulées et d'autres non.
L'approche plus sûre : les migrations rétrocompatibles
Une stratégie plus fiable consiste à concevoir chaque migration de base de données pour qu'elle soit rétrocompatible. Cela signifie que les changements de schéma que vous effectuez ne doivent pas casser l'ancienne version de votre application. Si vous devez ajouter une nouvelle colonne, ajoutez-la sans supprimer ni modifier les colonnes existantes. L'ancienne application continue de fonctionner car elle ignore simplement la nouvelle colonne. La nouvelle application commence à l'utiliser. Si la nouvelle version s'avère buggée, vous pouvez faire un rollback de l'application sans toucher à la base de données du tout. La colonne supplémentaire reste, mais l'ancien code ne s'en soucie pas.
Cette approche nécessite de la discipline. Chaque modification de schéma doit être évaluée pour son impact sur toutes les versions de l'application qui pourraient encore être en cours d'exécution. Mais c'est bien plus sûr que de compter sur des down migrations qui peuvent échouer ou perdre des données.
Voici comment les migrations rétrocompatibles fonctionnent en pratique pour les opérations courantes :
Ajout d'une colonne : Ajoutez-la simplement. Ne la rendez pas
NOT NULLsauf si vous pouvez fournir une valeur par défaut qui fonctionne à la fois pour l'ancien et le nouveau code. L'ancienne application ne la lira ni ne l'écrira, donc elle ne sera pas affectée.Renommage d'une colonne : Ne la renommez pas directement. Ajoutez plutôt la nouvelle colonne avec le nouveau nom, mettez à jour l'application pour écrire dans les deux colonnes pendant une période de transition, puis supprimez l'ancienne colonne dans une migration ultérieure après avoir confirmé que l'ancien code n'est plus en cours d'exécution.
Suppression d'une colonne : Arrêtez d'abord de l'utiliser dans l'application. Déployez ce changement. Ensuite, dans une migration séparée, supprimez la colonne. Si vous devez faire un rollback de l'application, la colonne est toujours là.
Modification du type d'une colonne : Ajoutez une nouvelle colonne avec le nouveau type, migrez les données progressivement, mettez à jour l'application pour utiliser la nouvelle colonne, et seulement alors supprimez l'ancienne colonne.
Chacun de ces modèles ajoute des étapes, mais chaque étape est réversible sans perte de données.
Le vrai coût des down migrations
Certaines équipes préfèrent encore les down migrations car elles semblent plus simples à écrire. Un seul script qui inverse le changement semble plus propre qu'une approche rétrocompatible en plusieurs étapes. Mais le coût de cette simplicité se révèle sous pression.
Lorsqu'un incident de production survient et que vous devez faire un rollback rapidement, la dernière chose que vous souhaitez est d'exécuter une down migration non testée qui pourrait échouer, prendre trop de temps, ou supprimer silencieusement des données. La pression du temps, le stress et l'absence de solution de repli propre font des down migrations un pari risqué.
Les migrations rétrocompatibles éliminent ce pari. Elles vous permettent de faire un rollback de l'application indépendamment de la base de données. Elles vous donnent le temps de décider quoi faire du changement de schéma sans forcer un retour en arrière immédiat et risqué.
Une checklist pratique pour la planification du rollback de base de données
Si vous voulez éviter les scénarios de rollback douloureux, voici une courte checklist à passer en revue avant chaque migration :
- L'ancienne version de l'application peut-elle toujours s'exécuter correctement après cette migration ?
- Si la migration ajoute une colonne, l'ancien code l'ignore-t-il ?
- Si la migration supprime une colonne, l'ancien code a-t-il déjà cessé de l'utiliser ?
- Si la migration renomme ou modifie une colonne, existe-t-il une période de transition où les deux structures, ancienne et nouvelle, coexistent ?
- Existe-t-il un moyen testé et sûr d'inverser cette migration sans perte de données ?
Si vous ne pouvez pas répondre oui à toutes ces questions, votre migration comporte un risque de rollback que vous n'avez pas traité.
Ce qu'il faut retenir
Le rollback de base de données ne consiste pas seulement à revenir à une version antérieure. Il s'agit de maintenir les données intactes et cohérentes tout en garantissant que l'application peut retrouver son état précédent sans effets secondaires. La voie la plus sûre est de concevoir des migrations qui ne vous forcent pas à choisir entre le rollback de l'application et la perte de données. Intégrez la rétrocompatibilité dans chaque changement de schéma, et traitez les down migrations comme un dernier recours, pas comme une stratégie par défaut. Votre futur vous, en train de déboguer un incident à 2 heures du matin, vous remerciera.