JavaRush /Курси /Docker for Spring /Помилка міграції в логах

Помилка міграції в логах

Docker for Spring
Рівень 18 , Лекція 2
Відкрита

1. Broken migration: зламалася історія схеми

Коли ви щойно підʼєднали застосунок до PostgreSQL, мозок ще живе в парадигмі «головне — щоб база відповідала на ping». Але після появи Flyway виникає вже інше, доросліше запитання: «А схема бази та сама?». Broken migration — це ситуація, коли база доступна, але конкретну зміну схеми, тобто SQL-файл, не вдається застосувати. Тоді застосунок чесно падає, бо далі працювати небезпечно.

Важливо відчути цю різницю на рівні інтуїції. Помилка підʼєднання — це ніби ви прийшли в офіс, а двері зачинені: всередину ви так і не потрапили. Broken migration — це коли двері відчинені, ви зайшли, дійшли до переговорної, а там замість столу стоїть коробка з написом «зібрати за інструкцією», і... інструкція виявилася з помилкою. Усередині Compose-стека це зовні дуже схоже: контейнер app завершує роботу. Але причина принципово інша: мережа або доступ проти коректності схеми.

Ще одна причина, чому новачок плутається: контейнер PostgreSQL лишається у стані healthy, бо з його точки зору все чудово — він запущений і готовий приймати зʼєднання. А от застосунок гине, бо Flyway отримав помилку під час виконання SQL. У такому місці корисно відразу прийняти: «healthy база» не означає «healthy застосунок», якщо між ними є міграційний шар.

Тут нам дуже допомагає сувора модель Flyway: він бачить світ через версійовані файли та історію їх застосування в базі. Тому невдалий запуск ми читаємо не як загальне «Spring знову шумить», а як збій конкретного кроку Vx__...: у нього є імʼя файла, версія і місце в flyway_schema_history.

2. Етап Flyway під час старту Spring Boot

Діагностика broken migration починається не з магії і не з «ну давайте ще раз перезапустимо». Вона починається з простого правила: зʼясувати, чи дійшов застосунок до Flyway. Spring Boot стартує доволі «шумно», але якщо ви знаєте кілька орієнтирів, то швидко бачите, на якому кроці все зламалося: до підʼєднання до бази, на міграціях чи вже після них.

Щоб спростити життя, зафіксуймо «ідеальну» послідовність старту в postgres-режимі. Логи можуть відрізнятися деталями, але загальна структура однакова: ми бачимо старт застосунку, активний профіль, ініціалізацію datasource, потім блок Flyway, і лише після цього — фінальне «Started …».

Нижче — умовний еталонний фрагмент логів. Не хвилюйтеся: не потрібно запамʼятовувати кожен рядок, нам важливо впізнавати вузлові моменти:

# Орієнтири на око: профіль -> Hikari (підʼєдналися) -> Flyway (застосували) -> Started (контекст піднявся)
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

Щоб не тримати це в голові як «заклинання», зручно думати так: рядок про активний профіль показує, що ми взагалі в потрібному режимі; рядок про Hikari каже «зʼєднання з базою піднято»; рядки Flyway кажуть «міграції відпрацювали успішно»; фінальний рядок Started каже «контекст піднято, вебрівень живий».

Ось компактна табличка-шпаргалка з орієнтирами, які ми шукаємо очима:

Орієнтир у старті Як виглядає в логах Що це означає
Профіль profile is active: "postgres" Ми справді працюємо в режимі з PostgreSQL та міграціями
Зʼєднання з БД HikariPool... Start completed Застосунок зміг підʼєднатися до PostgreSQL
Flyway-етап Flyway ... / Successfully applied ... migrations Ми дійшли до міграцій або впали саме на цьому етапі
Фінал старту Started ... Застосунок повністю піднявся

Найкорисніша ознака така: якщо ви взагалі не бачите рядків Flyway, то дуже ймовірно, що це не broken migration. Скоріше за все, ви впали раніше: на підʼєднанні до бази, на конфігурації або на профілі.

3. Помилка підʼєднання: ознаки в логах

Коли щось не так із підʼєднанням, Flyway найчастіше навіть не отримує шансу «зламатися красиво». Спочатку Spring Boot має створити DataSource, відкрити JDBC-зʼєднання, і лише потім Flyway починає свою роботу. Якщо зʼєднання не відкривається, міграції просто не стартують, а застосунок падає на етапі datasource. Ззовні це теж виглядає як «контейнер app завершився», але лог зовсім інший.

Найчастіший навчальний варіант — неправильний host у JDBC URL. У Compose це зазвичай спроба звернутися до localhost або до вигаданого імені. Наприклад:

# application-postgres.yml (приклад неправильної конфігурації)
spring:
  datasource:
    # У Compose "localhost" — це всередині контейнера, а не ваш хост (тому тут має бути сервіс postgres)
    url: jdbc:postgresql://wrong-host:5432/catalog

Логи в такому разі зазвичай містять слова на кшталт CannotGetJdbcConnectionException, UnknownHostException або Connection refused. Приблизно так:

# Ключова думка: це падіння на рівні зʼєднання, а не конкретної міграції (немає 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

Друга класика — неправильний пароль або користувач. Тут запит доходить до PostgreSQL, але сервер каже «ні». У логах застосунку ви побачите password authentication failed, а в логах PostgreSQL часто буде FATAL.

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

Якщо порівняти це з broken migration, різниця доволі пряма: за проблем із підʼєднанням лог зазвичай не каже вам «Migration V3__... failed». Він каже «не вдалося підʼєднатися», і в ньому немає привʼязки до конкретного SQL-файла. Це головний візуальний маркер.

Ще одна підказка: під час проблеми із підʼєднанням часто немає рядка Successfully applied ... migrations, бо до нього просто не доходять. Іноді буде рядок Flyway ..., коли бібліотека ініціалізується, але потім вона впаде на спробі підʼєднатися — і все одно не зʼявиться повідомлення про конкретну версію міграції.

4. Broken migration у логах застосунку

Broken migration — це, у дивному інженерному сенсі, зручний тип помилки: Flyway зазвичай повідомляє досить конкретно, що саме не так. Але щоб це було «зручно», треба знати, куди дивитися. У хаосі старту новачок часто чіпляється за останній рядок або за великий stack trace, хоча у Flyway майже завжди є короткий «паспорт помилки»: версія, імʼя файла, повідомлення від бази і інколи навіть SQL-оператор.

Коли Flyway падає, ви хочете витягти з логів три головні сигнали. Вони майже завжди присутні: яка міграція, де файл, яка SQL-помилка. Для цього не потрібно читати весь stack trace: вам достатньо знайти блок, який починається словами на кшталт Migration ... failed.

Ось типовий вигляд такого блоку. Це умовний приклад, але дуже схожий на реальність:

# "Паспорт" broken migration: версія/файл -> 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);

Давайте розберемо, що тут корисно, без занурення в «DBA-світ».

Таблично це виглядає так:

Поле в логу Flyway Що це означає простими словами Як використати в діагнозі
Migration V3__... Версія та імʼя SQL-файла Ви точно знаєте, який файл відкрити в репозиторії
Location ... (line N) Де файл і на якій рядці помилка Ви одразу переходите в потрібне місце, без «шукаю очима»
Message ... Текст помилки PostgreSQL Це «чому база не змогла виконати команду»
Statement ... SQL-оператор, який виконувався Ви бачите конкретну команду, інколи навіть без відкриття файла
SQL State Код класу помилки Корисно, якщо помилка коротка або незрозуміла, але зазвичай не обовʼязково

Ключовий прийом: спочатку шукаємо «паспорт помилки» Flyway, а вже потім читаємо stack trace. Stack trace майже завжди величезний, і якщо ви новачок, він емоційно тисне: «мені не стати програмістом, піду в фермери». Блок Flyway, навпаки, допомагає швидко перейти до дії: відкрити файл, побачити SQL, виправити.

Є ще один міграційний сценарій, який часто плутають із «зламався SQL», хоча по суті це інший вид broken migration: ви змінили вже застосований файл міграції. Flyway цього не любить, бо міграції — це історія, а історію не можна переписувати так, ніби нічого не було. Тоді замість SQL-помилки ви побачите повідомлення про validate та checksum mismatch.

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.

І це дуже важливий діагностичний маркер: тут помилка не в тому, що PostgreSQL «не зрозумів SQL». Тут помилка в тому, що база памʼятає міграцію V1 в одному вигляді, а ваш репозиторій показує її в іншому. Це теж broken migration, але лікується іншим способом. У межах курсу ми тримаємося простого принципу: не редагуємо вже застосовані міграції, а додаємо нові.

5. Логи PostgreSQL: підтвердження SQL-помилок

Логи застосунку — ваше головне джерело істини про те, який файл зламався. Але логи PostgreSQL — це корисна друга думка: вони підтверджують, що помилка справді була на боці виконання SQL, і показують її в термінах самої бази. Це особливо зручно, коли в логах застосунку все змішалося або ви хочете переконатися, що проблема не мережева.

У Compose-реальності найзручніше дивитися логи PostgreSQL через docker compose logs. Причому часто достатньо не всього виводу, а хвоста: останніх 50–200 рядків. Наприклад:

# Дивимося останні рядки логів сервісу postgres, щоб швидко побачити ERROR/STATEMENT або FATAL
docker compose logs --tail=100 postgres

Якщо помилка — «таблиця не існує» або «синтаксична помилка», PostgreSQL зазвичай логуватиме це так, що видно ключове слово ERROR і рядок STATEMENT. Приклад:

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

Чому це корисно? Тому що ви бачите, що база прийняла зʼєднання (тобто це не Connection refused), але не змогла виконати конкретну команду. Це і є «SQL-природа» проблеми.

А ось якщо проблема — неправильний пароль, PostgreSQL логуватиме це в стилі «доступ заборонено», і ключове слово там зазвичай FATAL:

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

Якщо проблема — неправильний host (wrong-host), PostgreSQL, найімовірніше, взагалі нічого не напише про вашу спробу: запит до нього не дійшов. І це теж діагностичний сигнал. Коли ви бачите UnknownHostException у логах застосунку і порожнечу в логах бази, це майже завжди означає проблему на мережевому або іменному рівні.

Практично корисна звичка тут така: спочатку ви читаєте docker compose logs app і знаходите, що зламалося, тобто версію і файл. Потім ви читаєте docker compose logs postgres, щоб підтвердити, як це сприйняла база. Якщо ви почнете одразу з логів PostgreSQL, ви побачите «relation does not exist», але не побачите, що це було з V3__add_status.sql. А нам важливо саме це: ми лікуємо репозиторій, а не «конкретну базу в колеги на ноутбуці».

6. Матриця симптомів і дерево рішень

Коли у вас у стеку два сервіси, хочеться мати зрозумілу таблицю відмінностей, щоб не стрибати між гіпотезами. Нижче — компактна матриця, яка допомагає швидко класифікувати проблему. У ній немає універсального лікування, але вона непогано відповідає на запитання: «це міграція чи підʼєднання?».

Клас проблеми Що відбувається з app Ключове в логах app Що в логах postgres Типовий корінь
Помилка підʼєднання app падає майже одразу CannotGetJdbcConnectionException, UnknownHostException, Connection refused Часто порожньо, коли host не той Невірний host або порт, localhost усередині Compose
Помилка аутентифікації app падає під час ініціалізації datasource password authentication failed / PSQLException: FATAL FATAL: password authentication failed... Невірний SPRING_DATASOURCE_USERNAME/PASSWORD
Не той профіль або режим app може стартувати, але поведінка не та У логах не ті профілі (standalone замість postgres) PostgreSQL може бути взагалі не потрібен Невірний SPRING_PROFILES_ACTIVE
Broken migration (SQL) app стартує, потім падає Migration V... failed, Location db/migration/... ERROR: ... + STATEMENT: ... Помилка в SQL-файлі: таблиця, колонка, синтаксис, обмеження
Broken migration (checksum) app падає на старті, часто без виконання SQL Validate failed, checksum mismatch Зазвичай нічого особливого Редагували застосований V1/V2

Якщо хочеться ще простіше, можна тримати в голові маленьке дерево рішень. Воно не замінює уважність, але добре витягує з хаотичного режиму:

flowchart TD
    A["контейнер app упав / перезапускається"] --> B["Дивимося docker compose logs app"]
    B --> C{"Є рядки Flyway Migration V... failed?"}
    C -->|Ні| D{"Є CannotGetJdbcConnection UnknownHost/Refused/FATAL?"}
    D -->|Так| E["Це підʼєднання або аутентифікація: виправляємо datasource env/url"]
    D -->|Ні| F["Перевіряємо профілі та конфігурацію SPRING_PROFILES_ACTIVE, datasource URL"]
    C -->|Так| G["Це broken migration: дивимося версію, файл, statement"]
    G --> H["Підтверджуємо в docker compose logs postgres ERROR/STATEMENT"]

Зверніть увагу: це дерево починається не з бази і не з «давайте ще раз піднімемо», а з логів app. Бо саме app знає, на якому етапі старту він перебував, і саме через app Flyway показує версію та імʼя файла.

7. Сценарій: міграція V3 з помилкою

Щоб усе це не залишилося абстракцією, корисно подумки прожити один короткий сценарій на нашому Container-Ready Catalog Service. Уявімо, що ми хочемо розширити таблицю catalog_item і додати колонку status. Ми створюємо новий файл міграції, як і домовилися в минулій лекції: нова версія — новий файл.

Але припустімо, що в момент копіювання ми помилилися і написали імʼя таблиці у множині. Вийшов такий файл:

-- src/main/resources/db/migration/V3__add_status.sql
-- Важливо: таблиця має існувати в поточній схемі, інакше міграція впаде на ALTER TABLE
ALTER TABLE catalog_items
ADD COLUMN status VARCHAR(32);

На перший погляд, SQL навіть виглядає пристойно. Проблема в тому, що в нашій схемі таблиця називається catalog_item (в однині). Flyway запускається, бере зʼєднання до бази, бачить, що є «не застосована» міграція V3, і намагається виконати її. PostgreSQL відповідає: «relation does not exist».

І ось тут починається найважливіше: ви не маєте вгадувати, що сталося. Ви маєте прочитати логи й побачити точне місце.

Типовий фрагмент логів app у такій ситуації буде схожий на це:

# Дивимося: яка міграція (V3) + де файл + що сказала база + який statement виконувався
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

Тут усе, що нам потрібно, уже є. Ми бачимо версію V3, імʼя файла V3__add_status.sql і навіть конкретний statement. Ми не «дебажимо Docker», не «лікуємо мережу», не чіпаємо depends_on і pg_isready — вони вже зробили свою роботу: база була готова. Ми лікуємо SQL у репозиторії.

Виправлена міграція має бути такою:

-- src/main/resources/db/migration/V3__add_status.sql
-- Виправлення: використовуємо реальне імʼя таблиці зі схеми (однину)
ALTER TABLE catalog_item
ADD COLUMN status VARCHAR(32);

І ось тут корисно зробити ще одне спостереження, яке економить години: виправляти треба саме файл міграції, бо це джерело істини. Якщо ви «підправите базу руками» (наприклад, залізете в psql і створите таблицю catalog_items просто щоб міграція пройшла), ви зробите свою локальну базу унікальною сніжинкою. На вашій машині стане «працює», а в колеги — ні. У навчальному курсі ми якраз боремося з цим антипатерном, бо він знищує відтворюваність середовища.

Найкращий наступний крок після такого збою — зберегти виправлення в репозиторії і знову підняти стек. Для нашої базової конфігурації PostgreSQL це зазвичай робиться на тому ж volume: невдалий DDL відкотиться, і Flyway просто ще раз спробує застосувати непройдену міграцію. Видаляти volume має сенс не як універсальне лікування, а коли вам потрібен саме чистий повтор усієї історії міграцій і початкового набору даних.

8. Типові помилки під час діагностики broken migration

Коли ви вперше стикаєтеся з міграціями в Compose-стеку, мозок часто намагається лікувати нову проблему старими ліками. Це нормально: усі ми так робимо, доки не набʼємо кілька ґуль. Але краще набивати їх на лекції, а не в пʼятницю ввечері на робочому проєкті.

Помилка №1: називати будь-яку startup-помилку «помилкою Flyway».
Часто в логах майнуло слово Flyway, і все — студент записує в голові «зламалася міграція». На практиці половина таких випадків — це просто підʼєднання: невірний host, localhost усередині Compose, пароль. Правильна звичка тут — шукати саме блок Migration V... failed або Validate failed. Коли їх немає, міграції, ймовірно, взагалі ні до чого.

Помилка №2: починати діагностику з логів PostgreSQL, а не з логів застосунку.
PostgreSQL чесно пише «relation does not exist», але він не скаже вам, що це було з V3__add_status.sql, на якій рядці і яку міграційну версію Flyway намагався застосувати. Застосунок знає контекст, PostgreSQL знає лише факт виконання statement. Якщо почати з бази, ви майже завжди втрачаєте найважливіший шлях назад до файла.

Помилка №3: намагатися виправити локальну базу руками замість виправлення міграції.
Це найпідступніший шлях, бо він дає швидкий дофамін: «ну працює ж!». Але ви отримуєте розбіжність між репозиторієм і реальністю конкретної машини. Через тиждень ви забуваєте, що «підкручували руками», і починаєте вірити в магію Docker. Джерело істини — SQL-файл міграції. Виправляємо його, а не «підсовуємо базі костиль».

Помилка №4: перезбирати image як першу реакцію на міграційну помилку.
За broken migration проблема майже ніколи не в Dockerfile, не в шарах і не в ENTRYPOINT. Це помилка SQL або міграційної історії. Перезбирання образу — це як лагодити велосипед, змінюючи колір рами: інколи приємно, але до проблеми це не має стосунку. Почніть з docker compose logs app, знайдіть файл, виправте міграцію, і лише потім запускайте знову.

Помилка №5: ігнорувати checksum mismatch і «лікувати» його як звичайний SQL-баг.
Якщо ви змінили V1__init.sql після того, як він уже застосований до бази, Flyway може впасти на validate. Новачок відкриває файл, бачить «ніби нормальний SQL», і починає безсистемно правити будь-що. А сенс помилки в іншому: база памʼятає одну версію V1, репозиторій — іншу. У навчальному проєкті ми намагаємося не потрапляти в цей стан: нові зміни схеми оформлюємо новими версіями.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ