Quand la suppression d'une colonne en base de données fait planter la production : gérer les modifications destructrices de schéma
Vous avez une migration de base de données qui supprime une colonne inutilisée. La requête SQL est propre. La migration s'exécute sans erreur. Mais cinq minutes plus tard, les alertes se déclenchent. L'application de production génère des erreurs car un morceau de code référence encore cette colonne. Ce qui ressemblait à un simple nettoyage vient de provoquer une panne.
Ce scénario est plus courant que la plupart des équipes ne l'admettent. Le problème n'est pas la migration en elle-même. Le problème est de supposer que supprimer quelque chose de la base de données est sûr simplement parce que vous pensez que personne ne l'utilise plus.
Qu'est-ce qui rend une modification destructrice ?
Les modifications de schéma de base de données se divisent en deux catégories. Les modifications additives ajoutent quelque chose de nouveau : une nouvelle colonne, une nouvelle table, un nouvel index. Celles-ci sont généralement sûres car le code existant ignore simplement ce qu'il ne connaît pas.
Les modifications destructrices suppriment, renomment ou modifient des structures existantes. Supprimer une colonne, renommer une table, changer le type d'une colonne ou retirer une contrainte sont tous destructeurs. Le risque est simple : si une application en cours d'exécution dépend encore de cette structure, elle plantera au moment où la modification est appliquée.
Le danger est amplifié par les stratégies de déploiement modernes. Les mises à jour progressives (rolling updates) et les déploiements blue-green signifient que les anciennes et nouvelles versions de l'application s'exécutent côte à côte pendant des minutes ou des heures. Une migration destructrice qui s'exécute pendant le déploiement cassera immédiatement les anciennes instances qui servent encore le trafic.
Le modèle de migration multi-phases
L'approche la plus sûre est de ne jamais rien supprimer en une seule étape. Au lieu de cela, décomposez les modifications destructrices en plusieurs phases. Chaque phase doit être compatible avec la version de l'application en cours d'exécution à ce moment-là.
Le diagramme suivant illustre les trois phases d'un renommage sécurisé de colonne, montrant quelles versions de l'application sont compatibles à chaque étape.
Prenons l'exemple du renommage d'une colonne de status à status_code. Une migration unique qui renomme la colonne cassera tout code qui lit encore status. L'approche multi-phases ressemble à ceci :
Phase 1 : Ajoutez la nouvelle colonne sans supprimer l'ancienne. Copiez les données de l'ancienne colonne vers la nouvelle. Mettez à jour le code de l'application pour lire depuis la nouvelle colonne tout en continuant à écrire dans les deux. Déployez ce changement.
Phase 2 : Après avoir confirmé que toutes les instances de l'application utilisent la nouvelle colonne, arrêtez d'écrire dans l'ancienne colonne. Mettez à jour le code pour ne référencer que status_code. Déployez à nouveau.
Phase 3 : Une fois que vous êtes certain qu'aucun code en cours d'exécution ne touche à l'ancienne colonne, supprimez-la dans une migration séparée. Planifiez cela pendant les heures de faible trafic.
Le même modèle s'applique à la suppression de tables. Créez une vue ou une nouvelle table qui remplace l'ancienne fonctionnalité. Redirigez le code de l'application vers la nouvelle structure. Attendez qu'aucun code ne référence l'ancienne table. Puis supprimez-la.
La suppression logicielle comme filet de sécurité
Parfois, vous souhaitez supprimer des données de la vue de l'application sans les supprimer réellement de la base de données. C'est là que la suppression logicielle (soft delete) est utile.
Au lieu d'exécuter une instruction DELETE, ajoutez une colonne comme deleted_at ou is_active. L'application filtre les lignes supprimées avec une clause WHERE. Les données restent dans la table pour l'audit, la récupération ou les dépendances imprévues d'autres fonctionnalités.
La suppression logicielle est particulièrement utile lorsque vous n'êtes pas totalement sûr que les données sont encore nécessaires. Elle vous donne une marge de sécurité. Si quelque chose casse, vous pouvez restaurer la visibilité sans restauration de base de données. Le compromis est que vos tables deviennent plus volumineuses et que les requêtes doivent tenir compte du filtre. Mais pour de nombreuses équipes, ce compromis vaut la sécurité.
Gérer les contraintes avec précaution
Supprimer une contrainte comme une clé étrangère ou une contrainte d'unicité est moins risqué que de supprimer des données, mais cela a tout de même des conséquences. Les contraintes garantissent l'intégrité des données. Si votre code d'application repose sur la base de données pour empêcher les entrées en double ou les enregistrements orphelins, supprimer la contrainte peut entraîner une corruption des données.
Avant de supprimer une contrainte, auditez la base de code pour confirmer qu'aucune logique n'en dépend. Si votre base de données le permet, envisagez de désactiver la contrainte d'abord plutôt que de la supprimer. Cela vous permet de tester l'impact sans perdre définitivement la possibilité de la réactiver.
Liste de contrôle pratique pour les modifications destructrices
- Vérifiez qu'aucun code d'application en cours d'exécution ne référence la structure que vous prévoyez de supprimer. Vérifiez à la fois la version actuelle et tout déploiement en cours.
- Divisez le changement en au moins deux migrations : une pour ajouter la nouvelle structure et rediriger le code, une autre pour supprimer l'ancienne structure après une période d'attente.
- Exécutez les migrations destructrices séparément des déploiements de fonctionnalités. Ne regroupez pas la suppression d'une colonne avec la publication d'un nouveau point d'accès.
- Planifiez les modifications destructrices pendant les fenêtres de faible trafic. Même avec une planification multi-phases, les problèmes inattendus sont plus faciles à gérer lorsque moins d'utilisateurs sont affectés.
- Après avoir supprimé les anciennes structures, nettoyez les résidus comme les colonnes renommées ou les contraintes désactivées dans une migration de suivi. Mais rappelez-vous : le nettoyage est également destructeur, alors appliquez la même approche par phases.
Le principe fondamental
Ne supprimez jamais quelque chose qui pourrait encore être accédé par une application en cours d'exécution. Cela semble évident, mais c'est l'erreur la plus courante que les équipes commettent avec les migrations de base de données. La pression pour garder le schéma propre, l'hypothèse que "personne n'utilise plus ça", et le désir de livrer une migration propre au lieu de plusieurs petites poussent toutes les équipes vers des suppressions en une seule étape risquées.
Les migrations multi-phases prennent plus de temps et nécessitent plus de déploiements. Mais elles évitent le type de panne de production qui transforme un simple nettoyage de schéma en un rollback d'urgence. Un schéma propre ne vaut pas une application cassée.