Exécuter des migrations de base de données en production sans perdre le sommeil

Le pipeline de déploiement est vert. Les modifications de code ont été revues et approuvées. L'environnement de staging semble correct. Puis vient le moment que tout ingénieur redoute : exécuter la migration sur la base de données de production.

C'est là que se trouve le vrai risque. Une modification de schéma qui fonctionne parfaitement sur votre poste peut verrouiller une table de production pendant plusieurs minutes. Une migration de données qui s'exécute correctement avec mille lignes peut prendre des heures face à un million. Et quand les choses tournent mal, l'application commence à renvoyer des erreurs à de vrais utilisateurs.

Le problème n'est pas seulement technique. Il concerne aussi le timing, la coordination et le savoir-faire pour s'arrêter à temps.

Quand exécuter la migration

Contrairement aux déploiements d'application, que l'on peut souvent faire à n'importe quelle heure, les migrations de base de données ont des conséquences directes sur les performances des requêtes et la disponibilité des données. Le moment le plus sûr pour les exécuter est lorsque le trafic est faible. De nombreuses équipes appellent cela une fenêtre de maintenance, mais ce nom est trompeur. Une fenêtre de maintenance ne signifie pas que l'application doit être hors ligne. Cela signifie que vous avez convenu d'une période pendant laquelle vous pouvez effectuer des modifications susceptibles de provoquer des perturbations, et que vous vous êtes préparé à cette éventualité.

La vraie question est : votre migration peut-elle s'exécuter pendant que l'application continue de servir du trafic ? Si vous concevez vos migrations avec soin, la réponse est souvent oui. Mais cela dépend de la compréhension de ce que fait votre base de données lorsque vous exécutez certaines commandes.

Le problème du verrouillage

La source la plus courante de problèmes lors des migrations en production est le verrouillage. Lorsque vous exécutez une commande comme ALTER TABLE, la plupart des bases de données verrouillent la table pour empêcher d'autres modifications pendant l'opération. Si cette commande prend beaucoup de temps, chaque requête de votre application qui touche cette table attend ou échoue. Les utilisateurs commencent à voir des pages lentes ou des messages d'erreur.

Certaines bases de données offrent des moyens de réduire le verrouillage. PostgreSQL prend en charge CREATE INDEX CONCURRENTLY, qui construit un index sans bloquer les écritures. MySQL propose des options similaires pour certaines opérations. Mais toutes les modifications ne peuvent pas être effectuées sans verrou. Ajouter une colonne avec une valeur par défaut, changer le type d'une colonne ou supprimer une colonne nécessite souvent un verrou exclusif.

La clé est de savoir ce que votre base de données prend en charge avant d'écrire la migration. Consultez la documentation de votre version de base de données. Testez la migration sur une copie de vos données de production, pas seulement sur un petit échantillon. Mesurez la durée du verrou dans des conditions réalistes.

Par exemple, voici un modèle sûr pour ajouter une colonne avec une valeur par défaut dans PostgreSQL, en évitant un long verrou exclusif :

-- Étape 1 : Ajouter la colonne comme nullable (rapide, pas de réécriture de valeur par défaut)
ALTER TABLE users ADD COLUMN display_name text;

-- Étape 2 : Remplir la colonne par lots
UPDATE users SET display_name = username WHERE display_name IS NULL LIMIT 1000;
-- Répéter jusqu'à ce qu'il n'y ait plus de lignes

-- Étape 3 : Définir NOT NULL (rapide, pas de réécriture de données)
ALTER TABLE users ALTER COLUMN display_name SET NOT NULL;

Découper les grosses migrations en petites étapes

Une erreur courante est d'essayer de faire trop de choses en une seule migration. Par exemple, ajouter une nouvelle colonne, la remplir avec des données d'une autre colonne, puis supprimer l'ancienne colonne en une seule étape. Cela crée une opération de longue durée qui maintient des verrous pendant toute la durée.

Une approche plus sûre consiste à diviser le travail en plusieurs migrations plus petites :

  1. Ajouter la nouvelle colonne sans valeur par défaut.
  2. Exécuter un job en arrière-plan pour remplir la nouvelle colonne par lots.
  3. Vérifier que les données sont complètes et correctes.
  4. Supprimer l'ancienne colonne dans une migration séparée.

Chaque étape peut être vérifiée avant de passer à la suivante. Si quelque chose tourne mal à l'étape deux, vous n'avez rien perdu. Vous pouvez corriger les données et réessayer. Cette approche prend plus de temps, mais elle est beaucoup moins risquée.

Intégrer des contrôles de sécurité dans le pipeline

Votre pipeline ne doit pas exécuter les migrations aveuglément. Il doit vérifier l'état actuel de la base de données avant d'apporter des modifications. Avant le début de la migration, le pipeline peut vérifier les requêtes de longue durée. S'il y a des requêtes qui s'exécutent depuis plus de quelques secondes, la migration doit attendre. Ces requêtes peuvent détenir des verrous qui bloqueraient la migration, ou la migration pourrait les bloquer.

Le diagramme suivant illustre le processus de vérification de sécurité décrit ci-dessus :

flowchart TD A[Démarrer la migration] --> B{Trafic faible ?} B -- Oui --> C[Exécuter la migration avec analyse des verrous] B -- Non --> D[Attendre et revérifier] D --> B C --> E{Verrous libérés ?} E -- Oui --> F{Latence des requêtes normale ?} E -- Non --> G[Arrêter et notifier] F -- Oui --> H[Confirmer le succès] F -- Non --> G G --> I[Revenir en arrière si nécessaire]

Pendant la migration, le pipeline doit surveiller :

  • La durée d'exécution : combien de temps chaque instruction prend
  • Le temps d'attente des verrous : si d'autres requêtes attendent sur des verrous
  • Le taux d'erreur : toute erreur provenant de la base de données ou de l'application

Si l'un de ces indicateurs dépasse un seuil, le pipeline doit arrêter la migration et notifier l'équipe. Il ne s'agit pas d'être prudent. Il s'agit d'avoir un mécanisme clair pour empêcher un petit problème de devenir un incident de production.

Que se passe-t-il après la fin de la migration

La migration s'est terminée sans erreur. Le pipeline est vert. Mais le travail n'est pas encore terminé.

Votre application peut encore avoir d'anciennes connexions à la base de données ouvertes. Les pools de connexions mettent en cache les connexions, et ces connexions mises en cache peuvent contenir des références à l'ancien schéma. Certains ORMs mettent en cache des plans de requêtes qui ne correspondent plus au nouveau schéma. Ces problèmes n'apparaissent pas toujours immédiatement. Ils peuvent provoquer des erreurs subtiles quelques minutes ou heures plus tard.

De nombreuses équipes redémarrent l'application après une migration, ou au moins vident les anciennes connexions et en créent de nouvelles. D'autres attendent quelques minutes en surveillant les logs et les métriques avant de déclarer la migration réussie. L'approche exacte dépend de votre stack applicative et du fonctionnement de votre pool de connexions.

Checklist pratique pour les migrations en production

Avant d'exécuter une migration en production, parcourez cette checklist :

  • Testez la migration sur une copie des données de production, pas seulement sur une base de développement
  • Mesurez la durée de chaque instruction et les verrous qu'elle acquiert
  • Vérifiez que la version de la base de données prend en charge des alternatives sans verrou pour votre opération
  • Vérifiez les requêtes de longue durée avant de démarrer la migration
  • Mettez en place une surveillance du temps d'attente des verrous et du taux d'erreur pendant la migration
  • Définissez une condition d'arrêt claire : quelle métrique ou erreur déclenche un arrêt
  • Préparez un plan de retour arrière : comment annuler la migration si quelque chose tourne mal
  • Notifiez l'équipe avant et après l'exécution de la migration
  • Prévoyez de redémarrer l'application ou de rafraîchir les connexions après la migration

Ce qu'il faut retenir

Exécuter une migration de base de données en production ne consiste pas à éviter le risque. Il s'agit de comprendre le risque, de le décomposer en morceaux gérables et d'avoir des signaux clairs pour savoir quand s'arrêter. Les meilleures migrations sont celles que personne ne remarque parce qu'elles se sont déroulées sans accroc. Les deuxièmes meilleures sont celles qui ont été arrêtées tôt avant de causer des dégâts réels. Les pires sont celles qui sont allées jusqu'au bout mais ont cassé l'application d'une manière qui a mis des heures à être détectée. Construisez votre pipeline pour viser la première catégorie, mais soyez toujours prêt pour la seconde.