JavaRush /Курсы /Docker for Spring /Seed data и recovery без правки БД

Seed data и recovery без правки БД

Docker for Spring
18 уровень , 4 лекция
Открыта

1. Роль seed data в локальной среде

Когда вы поднимаете локальный стенд, особенно через Docker Compose, хочется, чтобы через 30 секунд можно было сделать запрос в API и увидеть не пустоту, а что-то полезное. Иначе вы поднимаете целую PostgreSQL, запускаете миграции, ждёте старт приложения… чтобы получить в ответ: «список пуст». Это как открыть холодильник после долгой готовки и обнаружить там только ледяную полку и чувство вины.

Seed data решает сразу две практические задачи. Во‑первых, оно делает локальную среду “живой”: вы можете сразу проверить GET /api/catalog/items, увидеть несколько элементов и убедиться, что persistence действительно работает. Во‑вторых, seed data становится якорем воспроизводимости: если студент или коллега сделал полный reset окружения, у него снова появится ожидаемое базовое содержимое, а не «каждый раз по-разному, потому что я вчера руками что-то вставлял».

Есть важный нюанс: seed data не значит «запихнуть в базу 10 000 строк и имитировать прод». Для курса по Docker нам нужна небольшая, предсказуемая порция данных, которая помогает наблюдать поведение сервиса, миграций и reset’ов. Чем меньше магии и “случайностей”, тем лучше.

Именно поэтому вообще имеет смысл различать обычный rerun и clean reset. Если после docker compose down -v и нового старта у вас снова появляются те же записи, значит baseline правда живёт в репозитории. Если данные “восстанавливаются” только потому, что кто-то когда-то вставил их руками в локальную БД, это не baseline, а счастливое совпадение.

2. Seed data в миграциях Flyway

У новичков часто возникает соблазн сделать «скрипт для заполнения базы» где-нибудь в scripts/seed.sql и запускать его руками, когда «надо». И это работает ровно до первого дня, когда вы устали, спешите, и вместо seed-скрипта запускаете не тот файл. После этого у вас «вроде бы работает», но у коллеги — нет, а у вас — работает «после трёх танцев с бубном». В учебном проекте мы как раз пытаемся выработать привычку, которая спасает от этого.

В нашем Container-Ready Catalog Service seed data — это не внешняя ручная процедура, а часть того же механизма, что и создание схемы: Flyway migrations. По умолчанию Flyway ищет файлы в classpath:db/migration, то есть в нашем проекте это:

src/main/resources/db
└─ migration

Минимальная «правильная» структура для курса выглядит так:

src/main/resources/db/migration
├─ V1__init.sql
└─ V2__seed_demo_data.sql

Смысл разделения очень простой и жизненный. V1__init.sql отвечает за структуру (таблицы, ограничения), а V2__seed_demo_data.sql — за стартовые записи. Это делает миграции читаемыми и диагностируемыми: если что-то пошло не так, вы по логам быстро понимаете, на каком уровне проблема — на уровне схемы или на уровне данных.

И ещё один важный момент: Flyway хранит историю применённых миграций в самой базе (в таблице вроде flyway_schema_history). Поэтому если база живёт в named volume, то и «память о миграциях» живёт там же. Именно поэтому после docker compose down вы можете поднять стек снова и увидеть, что миграции не применяются заново. А после docker compose down -v — применяются, потому что база «родилась заново».

3. Минимальный seed-файл

Seed data хочется сделать «красивой» и «реалистичной». В этот момент очень легко улететь в сторону мини‑продакшена: сложные описания, десятки статусов, куча связанных таблиц, попытка заселить всё сразу. Но в Docker-курсе наша цель проще: быстро получить данные, которые можно увидеть через API, и которые помогают понять, что БД действительно работает.

Допустим, в V1__init.sql у нас была создана таблица catalog_item (упрощённо, как в предыдущих примерах дня). Тогда seed-миграция может выглядеть так:

-- V2__seed_demo_data.sql
-- Демо-данные: коротко, предсказуемо, без random()/now()
INSERT INTO catalog_item (sku, title, price)
VALUES
  -- Стабильные SKU удобны для поиска глазами в логах/ответах API
  ('BOOK-001', 'Docker Basics', 29.90),
  ('BOOK-002', 'Spring in Containers', 39.90),
  ('BOOK-003', 'PostgreSQL for Humans', 24.50);

Заметьте, тут есть несколько негласных «учебных хороших манер». Мы используем стабильные sku, чтобы записи можно было узнавать глазами в логах и ответах API. Мы не вставляем id, потому что он обычно генерируется (BIGSERIAL). Мы не делаем random() и now() просто ради красоты — потому что seed data в учебном проекте должна быть предсказуемой, а не “каждый старт — новая вселенная”.

Если в вашем реальном проекте есть дополнительные поля типа status, created_at, updated_at, то можно сделать это явно, но всё равно коротко. Например:

-- V2__seed_demo_data.sql
-- Если есть обязательные поля (status и т.п.) — заполняем их явно
INSERT INTO catalog_item (sku, title, price, status)
VALUES
  ('BOOK-001', 'Docker Basics', 29.90, 'ACTIVE'),
  ('BOOK-002', 'Spring in Containers', 39.90, 'ACTIVE');

Главное — помнить: seed data в рамках курса это инструмент воспроизводимости, а не витрина “как мы умеем писать SQL”.

4. Ручная правка БД как анти-recovery

Если вы уже немного видели SQL, рука сама тянется к подходу: «Ой, миграция упала? Сейчас зайду в базу, быстренько поправлю табличку, и всё». Это как чинить компиляцию Java-проекта тем, что вы вручную правите байткод в .class файле. Теоретически возможно, но в учебной реальности это почти всегда превращается в невоспроизводимую магию.

Проблема даже не в том, что вы «неправильно» поправите. Проблема в том, что вы ломаете источник истины. Источник истины — это репозиторий: миграции, которые лежат в db/migration, и которые любой участник команды должен уметь применить, не зная вашей личной истории ночных приключений в psql.

Когда вы меняете базу руками, вы создаёте рассинхрон. Flyway думает, что схема соответствует определённому набору версий, а реальная база уже «чуть-чуть другая». Потом вы вносите новую миграцию V3, и она падает с ошибкой, которую невозможно понять, глядя только на Git. И вот тут рождаются легенды: «Docker какой-то нестабильный», «Flyway у нас капризный», «PostgreSQL сегодня не в настроении». На самом деле просто кто-то однажды «быстро поправил» базу вручную.

Если хочется коротко, то можно запомнить так:

Подход Что происходит в реальности Чем заканчивается
Ручная правка БД Вы чините только свою локальную копию Через неделю никто не понимает, что у вас за состояние
Правка миграции / новая миграция Вы чините проект, а не базу Любой может воспроизвести, а reset всегда возвращает baseline

И да, иногда в работе действительно используют ручные запросы для диагностики, но не как способ “исправить” систему. Диагностика — окей. “Лечение” — только через репозиторий.

5. Recovery workflow без шаманства

Когда контейнер приложения перестал стартовать, очень хочется сделать три вещи: перезапустить всё, потом перезапустить ещё раз, потом добавить sleep 20 и сделать вид, что так и было задумано. Но мы с вами уже достаточно взрослые, чтобы стыдиться этого ровно пять секунд — и перейти к нормальному алгоритму.

Сценарий восстановления локальной среды после broken migration удобно держать в голове как короткую цепочку наблюдаемых шагов. Она начинается не с «удалить всё», а с «понять класс проблемы», а заканчивается не на up, а на проверке API.

flowchart TD
    A["Контейнер app не стартует / упал"] --> B["Смотрим логи app"]
    B --> C{"Это миграция Flyway?"}
    C -->|Нет| D["Ищем wrong-host / wrong-profile / wrong-password"]
    C -->|Да| E["Находим версию и файл миграции"]
    E --> F["Исправляем SQL в репозитории"]
    F --> G{"Нужен clean reset базы?"}
    G -->|Нет| H["docker compose up --build"]
    G -->|Да| I["docker compose down -v && docker compose up --build"]
    H --> J["Проверяем: logs + /actuator/health + /api/catalog/items"]
    I --> J

Теперь то же самое человеческим языком.

Сначала вы смотрите логи приложения, а не базы. Именно в логах Spring Boot + Flyway обычно написано, какая миграция упала: версия, имя файла и SQL-ошибка. Потом вы сверяете, что PostgreSQL вообще доступен и healthy (логи базы тоже полезны, но обычно вторичны). Затем вы исправляете причину в репозитории: либо правите новую миграцию, если она ещё не применялась, либо добавляете новый шаг истории, если старая уже успела стать частью применённой схемы.

После этого базовый путь — пересобрать образ приложения и снова поднять стек на том же volume. Для PostgreSQL это нормальный сценарий: неуспешная миграция обычно откатывается транзакцией, поэтому после фикса файла Flyway просто снова пытается применить непройденный шаг. docker compose down -v нужен для другой задачи: когда вы сознательно хотите вернуть БД к чистому baseline, прогнать всю историю с нуля и убедиться, что seed data поднимается без ручных хвостов.

И наконец, “готово” наступает только тогда, когда вы проверили три вещи: логи, health endpoint и один бизнес-endpoint. Без этого вы просто не знаете, что у вас реально восстановилось.

6. Мини-демо: сломали миграцию и восстановились

Представим типичный “контролируемый ужас”: вы добавили новую миграцию V3__add_status.sql, но ошиблись в имени таблицы (лишняя буква, неправильное число — классика жанра).

Например, так:

-- V3__add_status.sql (плохой вариант)
-- Ошибка: таблица называется catalog_item, а не catalog_items
ALTER TABLE catalog_items
ADD COLUMN status VARCHAR(32);

Таблица у нас, допустим, называется catalog_item, а не catalog_items. Что произойдёт? PostgreSQL скажет что-то в духе “relation does not exist”, Flyway поймёт, что миграция упала, и Spring Boot не завершит старт.

В логах приложения это обычно выглядит примерно так:

app-1  | Migration V3__add_status.sql failed
app-1  | ERROR: relation "catalog_items" does not exist
app-1  | Application run failed

Теперь “правильная магия” выглядит так: мы исправляем миграцию в репозитории, а не в базе. Фикс — банальный:

-- V3__add_status.sql (исправленный вариант)
-- Исправление: меняем имя таблицы на реальное
ALTER TABLE catalog_item
ADD COLUMN status VARCHAR(32);

После этого нам нужно, чтобы контейнер приложения получил обновлённый jar, ведь миграции лежат внутри приложения (в src/main/resources, а значит — внутри jar). Поэтому базовый recovery path в Compose выглядит так:

# Пересобираем образ приложения и снова поднимаем стек на том же volume
docker compose up --build

Если же цель другая — не просто пережить конкретную поломку, а сознательно проверить проект на чистой базе, тогда уже уместен clean reset:

# Стираем локальное состояние БД и заново прогоняем всю историю миграций и seed baseline
docker compose down -v
docker compose up --build

Почему сначала уместен именно обычный rerun? Потому что в нашем baseline с PostgreSQL неуспешная миграция обычно откатывается, и после исправления файла Flyway просто повторяет этот шаг. down -v нужен не для “лечения SQL”, а для clean replay: проверить, что проект поднимается с нуля, seed data возвращается автоматически, а локальный drift исчезает.

При этом контейнер PostgreSQL можно вообще не пересобирать: он поднимается из официального образа. А контейнер приложения нужно пересобрать, потому что код и ресурсы (включая SQL миграции) менялись.

7. Критерии успешного восстановления

Есть болезненный, но полезный навык: перестать считать победой момент, когда docker compose up перестал ругаться. Контейнер может быть запущен, процесс может жить, а сервис при этом быть “полумёртвым”: миграции не дошли, схема не совпала, приложение зависло на старте или живёт без нужного профиля.

Для нашего учебного сервиса критерии “всё снова работает” простые и проверяемые.

Во‑первых, в логах app вы должны увидеть, что Flyway применил нужные миграции и приложение дошло до Started ...:

app-1  | Successfully applied 3 migrations
app-1  | Started CatalogApplication

Во‑вторых, Actuator health должен быть UP:

# Проверяем, что приложение действительно healthy
curl -s http://localhost:8080/actuator/health
# Ожидаем: {"status":"UP"}

В‑третьих, бизнес-эндпоинт должен отвечать и, в случае чистого старта, показывать seed data:

# Проверяем бизнес-эндпоинт и наличие стартовых данных (seed)
curl -s http://localhost:8080/api/catalog/items
# Ожидаем, что вернутся элементы, например:
# [{"id":1,"sku":"BOOK-001","title":"Docker Basics","price":29.90}, ...]

Если вы после down -v не видите стартовых данных, это почти всегда означает одно из двух. Либо seed-миграции нет (или она пустая), либо приложение вообще не в том профиле и не смотрит на PostgreSQL (например, запустилось в standalone). И вот это уже нормальная, понятная диагностика, а не “что-то где-то сломалось”.

8. Типичные ошибки при seed data и recovery workflow

Ошибка №1: seed data живёт в голове или в личном файле, а не в миграциях.
Очень часто стартовые данные появляются “случайно”: кто-то однажды вставил пару строк через psql, и дальше все считают, что так и должно быть. До первого полного reset’а. Потом половина команды видит пустую базу и начинает чинить проблему с конца. Seed data должна быть версией в db/migration, иначе это не seed, а легенда.

Ошибка №2: исправлять упавшую миграцию «в базе», а не в репозитории.
Когда миграция упала, хочется быстро “добавить столбец руками” и поехать дальше. Но так вы не исправляете причину — вы прячете её. Через время вы получите вторую ошибку, и она будет выглядеть вообще не связанной с первой. Источник истины — migration-файлы в Git. Локальная база — только результат их применения.

Ошибка №3: всегда лечить любую проблему docker compose down -v.
down -v — это мощная кнопка “стереть память”. Иногда она реально нужна (например, после broken migration). Но если делать так на каждую мелочь, вы постоянно теряете состояние БД и перестаёте понимать, какие проблемы связаны с данными, а какие — с конфигурацией. В учебной и рабочей жизни важно, чтобы reset был осознанным, а не рефлексом.

Ошибка №4: забыть пересобрать образ приложения после изменения миграций.
Миграции лежат в ресурсах приложения. Если вы поменяли SQL-файл, но не пересобрали jar или образ, то контейнер всё равно запускает старую версию. Потом вы смотрите в логи и думаете: “Я же исправил… почему он снова ругается?”. Потому что вы исправили на host-машине, а в контейнере живёт прошлый артефакт. Для Compose это обычно означает запуск с --build.

Ошибка №5: считать восстановление завершённым после docker compose up.
up — это только старт. Восстановление заканчивается, когда вы увидели успешные миграции в логах, получили UP на /actuator/health и сделали хотя бы один запрос к API. Без этого вы просто переносите проблему в будущее: “ну вроде поднялось, но завтра опять упадёт”.

1
Задача
Docker for Spring, 18 уровень, 4 лекция
Недоступна
Seed data приходит из миграций после clean reset
Seed data приходит из миграций после clean reset
1
Задача
Docker for Spring, 18 уровень, 4 лекция
Недоступна
Recovery после broken migration без ручной правки БД
Recovery после broken migration без ручной правки БД
1
Опрос
Миграции БД, 18 уровень, 4 лекция
Недоступен
Миграции БД
Flyway и Docker Compose
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ