Привет, дружище!
Сегодня давай поговорим о том, что такое миграции баз данных и почему они так важны. В современном мире неудивительно, что любые изменения в базе данных должны вноситься осторожно и в соответствии с определенным процессом. В идеале эти шаги должны быть интегрированы в наш CI/CD конвейер, чтобы все работало автоматически.
Вот наша повестка дня:
- В чем проблема?
- Как мы это исправим?
- Простой пример
- Более сложный пример
- Рекомендации
- Результаты
- Заключение
В чем проблема?
Если ваша команда никогда не сталкивалась с миграциями баз данных и вы не совсем уверены, зачем они нужны, давайте разберемся. Если вы уже знаете основы, можете смело пропустить вперед.
Основная проблема
Когда мы вносим «запланированные» и «гладкие» изменения в базу данных, нам нужно поддерживать доступность сервиса и выполнять требования SLA (чтобы пользователи не страдали от простоя или задержек). Представьте, что вы хотите изменить тип столбца в таблице с 5 миллионами пользователей. Если вы сделаете это «напрямую» (например, просто выполните ALTER TABLE
без подготовки), таблица может заблокироваться на значительное время — и ваши пользователи останутся без сервиса.
Чтобы избежать таких головных болей, следуйте двум правилам:
- Применяйте миграции таким образом, чтобы не блокировать таблицу (или, по крайней мере, минимизировать блокировки).
- Если вам нужно изменить тип столбца, часто легче сначала создать новый столбец с правильным типом, а затем удалить старый.
Другая проблема: управление версиями и откаты
Иногда нужно откатить миграцию.
Делать это вручную — заходить в производственную базу данных и возиться с данными — не только рискованно, но и, вероятно, невозможно, если у вас нет прямого доступа. Здесь на помощь приходят специальные инструменты миграции. Они позволяют вам применять изменения аккуратно и при необходимости откатывать их.
Как это исправить? Используйте правильные инструменты
Каждый язык и экосистема имеют свои собственные инструменты миграции:
- Для Java, Liquibase или Flyway являются распространёнными.
- Для Go популярным выбором является goose (тот, который мы рассмотрим здесь).
- И так далее.
Goose: что это и почему это полезно
Goose — это легкий утилита Go, которая помогает вам управлять миграциями автоматически. Она предлагает:
- Простота. Минимальные зависимости и прозрачная файловая структура для миграций.
- Универсальность. Поддерживает различные драйверы БД (PostgreSQL, MySQL, SQLite и т.д.).
- Гибкость. Пишите миграции на SQL или коде Go.
Установка Goose
go install github.com/pressly/goose/v3/cmd/goose@latest
Как это работает: Структура миграции
По умолчанию Goose ищет файлы миграции в db/migrations
. Каждая миграция имеет следующий формат:
NNN_migration_name.(sql|go)
NNN
– номер миграции (например,001
,002
, и т. д.).- После этого может быть любое описательное название, например
init_schema
. - Расширение файла может быть
.sql
или.go
.
Пример SQL-миграции
Файл: 001_init_schema.sql
:
-- +goose Up
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT now()
);
-- +goose Down
DROP TABLE users;
Наш первый пример
Изменение типа столбца (Строка → Целое число)
Предположим, у нас есть таблица users
с столбцом age
типа VARCHAR(255)
. Теперь мы хотим изменить его на INTEGER
. Вот как может выглядеть миграция (файл 005_change_column_type.sql
):
-- +goose Up
ALTER TABLE users ALTER COLUMN age TYPE INTEGER USING (age::INTEGER);
-- +goose Down
ALTER TABLE users ALTER COLUMN age TYPE VARCHAR(255) USING (age::TEXT);
Что здесь происходит:
-
Миграция вверх
- Мы изменяем столбец
age
наINTEGER
. КлаузаUSING (age::INTEGER)
указывает PostgreSQL, как конвертировать существующие данные в новый тип. - Обратите внимание, что эта миграция завершится неудачно, если есть какие-либо данные в столбце
age
, которые не являются числовыми. В этом случае вам понадобится более сложная стратегия (см. ниже).
- Мы изменяем столбец
-
Обратная миграция
- Если мы откатим изменения, вернем
age
кVARCHAR(255)
. - Мы снова используем
USING (age::TEXT)
, чтобы преобразовать изINTEGER
обратно в текст.
- Если мы откатим изменения, вернем
Вторые и сложные случаи: многоступенчатые миграции
Если столбец age
может содержать неаккуратные данные (не только числа), безопаснее сделать это в несколько этапов:
- Добавьте новый столбец (
age_int
) типаINTEGER
. - Скопируйте допустимые данные в новый столбец, обрабатывая или удаляя недопустимые записи.
- Удалите старый столбец.
-- +goose Up
-- Step 1: Add a new column
ALTER TABLE users ADD COLUMN age_int INTEGER;
-- Step 2: Try to move data over
UPDATE users
SET age_int = CASE
WHEN age ~ '^[0-9]+$' THEN age::INTEGER
ELSE NULL
END;
-- (optional) remove rows where data couldn’t be converted
-- DELETE FROM users WHERE age_int IS NULL;
-- Step 3: Drop the old column
ALTER TABLE users DROP COLUMN age;
-- +goose Down
-- Step 1: Recreate the old column
ALTER TABLE users ADD COLUMN age VARCHAR(255);
-- Step 2: Copy data back
UPDATE users
SET age = age_int::TEXT;
-- Step 3: Drop the new column
ALTER TABLE users DROP COLUMN age_int;
Чтобы обеспечить правильный откат, раздел Down
просто зеркалит действия в обратном порядке.
Автоматизация — это ключ
Чтобы сэкономить время, действительно удобно добавить команды миграции в Makefile (или любую другую систему сборки). Ниже приведен пример Makefile с основными командами Goose для PostgreSQL.
Предположим:
- DSN для базы данных —
postgres://user:password@localhost:5432/dbname?sslmode=disable
. - Файлы миграций находятся в
db/migrations
.
# File: Makefile
DB_DSN = "postgres://user:password@localhost:5432/dbname?sslmode=disable"
MIGRATIONS_DIR = db/migrations
# Install Goose (run once)
install-goose:
go install github.com/pressly/goose/v3/cmd/goose@latest
# Create a new SQL migration file
new-migration:
ifndef NAME
$(error Usage: make new-migration NAME=your_migration_name)
endif
goose -dir $(MIGRATIONS_DIR) create $(NAME) sql
# Apply all pending migrations
migrate-up:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) up
# Roll back the last migration
migrate-down:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) down
# Roll back all migrations (be careful in production!)
migrate-reset:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) reset
# Check migration status
migrate-status:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) status
Как это использовать?
1. Создайте новую миграцию (SQL файл). Это создаст файл db/migrations/002_add_orders_table.sql
.
make new-migration NAME=add_orders_table
2. Примените все миграции. Goose создаст таблицу schema_migrations
в вашей базе данных (если она еще не существует) и применит любые новые миграции в порядке возрастания.
make migrate-up
3. Отмените последнюю миграцию. Просто откатите последнюю.
make migrate-down
4. Отмените все миграции (будьте осторожны в производственной среде). Полный сброс.
make migrate-reset
5. Проверьте статус миграции.
make migrate-status
Пример вывода:
$ goose status
$ Applied At Migration
$ =======================================
$ Sun Jan 6 11:25:03 2013 -- 001_basics.sql
$ Sun Jan 6 11:25:03 2013 -- 002_next.sql
$ Pending -- 003_and_again.go
Итог
Используя инструменты миграции и Makefile, мы можем:
- Ограничить прямой доступ к производственной базе данных, внося изменения только через миграции.
- Легко отслеживать версии базы данных и откатывать их, если что-то пойдет не так.
- Поддерживать единую, последовательную историю изменений базы данных.
- Выполнять “плавные” миграции, которые не нарушат работающую производственную среду в мире микросервисов.
- Получить дополнительную проверку — каждое изменение пройдет через процесс PR и код-ревью (при условии, что у вас есть эти настройки).
Еще одно преимущество заключается в том, что легко интегрировать все эти команды в ваш CI/CD процесс. И помните — безопасность превыше всего.
Например:
jobs
migrate
runs-on ubuntu-latest
steps
name Install Goose
run
make install-goose
name Run database migrations
env
DB_DSN $ secrets.DATABASE_URL
run
make migrate-up
Заключение и советы
Основные идеи настолько просты:
- Делайте ваши миграции маленькими и частыми. Их легче рецензировать, тестировать и откатывать в случае необходимости.
- Используйте один и тот же инструмент во всех средах, чтобы разработка, тестирование и продакшн были синхронизированы.
- Интегрируйте миграции в CI/CD, чтобы не зависеть от ручного запуска кого-либо.
Таким образом, у вас будет надежный и контролируемый процесс изменения структуры вашей базы данных — который не нарушает продакшн и позволяет быстро реагировать в случае проблем.
Удачи с миграциями!
Спасибо за внимание!
Source:
https://dzone.com/articles/goose-as-crucial-tool-for-your-service