Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 15 из 25 ~20 мин +50 XP

Миграции: automigrate и versioned

Кодогенерация из седьмого урока создаёт структуры, билдеры и клиент, но не трогает базу: таблицы users, groups, posts и вся join-инфраструктура сами не появятся. Сегодня — ent миграции в двух формах. Авто-миграция: один вызов client.Schema.Create(ctx) на старте — и схема из ваших Go-файлов становится колонками, индексами и внешними ключами. Версионные миграции atlas: SQL-файлы в репозитории, diff между желаемым и применённым, ревью в pull request и apply на деплое. Разберём append-only семантику автомиграции и её опции, поймём, почему переименование колонки — самая коварная операция, и соберём конвейер atlas: migrate diff, status, lint и чексумма atlas.sum. По канону курса: анна, борис, вера и группа go-ninja переезжают из схем в настоящую базу.

Содержание урока · 11 разделов

Маршрут урока — от первого CREATE TABLE до взрослого конвейера миграций. Сначала — проблема: схемы из второго урока — это код, а база о коде ничего не знает, и кто-то обязан перевести одно в другое. Затем — авто-миграция: механика Schema.Create, её append-only семантика и опции вроде WithDropColumn; здесь же — самая коварная операция, переименование поля, на которой ловятся все, кто путает «добавить колонку» с «переименовать». Третья часть — границы: почему automigrate отлично живёт в разработке, но не выдерживает прод-требований к истории, ревью и откату. Четвёртая — версионные миграции atlas: diff, каталог SQL-файлов, atlas.sum, apply, status и lint как ворота CI. Финиш — таблица выбора между двумя формами и чеклист деплоя. Если вы шли сюда по запросу «ent schema migration» — это тот урок: обе стратегии, честно, с ценой каждой.

Схема — код, база — отдельный мир#

Восемь уроков мы работали так, будто таблицы уже есть: создавали пользователей, строили рёбра, двигали счётчики. На самом деле у ent за это отвечает отдельный слой, и механика у него та же, что у всего фреймворка, — кодогенерация. Седьмой урок показал, где она живёт: файл ent/migrate/migrate.go содержит исполнение миграций, а ent/migrate/schema.go — описание вашего графа в терминах таблиц: users и groups, их колонки, индексы, внешние ключи и служебная join-таблица group_users с двумя каскадными FK. Это описание — единственный источник правды: и авто-миграция, и atlas читают одни и те же схемы, поэтому миграции не могут разойтись с билдерами, из которых вы вызываете SetX. ent миграции предлагает в двух формах. Первая — автоматическая: приложение само подводит базу к текущей схеме на старте. Вторая — версионная: инструмент atlas генерирует SQL-файлы, они лежат в репозитории, проходят ревью и применяются отдельным шагом. Формы не конкурируют, а делятся по зрелости проекта: пока вы в разработке — первая, когда появился прод и команда — вторая. Разница между ними не в силе, а в свойствах: у одной цена — слепота, у другой — церемония.

Авто-миграция: один вызов на старте

GO
// main.go — поднять схему при запуске приложения:
client, err := ent.Open("postgres", dsn)
if err != nil {
        log.Fatalf("драйвер не открылся: %v", err)
}
defer client.Close()

// сверить схемы с базой и создать недостающее:
if err := client.Schema.Create(ctx); err != nil {
        log.Fatalf("миграция не прошла: %v", err)
}

Schema.Create приходит из сгенерированного пакета ent/migrate и вызывается на клиенте. На пустой базе она исполняет CREATE TABLE IF NOT EXISTS для users, groups, posts и join-таблицы, ALTER TABLE ADD COLUMN для недостающих колонок, CREATE INDEX и ADD CONSTRAINT для внешних ключей. На уже размеченной базе — сверяет желаемое с фактическим и исполняет только недостающее: вызов идемпотентен, сколько раз ни запускай.

Проверка: второй запуск Schema.Create

Вы добавили в схему User поле bio и запустили приложение второй раз. Что делает Schema.Create?

append-only: что автомиграция делает и чего не делает#

Главная черта ent автомиграции — append-only семантика: она создаёт таблицы, добавляет колонки и индексы, расширяет внешние ключи — и ни при каких обстоятельствах не удаляет колонки и не сносит таблицы, даже если они исчезли из схем. Это защита данных: исчезновение поля из Go-файла не обязано означать «убить колонку с живыми данными». Из той же философии растёт самая коварная операция — переименование. Вы переименовали bio в about в схеме; automigrate не знает про «переименовал» — для неё это новая колонка about, которую надо добавить, и всё. Старая bio останется с данными, новая about добавится пустой, и никакой ошибки вы не увидите — просто тихая потеря, обнаруженная пользователями, а не компилятором. Если удаления нужны сознательно, их включают опциями: WithDropColumn(true) разрешает DROP COLUMN, WithDropIndex(true) — DROP INDEX. Ещё две опции стоит знать в лицо: WithGlobalUniqueID(true) переводит первичные ключи на общую таблицу счётчиков — сквозные уникальные id по всем таблицам, нужные полиморфным связям; WithForeignKeys(false) запрещает ent создавать внешние ключи — граф целостности вы обслуживаете сами, уровень приложения. Слово automigrate — общепринятое имя этой стратегии: миграция без человека, автоматически на каждом старте.

Опции Schema.Create

GO
import "ваш-модуль/ent/migrate"

// осторожный прогон с сознательными включениями:
if err := client.Schema.Create(
        ctx,
        migrate.WithDropIndex(true),   // уметь удалять индексы
        migrate.WithDropColumn(true),  // уметь удалять колонки
        migrate.WithGlobalUniqueID(true), // сквозные id по таблицам
); err != nil {
        log.Fatalf("миграция не прошла: %v", err)
}

Каждая опция — осознанный риск. Drop-режимы включают, когда точно знают, что колонка мертва: обычно на короткий период, чтобы убрать хвост после переименования. WithGlobalUniqueID меняет формат первичных ключей — включайте до первой продовой вставки, не после. В каноне курса мы этих флагов не касались: стандартный Schema.Create — append-only и безопасный по умолчанию.

Переименование: что окажется в базе

Прочитайте код и выберите, что он выведет:

// было:  field.String("bio").Optional().Nillable()
// стало: field.String("about").Optional().Nillable()

err := client.Schema.Create(ctx) // без drop-опций
// что увидит SELECT * FROM users?

Границы automigrate: почему прод требует большего#

Авто-миграция — стратегия доверия: код, исполняющийся на старте каждого инстанса, сам решает, какой станет база. У доверия три дыры. Первая — история: Schema.Create не оставляет следов того, что и когда исполняла; вопрос «когда у нас появилась колонка about» решается археологией, а не логом миграций. Вторая — откат: down-миграции не существует; если новая колонка с NOT NULL без дефолта разломала старую версию приложения, единственный путь назад — руками. Третья — ревью: SQL не видит ни один человек; каждая операция из этого урока — CREATE TABLE, ALTER TABLE, DROP — исполняется молча, и опасное изменение ничем не остановлено до момента, когда оно уже случилось. Четвёртая — деплой: при multi-instance-раскатке несколько подов стартуют одновременно, и каждый пытается мигрировать; на одних базах это окончится дублирующими ALTER, на других — взаимными блокировками. Отсюда правило: automigrate — для разработки, прототипов и пет-проектов; прод с командой, историями и SLA — территория версионных миграций, где каждая версия — файл, каждый файл — ревью, каждый apply — событие.

atlas: diff между желаемым и применённым#

Версионные миграции ent строятся на atlas — инструменте от той же команды, что развивает ent; связка atlas ent официальная, и это не случайность: atlas вырос из потребностей самого ent. Идея — та же, что у automigrate, только человек возвращается в цикл. atlas тоже сравнивает желаемое состояние (ваши Go-схемы) с фактическим, но не исполняет diff сразу, а записывает его SQL-файлом в каталог миграций — ent/migrate/migrations. Файлы нумеруются, складываются в историю и никогда не редактируются задним числом; рядом живёт atlas.sum — файл чексумм, гарантирующий, что уже применённые версии не подменят. Отдельный концепт — dev-база: чтобы вычислить diff, atlas поднимает временную базу (конфиг описывает её docker-строкой), применяет к ней весь каталог миграций, затем вашу новую схему — и снимает разницу. Поэтому diff не трогает прод: расчёт идёт на одноразовой базе. Правка схемы превращается в конвейер: go generate из седьмого урока, затем atlas migrate diff — и SQL ждёт вас в файле, готовый к чтению глазом.

Конфиг и генерация версии

GO
// atlas.hcl — конфиг провайдера ent:
env "ent" {
  src = "ent/schema"
  dev = "docker://postgres/15/dev?search_path=public"
  migration {
    dir = "file://ent/migrate/migrations"
  }
}

# первый снимок схемы — вся база одним файлом:
atlas migrate diff init --env ent

# после правки схемы — точечная версия:
atlas migrate diff add_bio --env ent

env ent описывает вход: src — каталог схем, dev — временная база для расчёта diff, migration.dir — каталог версий. diff генерирует SQL-файл с именем вида <версия>_add_bio.sql и обновляет atlas.sum. Второй вызов после правки схем снимет только разницу: для поля bio это ALTER TABLE users ADD COLUMN bio — тот же результат, что у automigrate, но в файле, который вы можете прочитать и отклонить.

Проверка: зачем atlas.sum

В каталоге миграций рядом с SQL-файлами лежит atlas.sum. Что произойдёт, если вы отредактируете уже применённую версию миграции?

Конвейер: apply, status, lint#

Генерация — половина истории; вторая — исполнение и контроль. atlas migrate apply применяет отложенные версии к базе по порядку: каждая — отдельным шагом, отметка о применении пишется в служебную таблицу ревизий, и повторный apply честно пропустит то, что уже применено. atlas migrate status отвечает на главный вопрос дежурного: совпадает ли каталог с базой, какие версии ждут, нет ли расхождений чексумм. atlas migrate lint — статический анализ новых версий: инструмент читает SQL и подсвечивает разрушительные операции — DROP COLUMN, изменения типов, которые могут потерять данные, — до того, как их увидит ревьюер. Именно lint превращает ревью миграции из «поверю на слово» в «посмотрю отчёт». Из этих трёх команд складываются ворота CI: на pull request прогоняется diff + lint, на деплой — apply отдельным шагом, и только один раз — не на старте каждого пода. Отдельная тема для существующих баз — baseline: точка, с которой вы начинаете вести историю, объявляя текущее состояние базы уже применённым; atlas умеет генерировать её отдельной командой, чтобы не переигрывать всё с нуля.

Деплой: статус и применение

GO
# что база думает о каталоге миграций:
atlas migrate status --env ent --url "$DSN"

# применить отложенные версии — один шаг деплоя:
atlas migrate apply --env ent --url "$DSN"

status читает таблицу ревизий и сравнивает с каталогом: текущая версия, число отложенных, целостность atlas.sum. apply идёт по порядку и останавливается на первой ошибке — база не «перепрыгивает» сломанную версию, следующий apply продолжит с места остановки. Ключевое отличие от automigrate: исполнение — отдельный шаг с одним исполнителем, а не побочный эффект старта приложения.

Проверка: apply на втором инстансе

На деплое три пода. Первый успел исполнить atlas migrate apply и упал на старте по другой причине. Что увидят остальные два, если и они запустят apply?

Чеклист миграций перед продом#

  • Правка схемы всегда в три шага: go generate из седьмого урока, atlas migrate diff с осмысленным именем, чтение SQL глазами перед коммитом.
  • Применённые версии не редактируются никогда — даже чтобы «поправить опечатку»: исправление идёт новой версией, atlas.sum сторожит историю.
  • Переименование колонки — три версии, а не одна: добавить новую, скопировать данные UPDATE-ом, убрать старую (и то — осознанным drop после периода совместимости).
  • Разрушительные операции — только с lint-отчётом и планом отката: down-миграции у вас нет, откат пишется руками заранее.
  • apply — отдельный шаг деплоя, не строка в main.go каждого пода; multi-instance-старт с Schema.Create — лотерея.
  • На существующей базе начните с baseline: объявите текущее состояние применённым и ведите историю дальше.
  • Локальные проверки — на dev-базе из atlas.hcl; продовая база получает только то, что прошло diff, lint и ревью.

Авто-миграция против версионных: карта выбора#

СвойствоSchema.Create (automigrate)atlas migrate (versioned)
Исполнительприложение на стартеCLI на шаге деплоя
Источник правдысгенерированный migrate/schema.goSQL-файлы каталога миграций
История версийнет — только текущее состояниеда — каталог + таблица ревизий
Откат (down-миграция)нет вовсепишется руками, заранее
Ревью человекомнет — SQL не видит никтода — файл в PR, lint-отчёт
Удаление колоноктолько с WithDropColumn(true)явный DROP COLUMN в версии
Переименованиетихая потеря данныхdiff подсвечивает, ревью решает
Гонка multi-instanceкаждый под мигрирует самодин apply на деплой
Целостность историинечего проверятьatlas.sum, apply падает на подмене
Каноническое местоdev, прототип, пет-проектprod, команда, CI/CD

Соберите обе стратегии

Впишите вызов авто-миграции, опцию drop-режима и подкоманду atlas для генерации версии.

// 1. поднять схему на старте приложения: if err := client.(ctx); err != nil { log.Fatalf("миграция не прошла: %v", err) } // 2. разрешить удаление колонок сознательно: migrate.(true) // 3. сгенерировать новую версию миграций: atlas migrate add_bio --env ent

Путь одной правки схемы

Соберите конвейер версионной миграции: от правки поля до обновлённой базы на проде.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Что дальше#

Вы умеете доводить схему до базы двумя путями: один вызов Schema.Create для разработки и полный конвейер atlas для прода — diff, lint, apply, ревью с atlas.sum в роли сторожа. Это замыкает контур из седьмого урока: схемы — код, кодогенерация — компилятор миграций, а база получает ровно то, что описано в Go. Дальше — две главы про поведение мутаций в рантайме. Шестнадцатый урок разберёт транзакции: client.Tx, идиому commit/rollback, вложенные Tx через savepoints — и вы наконец атомарно закроете «детей, потом родителя» из четырнадцатого. Семнадцатый — хуки: функции, которые перехватывают каждую мутацию до записи, валидируют её и пишут аудит-логи; там же вы автоматизируете счётчик posts_count, который двигали руками в тринадцатом. Домашнее задание ниже проведёт обе стратегии по вашей базе и заставит читать SQL как документ. Полный план из 25 уроков — на странице курса, механика кодогенерации — в седьмом уроке, тестовая инфраструктура с автосозданием схемы — в двадцать третьем, текстом.

Карточки: миграции

Термин

Что делает Schema.Create

нажмите, чтобы перевернуть
Определение

Авто-миграция из ent/migrate/migrate.go: сверяет схемы с базой и исполняет только недостающее — CREATE TABLE IF NOT EXISTS, ALTER TABLE ADD COLUMN, индексы, внешние ключи. Идемпотентна: второй запуск тихий. Вызов — client.Schema.Create(ctx) на старте приложения.

1 / 6

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • ent миграции живут в двух формах: авто-миграция client.Schema.Create(ctx) на старте приложения и версионные миграции atlas с SQL-файлами, ревью и ревизиями; механика у обеих одна — diff желаемого (схемы) и фактического (база).
  • Авто-миграция — append-only: создаёт таблицы (CREATE TABLE IF NOT EXISTS), добавляет колонки и индексы, и никогда не удаляет — исчезновение поля из схемы не трогает данные.
  • Переименование поля — самая коварная операция automigrate: новая колонка добавится пустой, старая останется с данными без единой ошибки; честный путь — добавить, скопировать UPDATE-ом, убрать старую отдельной версией.
  • Опции Schema.Create: WithDropColumn/WithDropIndex включают DROP, WithGlobalUniqueID — сквозные id для полиморфных связей, WithForeignKeys(false) — FK руками; каждая опция — осознанный риск.
  • Границы automigrate: нет истории, нет down-миграции, нет ревью, гонка при multi-instance-старте — стратегия разработки, не прода.
  • Конвейер atlas: atlas migrate diff пишет версию в каталог ent/migrate/migrations и обновляет atlas.sum; lint подсвечивает разрушительные операции, apply применяет отложенные версии и пишет ревизии, status показывает расхождения.
  • atlas.sum — целостность истории: отредактированная применённая версия останавливает apply; правки идут новыми версиями, а не правками старых.
  • Правило деплоя: apply — отдельный шаг, один раз на выкладку; automigrate в проде заменяют версионными миграциями с baseline для существующих баз.

Домашнее задание

Работайте на базе из восьмого урока — анна, борис, вера, их посты и группа go-ninja — с включённым логом SQL драйвера. Первое: запустите Schema.Create дважды подряд и докажите себе, что второй прогон тихий: ни одного CREATE TABLE, ни одного ALTER — запишите, сколько стейтментов исполнилось на пустой базе при первом. Второе: добавьте в схему User поле about (String, Optional, Nillable) и запустите automigrate: найдите в логе ALTER TABLE ADD COLUMN, затем SELECT-ом убедитесь, что bio и все её данные на месте. Третье: смоделируйте переименование — переименуйте bio в about, запустите Schema.Create и опишите письменно, что получилось в базе: две колонки, старая с данными, новая пустая, ни одной ошибки; затем верните схему к канону и допишите план честного переименования в три версии. Четвёртое: поставьте atlas, соберите atlas.hcl с docker-строкой dev-базы и сгенерируйте init-версию — прочитайте её SQL целиком и найдите в нём: join-таблицу group_users с двумя FK, уникальный индекс на email, все колонки posts. Пятое: добавьте about, снимите diff add_about, запустите atlas migrate lint и убедитесь, что отчёт чист; затем отредактируйте применённый init-файл (одну букву) и наблюдайте, как apply/status останавливается на atlas.sum — восстановите файл и убедитесь, что конвейер снова зелёный. Вопрос на подумать: почему ent не встроил down-миграции ни в automigrate, ни в atlas-конвейер, и какие три изменения в вашей схеме вы бы потребовали описать планом отката до ревью?

Личные заметкиПусто
Хранятся только в вашем браузере (ent · Миграции: automigrate и versioned)

Вопросы и ответы по уроку

Schema.Create или atlas: что выбрать для проекта?

По зрелости: разработка и прототип — Schema.Create на старте, она бесплатна и безопасна в append-only режиме. Как только у базы появился прод, команда и вопросы «а что было в прошлый вторник» — версионные миграции: diff в PR, lint, ревью, один apply на деплой. Переход не разрушителен: baseline объявляет текущее состояние применённым, и дальше вы живёте версиями.

Опасно ли включать WithDropColumn?

Опасно ровно настолько, насколько слепо включать drop-режим глобально. Держите его в отдельных запусках с конкретной целью: убрать хвост после переименования, снять индекс, признанный мёртвым. В версионном конвейере та же операция честнее: DROP COLUMN живёт в отдельной версии, проходит lint и ревью, где его видят глаза, а не флаг в main.go.

Как переименовать колонку без потери данных?

Никак одной операцией — ни automigrate, ни atlas не знают ваш замысел. Три версии: добавить новую колонку, скопировать данные (UPDATE ... SET new = old, можно дозировать по id), убрать старую после периода совместимости. В atlas-конвейере каждый шаг — отдельный файл с ревью и lint-отчётом; в automigrate шаги размоются, и «убрать старую» потребует WithDropColumn. Урок один: переименование — это миграционный план, а не правка поля.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Сравните с альтернативами

Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.

Следующая ступень учебного пути

go-kit25 уроков~14 чпродвинутый25 уроков уже доступны

go-kit: микросервисный тулкит

Курс go-kit: endpoint, транспорт, логирование, метрики, трейсинг, circuit breaker, service discovery. 25 уроков с итоговым микросервисом.

  • Понимать архитектуру endpoint → transport → service
  • Оборачивать сервисы middleware: логи, метрики, трейсинг
  • Подключать HTTP и gRPC транспорты
  • Ставить rate limiting и circuit breaker