CodeGym /Cours /Docker for Spring /Erreur de migration dans les logs

Erreur de migration dans les logs

Docker for Spring
Niveau 18, Leçon 2
Disponible

1. Broken migration : l’historique du schéma est corrompu

Lorsque vous venez de connecter l’application à PostgreSQL, votre esprit vit encore dans le paradigme « l’essentiel est que la base réponde au ping ». Mais après l’apparition de Flyway, une deuxième question, plus mature, se pose : « le schéma de la base est-il le bon ?». Une migration cassée (broken migration) est une situation où la base est accessible, mais une modification spécifique du schéma (fichier SQL) ne peut pas être appliquée, et l’application échoue honnêtement car il n’est pas sûr de continuer.

Il est important de saisir cette différence au niveau des sensations. Une erreur de connexion, c’est comme si vous arriviez au bureau et que la porte était fermée : vous n’entrez tout simplement pas. Une migration cassée, c’est comme si la porte était ouverte, vous entriez, arriviez à la salle de réunion, et au lieu d’une table, il y a une boîte avec l’inscription « à monter selon les instructions », et… les instructions contiennent une faute de frappe. Dans la pile Compose, cela ressemble extérieurement à la même chose : le conteneur app meurt. Mais la cause est fondamentalement différente : réseau/accès contre validité du schéma.

Une autre raison pour laquelle un débutant se trompe : le conteneur PostgreSQL reste healthy, car de son point de vue tout va bien — il est démarré et prêt à accepter les connexions. L’application, elle, meurt parce que Flyway a reçu une erreur lors de l’exécution du SQL. Il est utile d’accepter à l’avance : « base healthy » ne signifie pas « application healthy » s’il y a une couche de migration entre les deux.

Ici, le modèle strict de Flyway nous aide beaucoup : il voit le monde à travers des fichiers versionnés et l’historique de leur application dans la base. Par conséquent, nous lisons un démarrage cassé non pas comme un « Spring fait encore du bruit » général, mais comme un échec d’une étape spécifique Vx__... : il a un nom de fichier, une version et une place dans flyway_schema_history.

2. L’étape Flyway dans le démarrage de Spring Boot

Le diagnostic d’une migration cassée ne commence pas par la magie ni par « eh bien, redémarrons encore une fois ». Il commence par une habitude simple : comprendre si l’application est arrivée jusqu’à Flyway. Spring Boot démarre assez « bruyamment », mais si vous connaissez quelques repères, vous voyez rapidement à quelle étape tout s’est cassé : avant la connexion à la base, pendant les migrations, ou après celles-ci.

Simplifions-nous la vie et fixons la séquence « idéale » de démarrage en mode postgres. Les logs peuvent différer dans les détails, mais la structure est globalement similaire : nous voyons le démarrage de l’application, le profil actif, l’initialisation de la datasource, puis le bloc Flyway, et seulement après — le « Started … » final.

Ci-dessous, un extrait de logs de référence conditionnel (n’ayez pas peur : nous n’avons pas besoin de mémoriser chaque ligne, l’important est de reconnaître les moments clés) :

# Repères visuels : profil -> Hikari (connecté) -> Flyway (appliqué) -> Started (contexte levé)
app-1  | Starting CatalogApplication using Java 25
app-1  | The following 1 profile is active: "postgres"
app-1  | HikariPool-1 - Start completed.
app-1  | Flyway Community Edition 11.x by Redgate
app-1  | Database: jdbc:postgresql://postgres:5432/catalog (PostgreSQL 18)
app-1  | Successfully applied 2 migrations to schema "public"
app-1  | Started CatalogApplication in 2.345 seconds

Pour ne pas garder cela en tête comme une « incantation », il est pratique de penser ainsi : la ligne sur le profil actif montre que nous sommes dans le bon mode ; la ligne sur Hikari indique « la connexion à la base est établie » ; les lignes Flyway indiquent « les migrations sont arrivées et appliquées » ; la ligne finale Started indique « le contexte est levé, la couche web est vivante ».

Voici un petit tableau « antisèche des repères » que nous cherchons visuellement :

Repère dans le démarrage À quoi il ressemble dans les logs Ce que cela signifie
Profil profile is active: "postgres" Nous sommes bien en mode PostgreSQL avec migrations
Connexion à la BDD HikariPool... Start completed L’application a pu se connecter à PostgreSQL
Étape Flyway Flyway ... / Successfully applied ... migrations Nous sommes arrivés aux migrations (ou avons échoué sur elles)
Final du démarrage Started ... L’application est complètement démarrée

Le plus utile : si vous ne voyez pas du tout de lignes Flyway, il est très probable que ce ne soit pas une migration cassée. Vous avez très probablement échoué plus tôt : sur la connexion à la base, sur la configuration, sur le profil.

3. Erreur de connexion : signes dans les logs

Quand quelque chose ne va pas avec la connexion, Flyway n’a le plus souvent même pas la chance de « se casser élégamment ». D’abord, Spring Boot doit créer un DataSource, ouvrir une connexion JDBC, et ensuite seulement Flyway commence son travail. Si la connexion ne s’ouvre pas, les migrations ne démarrent tout simplement pas, et l’application échoue à l’étape de la datasource. Extérieurement, cela ressemble aussi à « le conteneur app s’est arrêté », mais le log est complètement différent.

Le cas « pédagogique » le plus fréquent est un mauvais host dans l’URL JDBC. Dans Compose, il s’agit généralement d’une tentative d’accès à localhost ou à un nom inventé. Par exemple :

# application-postgres.yml (exemple de mauvaise configuration)
spring:
  datasource:
    # Dans Compose, "localhost" est à l’intérieur du conteneur, pas votre hôte (donc ici doit être le service postgres)
    url: jdbc:postgresql://wrong-host:5432/catalog

Les logs dans un tel cas contiennent généralement des mots comme CannotGetJdbcConnectionException, UnknownHostException ou Connection refused. À peu près comme ça :

# Idée clé : c’est un échec au niveau de la connexion, pas d’une migration spécifique (pas de Migration V... failed)
app-1  | ERROR --- Application run failed
app-1  | org.springframework.jdbc.CannotGetJdbcConnectionException:
app-1  | Failed to obtain JDBC Connection
app-1  | Caused by: java.net.UnknownHostException: wrong-host

Le deuxième classique est un mot de passe/utilisateur incorrect. Ici, la connexion arrive jusqu’à PostgreSQL, mais le serveur dit « non ». Dans les logs de l’application, vous verrez password authentication failed, et dans les logs de PostgreSQL, il y aura souvent FATAL.

app-1  | Caused by: org.postgresql.util.PSQLException:
app-1  | FATAL: password authentication failed for user "catalog"

Si on compare cela avec une migration cassée, la différence est assez nette : en cas de problèmes de connexion, le log ne vous dit généralement pas « Migration V3__... failed ». Il dit « échec de connexion », et il n’y a aucun lien avec un fichier SQL spécifique. C’est le principal marqueur visuel.

Un autre indice : lors d’un problème de connexion, il n’y a souvent pas de ligne Successfully applied ... migrations, car on n’est tout simplement pas arrivé jusque-là. Parfois, il y aura une ligne Flyway ... car la bibliothèque s’initialise, mais ensuite elle échouera en tentant de se connecter — et il n’y aura toujours pas de message concernant une version de migration spécifique.

4. Migration cassée dans les logs de l’application

Une migration cassée est un type d’erreur « agréable » (dans un sens étrange d’ingénieur) : Flyway indique généralement assez précisément ce qui ne va pas. Mais pour que ce soit « agréable », il faut savoir où regarder. Dans le chaos du démarrage, un débutant s’accroche souvent à la dernière ligne ou à une grosse stack trace, alors que Flyway a presque toujours un « passeport d’erreur » court : version, nom du fichier, message de la base et parfois même l’instruction SQL.

Quand Flyway échoue, vous voulez extraire des logs trois signaux principaux. Ils sont presque toujours présents : quelle migration, où est le fichier, quelle erreur SQL. Pour cela, pas besoin de lire toute la stack trace : vous devez trouver le bloc qui commence par des mots comme Migration ... failed.

Voici à quoi ressemble typiquement un tel bloc (exemple conditionnel, mais très proche de la réalité) :

# « Passeport » d’une migration cassée : version/fichier -> SQL State -> Message -> Location -> Statement
app-1  | Migration V3__add_status.sql failed
app-1  | SQL State  : 42P01
app-1  | Message    : ERROR: relation "catalog_items" does not exist
app-1  | Location   : db/migration/V3__add_status.sql (line 1)
app-1  | Statement  : ALTER TABLE catalog_items ADD COLUMN status VARCHAR(32);

Analysons ce qui est utile ici, sans plonger dans le « monde DBA ».

Sous forme de tableau, cela donne :

Champ dans le log Flyway Ce que cela signifie en termes simples Comment l’utiliser dans le diagnostic
Migration V3__... Version et nom du fichier SQL Vous savez exactement quel fichier ouvrir dans le dépôt
Location ... (line N) Où se trouve le fichier et à quelle ligne est l’erreur Vous allez directement à l’endroit concerné, sans chercher des yeux
Message ... Texte de l’erreur PostgreSQL C’est le « pourquoi la base n’a pas pu exécuter la commande »
Statement ... Instruction SQL qui était exécutée Vous voyez la commande spécifique, parfois même sans ouvrir le fichier
SQL State Code de la classe d’erreur Utile si l’erreur est courte/peu claire, mais généralement pas obligatoire

L’astuce clé : cherchez d’abord le « passeport d’erreur » Flyway, et seulement ensuite lisez la stack trace. La stack trace est presque toujours énorme, et si vous êtes débutant, elle vous écrase émotionnellement (« je ne deviendrai jamais programmeur, je vais devenir agriculteur »). Le bloc Flyway, au contraire, vous aide à passer rapidement à l’action : ouvrir le fichier, voir le SQL, corriger.

Il existe un autre scénario de migration souvent confondu avec « le SQL est cassé », alors qu’il s’agit en fait d’un autre type de migration cassée : vous avez modifié un fichier de migration déjà appliqué. Flyway n’aime pas cela, car les migrations sont un historique, et l’historique ne peut pas être réécrit comme si de rien n’était. Au lieu d’une erreur SQL, vous verrez un message concernant la validation et une divergence de checksum.

app-1  | Validate failed: Migration checksum mismatch for migration version 1
app-1  | -> Applied to database : 123456789
app-1  | -> Resolved locally    : 987654321
app-1  | Either revert the changes to the migration, or run repair to update the schema history.

Et c’est un marqueur de diagnostic très important : ici l’erreur n’est pas que PostgreSQL « n’a pas compris le SQL ». L’erreur est que la base se souvient de la migration V1 sous une forme, et votre dépôt la montre sous une autre. C’est aussi une migration cassée, mais le traitement est différent (et dans le cadre du cours, nous nous en tiendrons au principe simple : ne pas modifier les migrations déjà appliquées, mais en ajouter une nouvelle).

5. Logs PostgreSQL : confirmation des erreurs SQL

Les logs de l’application sont votre source principale de vérité sur le fichier qui a cassé. Mais les logs PostgreSQL sont un utile « deuxième avis » : ils confirment que l’erreur a vraiment eu lieu côté exécution SQL, et la montrent dans les termes de la base elle-même. C’est particulièrement pratique lorsque les logs de l’application sont confus ou que vous voulez vous assurer que le problème n’est pas de nature « réseau ».

Dans la réalité de Compose, le plus pratique est de consulter les logs PostgreSQL via docker compose logs. Souvent, il suffit de voir non pas la totalité, mais la fin : les 50 à 200 dernières lignes. Par exemple :

# Consultez les dernières lignes des logs du service postgres pour voir rapidement ERROR/STATEMENT ou FATAL
docker compose logs --tail=100 postgres

Si l’erreur est « la table n’existe pas » ou « erreur de syntaxe », PostgreSQL la logge généralement de manière à ce que le mot-clé ERROR et la ligne STATEMENT soient visibles. Exemple :

postgres-1  | ERROR:  relation "catalog_items" does not exist at character 13
postgres-1  | STATEMENT:  ALTER TABLE catalog_items ADD COLUMN status VARCHAR(32);

Pourquoi est-ce utile ? Parce que vous voyez que la base a accepté la connexion (ce n’est donc pas un Connection refused), mais n’a pas pu exécuter la commande spécifique. C’est la « nature SQL » du problème.

En revanche, si le problème est un mot de passe incorrect, PostgreSQL le logge dans le style « accès refusé », et le mot-clé est généralement FATAL :

postgres-1  | FATAL:  password authentication failed for user "catalog"

Si le problème est un mauvais host (wrong-host), PostgreSQL ne va probablement rien écrire du tout concernant votre tentative : la requête ne lui est pas parvenue. Et c’est aussi un signal de diagnostic. Lorsque vous voyez UnknownHostException dans les logs de l’application et rien dans les logs de la base, cela indique presque toujours un problème au niveau réseau/nom.

L’habitude pratique ici est la suivante : vous lisez d’abord docker compose logs app et trouvez ce qui a cassé (version et fichier). Ensuite, vous lisez docker compose logs postgres pour confirmer comment la base l’a perçu. Si vous commencez directement par les logs PostgreSQL, vous verrez « relation does not exist », mais vous ne verrez pas que cela venait de V3__add_status.sql. Or, c’est précisément cela qui est important : nous corrigeons le dépôt, pas « la base spécifique sur le PC de Pierre ».

6. Matrice des symptômes et arbre de décision

Lorsque vous avez deux services dans votre pile, vous voulez disposer d’un « tableau des différences » clair pour ne pas sauter entre les hypothèses. Voici une matrice compacte qui aide à classer rapidement le problème. Il n’y a pas de « traitement universel », mais elle répond bien à la question : « est-ce une migration ou une connexion ?».

Classe du problème Ce qui arrive à app Élément clé dans les logs de app Ce qu’il y a dans les logs de postgres Racine typique
Erreur de connexion app échoue presque immédiatement CannotGetJdbcConnectionException, UnknownHostException, Connection refused Souvent vide (si le host n’est pas le bon) Mauvais host/port, localhost à l’intérieur de Compose
Erreur d’authentification app échoue lors de l’initialisation de la datasource password authentication failed / PSQLException: FATAL FATAL: password authentication failed... Mauvais SPRING_DATASOURCE_USERNAME/PASSWORD
Mauvais profil/mode app peut démarrer, mais « comportement incorrect » Dans les logs, les profils ne sont pas les bons (standalone au lieu de postgres) PostgreSQL peut ne pas être nécessaire du tout Mauvais SPRING_PROFILES_ACTIVE
Migration cassée (SQL) app démarre, puis échoue Migration V... failed, Location db/migration/... ERROR: ... + STATEMENT: ... Erreur dans le fichier SQL : table/colonne, syntaxe, contraintes
Migration cassée (checksum) app échoue au démarrage, souvent sans exécuter de SQL Validate failed, checksum mismatch Généralement rien de « particulier » Modification de V1/V2 déjà appliqué

Si vous voulez encore plus simple, vous pouvez garder en tête un petit arbre de décision. Il ne remplace pas l’attention, mais il permet de sortir du mode chaotique :

flowchart TD
    A["Le conteneur app a échoué / redémarre"] --> B["Regardons docker compose logs app"]
    B --> C{"Y a-t-il des lignes Flyway Migration V... failed ?"}
    C -->|Non| D{"Y a-t-il CannotGetJdbcConnection UnknownHost/Refused/FATAL ?"}
    D -->|Oui| E["C’est une connexion/authentification : on corrige datasource env/url"]
    D -->|Non| F["Vérifier les profils et la config SPRING_PROFILES_ACTIVE, datasource URL"]
    C -->|Oui| G["C’est une migration cassée : on regarde la version, le fichier, le statement"]
    G --> H["Confirmer avec docker compose logs postgres ERROR/STATEMENT"]

Remarquez que cet arbre ne commence pas par la base ni par « redémarrons encore une fois », mais par les logs de app. Car c’est app qui sait à quelle étape du démarrage il se trouvait, et c’est app qui, via Flyway, indique la version et le nom du fichier.

7. Scénario : migration V3 avec erreur

Pour que tout cela ne reste pas une abstraction, il est utile de vivre mentalement un court scénario avec notre Container-Ready Catalog Service. Imaginons que nous voulions étendre la table catalog_item et ajouter une colonne status. Nous créons un nouveau fichier de migration, comme convenu dans la leçon précédente : nouvelle version = nouveau fichier.

Mais supposons qu’au moment du « copier-coller » nous nous soyons trompés et ayons écrit le nom de la table au pluriel. Cela donne ce fichier :

-- src/main/resources/db/migration/V3__add_status.sql
-- Important : la table doit exister dans le schéma actuel, sinon la migration échouera sur ALTER TABLE
ALTER TABLE catalog_items
ADD COLUMN status VARCHAR(32);

À première vue, le SQL a même l’air correct. Le problème est que dans notre schéma, la table s’appelle catalog_item (au singulier). Flyway démarre, prend une connexion à la base, voit qu’il y a une migration V3 « non appliquée » et tente de l’exécuter. PostgreSQL répond : « relation does not exist ».

Et c’est là que commence le plus important : vous ne devez pas deviner ce qui s’est passé. Vous devez lire les logs et voir l’endroit exact.

Un extrait typique des logs de app dans une telle situation ressemblera à ceci :

# Regardons : quelle migration (V3) + où est le fichier + ce qu’a dit la base + quel statement était exécuté
app-1  | Flyway Community Edition 11.x by Redgate
app-1  | Migrating schema "public" to version "3 - add status"
app-1  | Migration V3__add_status.sql failed
app-1  | Message    : ERROR: relation "catalog_items" does not exist
app-1  | Location   : db/migration/V3__add_status.sql (line 1)
app-1  | Statement  : ALTER TABLE catalog_items ADD COLUMN status VARCHAR(32);
app-1  | Application run failed

Ici, tout ce dont nous avons besoin est déjà présent. Nous voyons la version V3, le nom du fichier V3__add_status.sql, et même le statement spécifique. Nous ne « déboguons pas Docker », nous ne « soignons pas le réseau », nous ne touchons pas à depends_on ni à pg_isready (ils ont déjà fait leur travail : la base était prête). Nous corrigeons le SQL dans le dépôt.

La migration corrigée doit être celle-ci :

-- src/main/resources/db/migration/V3__add_status.sql
-- Correction : utilisons le vrai nom de la table du schéma (singulier)
ALTER TABLE catalog_item
ADD COLUMN status VARCHAR(32);

Et ici, il est utile de faire une autre observation qui fait gagner des heures : c’est le fichier de migration qu’il faut corriger, car c’est la source de vérité. Si vous « corrigez la base à la main » (par exemple, en vous connectant en psql et en créant la table catalog_items juste pour que la migration réussisse), vous transformez votre base locale en un flocon de neige unique. Cela « fonctionnera » sur votre machine, mais pas chez votre collègue. Dans le cadre du cours, nous luttons justement contre cet anti-pattern, car il détruit la reproductibilité de l’environnement.

L’étape suivante normale après un tel échec est de sauvegarder la correction dans le dépôt et de relancer la pile. Pour notre baseline PostgreSQL, cela se fait généralement sur le même volume : le DDL non réussi est annulé, et Flyway réessaie simplement d’appliquer la migration non passée. Supprimer le volume n’a de sens non pas comme un remède universel, mais lorsque vous avez besoin d’une relecture propre de tout l’historique des migrations et du seed baseline.

8. Erreurs typiques lors du diagnostic d’une migration cassée

Lorsque vous rencontrez des migrations dans une pile Compose pour la première fois, le cerveau essaie souvent de traiter un nouveau problème avec de vieux remèdes. C’est normal : nous faisons tous cela jusqu’à ce que nous prenions quelques claques. Mais il vaut mieux les prendre pendant le cours que le vendredi soir sur un projet professionnel.

Erreur n°1 : appeler toute erreur de démarrage une « erreur Flyway ».
Souvent, le mot Flyway apparaît dans les logs, et l’étudiant enregistre dans sa tête « la migration est cassée ». En pratique, la moitié de ces cas sont simplement une connexion : mauvais host, localhost dans Compose, mot de passe. La bonne habitude ici est de chercher spécifiquement le bloc Migration V... failed ou Validate failed. S’ils sont absents, alors les migrations ne sont probablement pas en cause.

Erreur n°2 : commencer le diagnostic par les logs PostgreSQL plutôt que par les logs de l’application.
PostgreSQL écrit honnêtement « relation does not exist », mais il ne vous dira pas que cela venait de V3__add_status.sql, à quelle ligne, et quelle version de migration Flyway essayait d’appliquer. L’application connaît le contexte, PostgreSQL ne connaît que le fait de l’exécution du statement. Si vous commencez par la base, vous perdez presque toujours le « chemin de retour vers le fichier » le plus important.

Erreur n°3 : essayer de corriger la base locale à la main plutôt que de corriger la migration.
C’est la voie la plus insidieuse, car elle donne une dopamine rapide : « eh ben ça marche ! ». Mais vous obtenez un décalage entre le dépôt et la réalité de la machine spécifique. Une semaine plus tard, vous oubliez que vous avez « bidouillé à la main » et commencez à croire à la mystique de Docker. La source de vérité est le fichier SQL de migration. Corrigeons-le, ne « fourrons pas une béquille à la base ».

Erreur n°4 : reconstruire l’image comme première réaction à une erreur de migration.
En cas de migration cassée, le problème n’est presque jamais dans le Dockerfile, les couches ou ENTRYPOINT. C’est une erreur SQL ou d’historique de migration. Reconstruire l’image, c’est comme réparer un vélo en changeant la couleur du cadre : parfois agréable, mais sans rapport avec le problème. Commencez par docker compose logs app, trouvez le fichier, corrigez la migration, et seulement ensuite relancez.

Erreur n°5 : ignorer le checksum mismatch et le « traiter » comme un bogue SQL ordinaire.
Si vous avez modifié V1__init.sql après qu’il a déjà été appliqué à la base, Flyway peut échouer sur validate. Le débutant ouvre le fichier, voit « un SQL apparemment normal » et commence à corriger n’importe quoi au hasard. Or, le sens de l’erreur est différent : la base se souvient d’une version de V1, le dépôt d’une autre. Dans le projet d’apprentissage, nous essayons de ne pas tomber dans cet état : les nouvelles modifications du schéma sont formalisées dans de nouvelles versions.

1
Mission
Docker for Spring, niveau 18, leçon 2
Bloqué
Migration brisée et diagnostic par les logs
Migration brisée et diagnostic par les logs
1
Mission
Docker for Spring, niveau 18, leçon 2
Bloqué
Erreur de connexion, pas une erreur de migration
Erreur de connexion, pas une erreur de migration
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION