Fabien Dorey
← Carnet N° 02 — — 5 min

La base de données aussi se livre

Des modifications SQL passées à la main, jusqu’en production, sans que l’exploitation soit dans la boucle. Comment Flyway a remis le schéma de la base dans le même circuit que le code — et pourquoi c’est d’abord une affaire de transparence.

Sur l’application dont j’ai hérité, le code avait son circuit : un dépôt, des versions, des livraisons. La base de données, non. Les modifications de schéma étaient passées à la main, directement sur les environnements, production comprise. Une colonne ajoutée ici, un index là, un script gardé dans un coin ou retapé de mémoire.

Les mises en production fonctionnaient. Magiquement. Ou pas toujours. Et quand elles ne fonctionnaient pas, l’équipe d’exploitation découvrait le sujet en même temps que l’incident : elle n’avait pas été impliquée, puisqu’il n’y avait rien à lui confier.

Ce n’est pas un procès. C’est une habitude qui s’installe sans bruit dans une équipe qui connaît sa base par cœur, et qui tient tant que les mêmes personnes sont là. Mais elle a un coût, et il est toujours payé le jour de la mise en production.

Ce que le SQL à la main coûte

  • Personne ne sait ce qui a été livré. L’état de la base est la somme de gestes dont il ne reste aucune trace. La question « qu’est-ce qui a changé depuis la dernière fois ? » n’a pas de réponse.
  • Les environnements dérivent. Un script passé en recette et oublié en production, un autre corrigé au vol sur un seul environnement : ce qu’on teste n’est plus ce qu’on livre.
  • Le code et la base ne sont plus synchronisés. Une version de l’appli attend une colonne ; rien ne garantit qu’elle existe là où on la déploie.
  • La production devient une affaire d’initiés. Ceux qui savent passent les scripts, les autres regardent. L’exploitation, qui porte pourtant la disponibilité, est tenue à l’écart de ce qui la menace le plus.

Le principe : un changement, un fichier

Flyway est un outil de migration de base de données, et son idée est d’une simplicité désarmante. Chaque modification du schéma est un fichier SQL, numéroté, rangé dans le dépôt à côté du code :

text
db/migration/
  V1__baseline.sql
  V2__add_delivery_note_to_orders.sql
  V3__create_order_history_table.sql
SQL
-- V2__add_delivery_note_to_orders.sql
ALTER TABLE orders ADD COLUMN IF NOT EXISTS delivery_note VARCHAR(255);

CREATE INDEX IF NOT EXISTS idx_orders_delivery_date ON orders (delivery_date);

Dans chaque base, Flyway tient une table d’historique : quelles migrations ont été jouées, quand, par qui, avec quelle empreinte. À l’exécution, il compare les fichiers à cet historique et ne joue que ce qui manque, dans l’ordre.

V1 V2 V3 Dev Recette Prod
Fig. 1 — Les mêmes fichiers, dans le même ordre, partout

La conséquence est immédiate : la production ne reçoit plus que des scripts qui ont déjà tourné, à l’identique, sur les environnements précédents. La mise en production cesse d’être une première fois.

Partir d’une base qui existe déjà

On ne met pas en place Flyway sur une page blanche. La base est là, avec son passé, et parfois avec des environnements qui ne se ressemblent plus tout à fait.

Le point de départ s’appelle une baseline : on déclare l’état actuel comme la version 1, sans rien rejouer, et tout ce qui suit passe par des migrations. C’est aussi le moment de comparer les schémas entre environnements et de résorber les écarts — un travail ingrat, mais qu’on ne fait qu’une fois.

Les règles qui vont avec

L’outil ne suffit pas. Ce qui change les choses, ce sont quelques règles tenues sans exception.

  • Plus aucun SQL de structure à la main. Si ce n’est pas dans une migration, ça n’existe pas. La règle vaut aussi pour les urgences — surtout pour les urgences.
  • Une migration jouée ne se modifie plus. Flyway vérifie l’empreinte de chaque fichier : toucher à un script déjà appliqué bloque la suite. Pour corriger, on écrit la migration suivante.
  • Les scripts sont relus comme du code. Même dépôt, même revue, même historique. Le changement de schéma voyage avec la version de l’appli qui en a besoin.
  • Un script doit pouvoir être rejoué sans dégât. IF NOT EXISTS, IF EXISTS : une migration idempotente ne casse rien si elle retombe sur une base où le changement est déjà là — ce qui arrive forcément quand on part d’environnements modifiés à la main.
  • On corrige vers l’avant. Pas de retour arrière improvisé en production : le plan de repli se prépare, s’écrit et se relit avant la livraison.

Des règles tenues par le pipeline

Une règle qui repose sur la vigilance de chacun finit par céder, un vendredi soir. Celles-ci sont donc portées par GitLab, avec deux jobs.

Le premier est un contrôle, qui tourne à chaque modification : il vérifie que les scripts respectent les règles de nommage, et qu’ils sont idempotents. Un fichier mal nommé, Flyway l’ignore en silence ; un script non rejouable, on le découvre en production. Autant que ce soit le pipeline qui le dise, avant la relecture.

Le second est le job de migration : un validate, puis un migrate.

YAML
stages: [check, migrate]

check-migrations:
  stage: check
  script:
    - ./ci/check-naming.sh db/migration       # V<version>__<description>.sql
    - ./ci/check-idempotence.sh db/migration  # safe to run twice

migrate:
  stage: migrate
  when: manual
  script:
    - flyway validate   # do the files still match what was applied?
    - flyway migrate    # apply pending migrations, in order

Le validate refuse d’aller plus loin si un script déjà appliqué a été modifié ou s’il en manque un. Le migrate ne joue que ce qui reste à jouer.

Rendre la production à ceux qui l’opèrent

Le versioning n’était que la moitié du chantier. L’autre moitié, la plus utile, a été de former l’équipe d’exploitation à exécuter ces migrations elle-même.

Concrètement, c’est elle qui lance le job de migration. Avant de livrer, elle voit exactement ce qui va être joué : la liste des scripts est dans le dépôt, relue et contrôlée. Après, le journal du job et la table d’historique disent ce qui l’a été. Elle n’exécute plus un geste qu’on lui dicte : elle opère un outil qu’elle comprend, sur une liste qu’elle peut lire.

Et pour l’équipe de développement, c’est une discipline salutaire : un script que quelqu’un d’autre va passer s’écrit avec plus de soin qu’un script qu’on passe soi-même, tard, en se disant que ça ira.

Un historique qui ne ment pas

Je n’avais pas anticipé à quel point ce dossier de migrations deviendrait une documentation. Chaque fichier dit ce qui a changé, son nom dit pourquoi, et l’historique du dépôt dit qui, quand, et pour quelle livraison. La table d’historique, elle, dit ce qui tourne réellement sur chaque environnement.

Contrairement à un schéma dessiné dans un wiki, celui-là ne peut pas être en retard : c’est lui qui fait la base.

Ce que j’en retiens

La production est l’affaire de tous.

Pas celle de quelques-uns qui savent, ni celle d’une équipe qui subit. Livrer, c’est être transparent sur ce que l’on livre : pouvoir le montrer avant, le vérifier après, et le retrouver dans six mois.

Flyway n’a rien de spectaculaire. C’est un dossier de fichiers numérotés, une table et deux jobs. Mais c’est souvent ça, une bonne pratique : rendre visible ce qui se faisait dans l’ombre, et confier à chacun la part qui lui revient.