> Как реализовать миграции базы данных (Go)
Уровень: senior · Роль: backend · Категория: Технические вопросы
Компании: Лукойл
Стек: Go
> Пример ответа
Короткий ответ
Миграции в Go реализуют через специализированные библиотеки (golang-migrate, goose, atlas) либо самописный инструмент на основе embed.FS. Основная идея - версионирование схемы БД: каждый файл миграции содержит up/down SQL, применяется атомарно, состояние отслеживается в служебной таблице. Для production критичны: транзакционность, идемпотентность, возможность отката, интеграция в CI/CD и запуск перед деплоем приложения.
Подробное объяснение
Миграции решают проблему эволюции схемы БД в команде. Без них каждый разработчик меняет БД вручную, что приводит к рассинхрону. В Go подходы:
-
Файловые миграции - SQL-файлы с версиями (например,
0001_create_users.up.sqlи.down.sql). Инструмент применяет только неприменённые. Популярные: golang-migrate, goose, tern. -
Миграции на Go - миграция описывается кодом (например,
migrate.Up(func(tx *sql.Tx) error {...})). Удобно для сложной логики, но хуже читается. -
Декларативный подход - описывается целевая схема, инструмент генерирует diff (atlas, skeema). Меньше контроля, но проще поддержка.
Ключевые механизмы:
- Таблица версий - обычно
schema_migrationsс полямиversion,dirty. Инструмент блокирует таблицу или использует advisory lock для конкурентного доступа. - Транзакции - DDL в PostgreSQL транзакционен, в MySQL - нет. Поэтому для MySQL часто применяют миграции по одной, с проверкой идемпотентности.
- Откат - down-миграции должны быть симметричны up. Для необратимых изменений (удаление данных) - down может быть пустым или выбрасывать ошибку.
- Порядок применения - строго по возрастанию версий, без пропусков. Dirty-флаг сигнализирует о сбое - требуется ручное вмешательство.
На практике
Для production-системы:
- Миграции запускаются отдельным процессом (в CI или init-контейнере), а не внутри основного приложения - иначе несколько реплик одновременно начнут миграции.
- Используйте advisory lock (PostgreSQL) или
GET_LOCK(MySQL) для предотвращения гонок. - Храните миграции в репозитории рядом с кодом, в папке
migrations/. - Для встраивания в бинарник -
embed.FS, чтобы не зависеть от файловой системы при деплое. - В CI: сначала прогон миграций на staging, затем на production, затем деплой приложения.
- Для отката - отдельная команда
migrate down, но в production часто предпочитают forward-only (новую миграцию, исправляющую проблему), чтобы избежать потери данных.
Пример кода
GO// migrations/0001_create_users.up.sqlCREATE TABLE users (id BIGSERIAL PRIMARY KEY,email TEXT NOT NULL UNIQUE,created_at TIMESTAMPTZ NOT NULL DEFAULT now());// migrations/0001_create_users.down.sqlDROP TABLE users;
GO// main.go - запуск миграций через golang-migratepackage mainimport ("embed""log""github.com/golang-migrate/migrate/v4""github.com/golang-migrate/migrate/v4/database/postgres""github.com/golang-migrate/migrate/v4/source/iofs"_ "github.com/lib/pq")//go:embed migrations/*.sqlvar migrationsFS embed.FSfunc runMigrations(databaseURL string) error {source, err := iofs.New(migrationsFS, "migrations")if err != nil {return err}db, err := sql.Open("postgres", databaseURL)if err != nil {return err}driver, err := postgres.WithInstance(db, &postgres.Config{})if err != nil {return err}m, err := migrate.NewWithInstance("iofs", source, "postgres", driver)if err != nil {return err}if err := m.Up(); err != nil && err != migrate.ErrNoChange {return err}return nil}
Как отвечать на собеседовании
Начните с определения: миграции - это версионирование схемы БД. Затем опишите два подхода: файловые SQL-миграции и код-миграции. Упомяните инструменты для Go: golang-migrate, goose, atlas. Обязательно расскажите про таблицу версий, dirty-флаг и транзакционность. Подчеркните разницу между PostgreSQL (DDL в транзакции) и MySQL (нет). Затем переходите к практике: отдельный процесс, embed.FS, advisory lock, CI/CD. Если спросят про откат - объясните trade-off между down-миграциями и forward-only. Завершите примером кода, если просят.
Что проверяет интервьюер
- Понимание проблемы, которую решают миграции (синхронизация схемы, воспроизводимость).
- Знание механизмов: таблица версий, блокировки, транзакции.
- Умение выбирать инструмент под задачу, а не "всё подряд".
- Понимание production-нюансов: конкурентный запуск, откат, CI/CD.
- Способность объяснить trade-off между подходами (SQL vs код, файлы vs декларативный).
Типичные ошибки
- Запуск миграций внутри основного приложения при нескольких репликах - гонки.
- Игнорирование dirty-флага - после сбоя миграции система остаётся в неопределённом состоянии.
- Отсутствие down-миграций или несимметричные up/down.
- Применение миграций без транзакции там, где это возможно (PostgreSQL).
- Хранение миграций вне репозитория или без embed - проблемы с деплоем.
- Смешивание миграций и сидов (seed) - это разные вещи.
- Использование
migrate downв production без понимания последствий потери данных.
> Похожие задачи по backend
Приведите пример инкапсуляции
Что такое WebSocket и как он работает
Что такое функция
Какие стратегии пагинации можно использовать вместо LIMIT OFFSET?
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью