Зачем версионировать схему БД: подходы state-based и migration-based
↑ 5.1 Миграции: EF Core, Flyway, goose · → Следующая
Зачем это на собесе
Схема БД — часть кода. Спрашивают, как вы гарантируете одинаковую структуру на всех окружениях и как выкатываете изменения.
Проблема
Без контроля версий схемы:
- разработчики вносят изменения вручную, окружения расходятся (dev ≠ stage ≠ prod);
- нельзя воспроизвести БД с нуля, нет истории изменений;
- релизы требуют ручных скриптов, растёт риск ошибок и простоя;
- невозможно корректно откатить или проанализировать инцидент.
Решение: схема описывается в репозитории и применяется автоматически в пайплайне.
Подходы
Migration-based (на основе миграций)
Хранится последовательность скриптов (миграций). Каждая переводит схему из версии N в N+1. БД запоминает, какие применены, в служебной таблице.
V1__create_customers.sql
V2__add_orders.sql
V3__add_orders_status_index.sqlПлюсы: точный контроль изменений, работа с данными (бэкфилл), явные безопасные шаги, подходит для прода с существующими данными. Минусы: состояние схемы нужно «собирать» из истории, конфликты версий при ветках.
Инструменты: Flyway, Liquibase, EF Core Migrations, goose, dbmate, Alembic, Sqitch, Atlas (versioned).
State-based (на основе состояния)
Хранится желаемое состояние (DDL целевой схемы). Инструмент сам вычисляет разницу между целевой схемой и БД и генерирует изменения.
Плюсы: всегда видно актуальную схему, меньше конфликтов. Минусы: автогенерация не знает о данных (переименование колонки = drop + add → потеря данных), нужно ревью сгенерированного плана; сложно с миграциями данных.
Инструменты: SSDT/SqlPackage (SQL Server), Atlas (declarative), Skeema, Prisma db push.
Гибрид
State-based для разработки/локально, migration-based для прода; либо Atlas: декларативная схема + генерация версионированных миграций.
Code-first и Database-first
- Code-first: модель в коде → миграции генерируются (EF Core
Add-Migration); - Database-first: схема главная, модель генерируется (
Scaffold-DbContext).
Принципы хороших миграций
- Неизменяемость: применённую миграцию не редактируют, исправление делается новой миграцией (иначе контрольные суммы не совпадут).
- Идемпотентность и проверка: скрипты запускаются один раз; защитные конструкции (
IF NOT EXISTS) для повторяемых. - Маленькие атомарные шаги: одна миграция — одна понятная задача.
- Обратная совместимость с кодом: старая версия приложения должна работать со новой схемой (на время выкатки).
- Транзакционность: в PostgreSQL DDL транзакционен (но
CREATE INDEX CONCURRENTLYнельзя в транзакции). - Тестирование: прогон миграций с нуля и на копии прод-данных в CI.
- Ревью как кода: DBA/коллеги проверяют блокировки и влияние на нагрузку.
- Не смешивать изменение схемы и тяжёлую миграцию данных (раздельные шаги).
- Автоматизация: применение в CD, а не вручную.
Кто и когда применяет
| Способ | Плюсы | Минусы |
|---|---|---|
При старте приложения (Migrate()) | просто | гонки при нескольких инстансах, права DDL у приложения, долгий старт, риск откатов |
| Отдельный шаг пайплайна (job, init-контейнер) | контроль, отдельные права, один раз | нужно отладить порядок и координацию |
| Вручную DBA | строгий контроль | медленно, человеческий фактор |
Рекомендация: отдельный шаг деплоя под отдельным пользователем с правами DDL; приложение работает с урезанными правами.
Откат (rollback)
- «down»-миграции часто ненадёжны (потеря данных, невозможность вернуть);
- практика: roll-forward (исправление новой миграцией) + backward-compatible изменения + бэкап/PITR для катастроф;
- перед рискованной миграцией: резервная копия, план отката, окно обслуживания.
Вопросы с ответами
Чем migration-based отличается от state-based?
В первом хранятся упорядоченные скрипты изменений и применяются последовательно; во втором хранится целевое состояние, а инструмент вычисляет разницу и генерирует изменения.
Почему нельзя править уже применённую миграцию?
Она уже выполнена на других окружениях; инструмент хранит контрольную сумму и отклонит изменение, а схемы разойдутся. Исправлять нужно новой миграцией.
Где лучше запускать миграции: при старте приложения или в пайплайне?
В пайплайне отдельным шагом: без гонок между инстансами, с отдельными правами и контролем результата, до выкатки нового кода.