Pourquoi un changement de schéma, même minime, peut faire planter votre base de production

Vous avez une application en production. Elle sert des milliers d'utilisateurs chaque minute. Un matin, vous décidez d'ajouter une seule colonne à une table de base de données. Juste une colonne. La modification semble anodine sur le papier. Mais quelques instants après le début de la migration, les utilisateurs commencent à voir des erreurs. Les requêtes expirent. Les nouvelles inscriptions échouent. Votre équipe se démène pour annuler la modification.

Ce scénario se produit plus souvent que la plupart des ingénieurs ne le pensent. Une modification de schéma qui semble triviale sur le portable d'un développeur peut mettre un système de production à genoux. Comprendre pourquoi cela se produit est essentiel pour quiconque déploie des modifications de base de données en même temps que du code applicatif.

La différence fondamentale entre le code et le schéma

Lorsque vous modifiez du code applicatif, l'effet est relativement limité. Une nouvelle version remplace l'ancienne. Si quelque chose tourne mal, vous pouvez déployer la version précédente et rétablir le fonctionnement normal. Le risque est réel, mais la voie de récupération est simple.

Les modifications de schéma de base de données ne fonctionnent pas de cette façon. Lorsque vous modifiez la structure d'une table, vous modifiez les fondations sur lesquelles repose chaque instance d'application en cours d'exécution. Il n'y a pas d'« échange » propre entre l'ancien et le nouveau schéma. L'ancien schéma disparaît dès que la migration se termine. Si quelque chose se casse, annuler la modification du schéma peut être plus complexe et plus risqué que la modification elle-même.

Cette asymétrie est la cause profonde de nombreux incidents de production qui remontent à des modifications de base de données apparemment mineures.

Un petit ajout de colonne qui cause de gros problèmes

Prenons un exemple concret. Vous avez une table users avec une colonne email définie comme varchar(255). Vous décidez d'augmenter la limite à varchar(500). C'est un simple changement de type de colonne. À quel point cela pourrait-il être grave ?

Pendant la migration, la base de données peut avoir besoin de verrouiller la table pour restructurer la colonne. Pendant que ce verrou est maintenu, aucune application ne peut lire ou écrire dans la table users. Si votre application gère des centaines de requêtes par seconde, même quelques secondes de verrouillage de table peuvent provoquer une cascade de timeouts et de requêtes échouées. Les utilisateurs rencontrent des erreurs. Les alertes de surveillance se déclenchent. L'équipe panique.

Considérons maintenant l'ajout d'une nouvelle colonne phone_number à la même table. La migration ajoute la colonne avec une contrainte NOT NULL et sans valeur par défaut. Les instances d'application exécutant l'ancien code ne savent pas que cette colonne existe. Lorsqu'elles exécutent une instruction INSERT qui omet la nouvelle colonne, la base de données rejette la requête. Soudainement, les nouvelles inscriptions d'utilisateurs cessent de fonctionner sur toutes les instances exécutant encore l'ancien code. La modification consistait à ajouter une colonne. L'impact a été une panne complète des inscriptions.

Voici le SQL qui provoquerait la panne décrite ci-dessus :

-- Risqué : verrouille toute la table users, bloquant toutes les lectures et écritures
ALTER TABLE users ADD COLUMN phone_number VARCHAR(20) NOT NULL;

-- Alternative plus sûre : ajouter la colonne sans NOT NULL d'abord,
-- puis faire un backfill, puis ajouter la contrainte avec un délai d'attente de verrouillage
ALTER TABLE users ADD COLUMN phone_number VARCHAR(20);

-- Backfill par lots (le code applicatif gère les valeurs manquantes)
UPDATE users SET phone_number = 'unknown' WHERE phone_number IS NULL;

-- Ajouter NOT NULL avec un délai d'attente de verrouillage pour éviter un blocage indéfini
SET lock_timeout = '5s';
ALTER TABLE users ALTER COLUMN phone_number SET NOT NULL;

La première instruction verrouille la table pendant toute la durée de l'opération. Sur une grande table, cela peut prendre des minutes, provoquant des timeouts en cascade sur toutes les instances d'application.

Les changements de type qui cassent les requêtes en silence

Certaines modifications de schéma semblent sûres mais modifient subtilement le comportement des requêtes. Changer une colonne de clé primaire de INT à BIGINT est un exemple courant. L'application approche de la limite d'entier, donc le changement est nécessaire. Mais pendant le processus de conversion, les requêtes qui reposent sur l'index de cette colonne peuvent devenir lentes ou cesser complètement d'utiliser l'index. La base de données peut avoir besoin de réécrire toute la table et tous ses index. Pour une grande table, cela peut prendre des minutes ou des heures.

Même après la fin de la conversion, le code applicatif peut avoir des hypothèses sur le type de données. Le code qui formate l'ID pour l'affichage, le transmet à une API externe ou l'utilise dans des opérations arithmétiques pourrait se casser silencieusement. La modification du schéma était correcte, mais les hypothèses intégrées dans le code applicatif ne l'étaient pas.

Supprimer des colonnes inutilisées est également risqué

Supprimer une colonne qui semble inutilisée dans l'application principale semble être un nettoyage sûr. Mais les bases de données ont rarement un seul consommateur. Un job batch qui s'exécute chaque nuit peut lire cette colonne pour des rapports. Un service legacy dont personne ne se souvient peut l'interroger. Une équipe de data science peut avoir un script qui la récupère pour analyse.

Au moment où vous supprimez la colonne, tous ces consommateurs tombent en panne. Le rapport nocturne échoue. Le service legacy commence à générer des erreurs. Le pipeline de data science cesse de produire des résultats. Ce qui ressemblait à une opération de nettoyage s'est transformé en un incident multi-équipes.

Pourquoi les modifications de schéma sont des changements cassants

Dans le code applicatif, un changement cassant est généralement évident : vous supprimez une fonction, modifiez une signature de méthode ou modifiez le format de réponse d'une API. Dans les bases de données, les changements cassants sont plus difficiles à détecter car la base de données est une ressource partagée avec de nombreux consommateurs invisibles.

Une seule table de base de données peut être accédée par :

  • L'application principale
  • Les processeurs de jobs en arrière-plan
  • Les outils de reporting
  • Les pipelines d'analyse de données
  • Les services legacy
  • Les requêtes ad-hoc des équipes d'exploitation
  • Les intégrations tierces

Chaque consommateur a ses propres hypothèses sur le schéma. Un changement qui est sûr pour l'application principale peut casser un script de reporting qui s'exécute une fois par mois. Comme ce script s'exécute rarement, la panne peut passer inaperçue pendant des semaines.

Le principe fondamental

Il n'existe pas de véritable petit changement de schéma. Chaque modification de la structure d'une base de données est une opération coordonnée qui nécessite une planification, des tests et une exécution minutieuse. La taille du changement en termes de lignes de code de migration n'est pas corrélée à l'ampleur de l'impact potentiel.

Liste de contrôle pratique avant tout changement de schéma

Avant d'exécuter une migration en production, vérifiez ces points :

  • Connaissez-vous toutes les applications, services et scripts qui accèdent à cette table ?
  • Pouvez-vous exécuter la migration sans verrouiller la table en écriture ?
  • Le changement casse-t-il des requêtes existantes ou des hypothèses applicatives ?
  • Les anciens et nouveaux codes applicatifs peuvent-ils coexister avec le nouveau schéma ?
  • Avez-vous un plan de rollback testé qui n'implique pas de perte de données ?
  • Avez-vous vérifié les transactions longues qui pourraient entrer en conflit avec la migration ?
  • Existe-t-il un tableau de bord de surveillance qui vous montrera les erreurs de requête immédiatement après la migration ?

Ce que cela signifie pour votre processus de déploiement

Les modifications de schéma de base de données nécessitent une stratégie de déploiement différente de celle des modifications de code applicatif. Elles doivent être réversibles, rétrocompatibles lorsque c'est possible, et testées avec des volumes de données réalistes. Elles doivent également être coordonnées avec toutes les équipes qui dépendent de la base de données.

Traitez chaque modification de schéma comme une opération à haut risque, quelle que soit sa taille apparente. La colonne que vous ajoutez aujourd'hui pourrait provoquer une panne demain. Le type que vous modifiez pourrait casser un rapport la semaine prochaine. La table que vous supprimez pourrait être celle dont dépend le script d'un collègue.

Planifiez vos déploiements de base de données avec le même soin que vous accorderiez à un changement d'infrastructure critique. Parce que c'est exactement ce qu'ils sont.