← Данные
12 мин · средне · Урок 2 из 5

Миграции схемы базы данных

Зачем менять схему версионированными миграциями, чем up отличается от down и как это вписать в деплой без даунтайма.

Зачем версионировать схему

Код в git версионируется, а схема базы (таблицы, колонки, индексы) меняется руками через psql. Тогда непонятно, какая схема на каком сервере, новый разработчик не может поднять базу с нуля, а откат невозможен. Миграции решают это: каждое изменение схемы это пронумерованный файл в репозитории, рядом с кодом.

Миграция — файл с номером

Миграция описывает одно изменение схемы и имеет порядковый номер или timestamp. Инструмент (Flyway, Liquibase, Alembic, golang-migrate и подобные) хранит в самой базе таблицу применённых миграций и накатывает только новые.

migrations/
  0001_create_users.sql
  0002_add_email_index.sql
  0003_add_active_column.sql
-- 0003_add_active_column.sql
ALTER TABLE users ADD COLUMN active boolean NOT NULL DEFAULT true;

Запуск применяет все непринятые миграции по порядку:

$ migrate up
applied 0002_add_email_index
applied 0003_add_active_column
database is at version 3

База помнит, что версия 3, и повторный запуск ничего не делает. Это та же идемпотентность из прошлого трека: накатить дважды безопасно.

up и down

У миграции две стороны. up применяет изменение, down откатывает его. Добавили колонку в up, в down её удаляют.

-- up
ALTER TABLE users ADD COLUMN phone text;
-- down
ALTER TABLE users DROP COLUMN phone;

down полезен для отката в разработке, но на проде откатывать схему опасно: down, удаляющий колонку, уничтожит данные. Поэтому на проде чаще катят только вперёд, а ошибку чинят новой миграцией.

Миграции и деплой без даунтайма

Здесь миграции встречаются с rolling-выкатом из урока про стратегии. Во время выката старая и новая версии кода работают вместе, поэтому схема должна подходить обеим. Опасны разрушающие изменения: удалить колонку, на которую ещё ходит старый код, значит уронить его.

Безопасный приём — менять схему в несколько шагов (expand/contract):

  1. expand: добавить новое, не ломая старое (новая колонка, nullable).
  2. выкатить код, который пишет и в старое, и в новое.
  3. перенести данные, переключить код на новое.
  4. contract: в следующем релизе удалить старое, когда им никто не пользуется.

Так схема в каждый момент совместима с обеими версиями кода, и выкат проходит без даунтайма.

Что важно запомнить

Миграции версионируют схему вместе с кодом: каждое изменение это пронумерованный файл, инструмент применяет только новые и помнит версию в базе. Накат идемпотентен.

up применяет, down откатывает, но на проде откат схемы опасен (теряются данные), поэтому ошибку чаще исправляют новой миграцией.

Во время rolling-выката схема должна подходить и старой, и новой версии кода. Разрушающие изменения делают поэтапно (expand/contract), чтобы не было даунтайма.

В следующем уроке разберём пулы соединений: как не упереться в лимит подключений к базе.

Прошли материал до конца? Отметьте урок пройденным.