Миграции БД с Claude Code — Alembic, Prisma, TypeORM (Гайд и обучение Claude Code)
Миграция — самое опасное изменение в проекте. Неправильный ALTER TABLE блокирует таблицу на часы. Пропущенный downgrade превращает откат в рулетку. Claude Code умеет генерировать миграции, но требует ревью с упором на безопасность.
Канал с гайдами и контентом по claude code, выкладываем новости (когда режут лимиты в 10 раз) и какие инструменты через claude реализуем для проектов, канал: https://t.me/claudedevolper
CLAUDE.md для миграций
Правила миграций ### Нельзя в одном релизе - Добавить NOT NULL без default - Переименовать колонку без deprecation-фазы - Удалить колонку, которую ещё читает прод-код - CREATE INDEX без CONCURRENTLY на таблице > 1M строк ### Обязательно - Каждая миграция — атомарна (одно изменение) - downgrade() всегда описан - Для Postgres: CONCURRENTLY для индексов, CHECK NOT VALID для ограничений - Проверка на staging с копией прод-данных ### Workflow 1. Изменил модель → сгенерировал миграцию 2. Ревью диффа — особенно op.drop_column 3. Прогон на dev/staging 4. Deploy: миграция СНАЧАЛА, потом новый код (или наоборот — зависит от изменения)
Alembic (SQLAlchemy)
Генерация:
alembic revision --autogenerate -m "add phone to users"
Результат:
migrations/versions/2026_04_17_add_phone.py from alembic import op import sqlalchemy as sa revision = "e7a2b8c1f9d0" down_revision = "a3b2c1d4e5f6" def upgrade(): op.add_column( "users", sa.Column("phone", sa.String(20), nullable=True), ) op.create_index( "idx_users_phone", "users", ["phone"], postgresql_concurrently=True, ) def downgrade(): op.drop_index("idx_users_phone", table_name="users") op.drop_column("users", "phone")
Запуск:
alembic upgrade head alembic downgrade -1 # откат на одну alembic history --verbose
Prisma (Node.js)
npx prisma migrate dev --name add_phone_to_users
Prisma сравнит schema.prisma с БД и сгенерит SQL. Пример:
-- migration.sql ALTER TABLE "users" ADD COLUMN "phone" TEXT; CREATE INDEX "users_phone_idx" ON "users"("phone");
Для прода — prisma migrate deploy (без генерации, только apply).
Откат Prisma не поддерживает «из коробки» — пишешь новую миграцию, которая восстанавливает состояние.
Zero-downtime добавление NOT NULL
Три деплоя вместо одного:
Шаг 1 — колонка nullable + default в приложении:
def upgrade(): op.add_column("users", sa.Column("phone", sa.String(20), nullable=True))
Шаг 2 — backfill в фоне:
UPDATE users SET phone = '' WHERE phone IS NULL; -- лучше батчами по 10K строк
Шаг 3 — NOT NULL:
def upgrade(): op.alter_column("users", "phone", nullable=False, server_default="")
Безопасное переименование колонки
Нельзя просто op.alter_column("users", "email", new_column_name="email_address") — старый код сломается.
Схема:
1. Добавить email_address + триггер, копирующий из email 2. Задеплоить код, который пишет в обе, читает из email_address 3. Backfill существующих записей 4. Задеплоить код, который пишет только в email_address 5. Удалить email
Проверка миграции перед применением
Показать SQL без выполнения alembic upgrade head --sql > up.sql # Prisma npx prisma migrate diff \ --from-schema-datamodel schema.prisma \ --to-schema-datasource schema.prisma \ --script > up.sql
Чтение up.sql глазами — обязательный этап для прод-миграции.
CONCURRENTLY в PostgreSQL
-- НЕ CREATE INDEX idx_users_email ON users(email); CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
Без CONCURRENTLY — эксклюзивный лок на всю таблицу. На миллионной таблице — минуты простоя.
В Alembic:
op.create_index( "idx_users_email", "users", ["email"], postgresql_concurrently=True, )
Нужен autocommit_block:
def upgrade(): with op.get_context().autocommit_block(): op.create_index( "idx_users_email", "users", ["email"], postgresql_concurrently=True, )
Подводные камни
- autogenerate не видит ENUM-значения. Добавление варианта в ENUM — руками.
- Foreign key без индекса. DELETE по parent таблице превращается в seq scan по child.
- Prisma шизу делает при ручных правках SQL. Не трогай миграционный файл после создания.
- alembic downgrade не всегда обратим. Если в upgrade был DROP COLUMN — данные уже не вернуть.
Как попробовать
1. Добавь в CLAUDE.md секцию миграций 2. Сгенерируй тестовую миграцию 3. Прогони --sql чтобы увидеть реальный SQL 4. Примени на staging → убедись, что downgrade работает
Канал с гайдами и контентом по claude code, выкладываем новости (когда режут лимиты в 10 раз) и какие инструменты через claude реализуем для проектов, канал: https://t.me/claudedevolper