Миграции базы данных: как не взорвать продакшен и сохранить нервы

Вступление: почему миграции — это не просто ALTER TABLE

Помните первый раз, когда вы запустили ALTER TABLE users ADD COLUMN phone VARCHAR(20) в продакшене? И сразу же поняли, что забыли про индексы, триггеры и 500 миллионов записей? Миграции базы данных — это не просто версионирование схемы. Это система, которая должна учитывать:

  • Производительность: как ALTER TABLE на 100ГБ таблице отразится на запросах пользователей?
  • Откаты: что делать, если миграция упала на середине?
  • Данные: как обновить миллионы строк без блокировок?
  • Совместимость: как гарантировать, что новая схема не сломает приложение до следующего релиза?

Без правильного подхода миграции становятся источником стресса, а не инструментом для улучшения архитектуры.


Проблема: когда миграции превращаются в кошмар

Вот реальные сценарии, с которыми сталкиваются команды:

  1. Блокировки и тайм-ауты:

    -- Так делать нельзя: это убивает производительность на больших таблицах
    ALTER TABLE orders ADD COLUMN status VARCHAR(50);
    
    • Решение: использовать pt-online-schema-change (Percona Toolkit) или аналогичные инструменты для онлайн-миграций.
  2. Неявные зависимости:

    • Разработчик добавляет поле в таблицу, а через неделю оказывается, что его использует 3-й сервис, о котором никто не знал.
    • Решение: автоматизировать проверку зависимостей (например, с помощью pg_depend в PostgreSQL или information_schema в MySQL).
  3. Отсутствие откатов:

    • Миграция прошла на половину, а затем упала. Как вернуться назад без потери данных?
    • Решение: всегда писать откатные миграции и тестировать их отдельно.
  4. "Ручные" миграции:

    • Админ запускает SQL-скрипт вручную, забывает записать версию, и через месяц никто не понимает, что уже сделано.
    • Решение: использовать инструменты типа Flyway, Liquibase или собственные скрипты с версионированием.

Практика: как правильно мигрировать

1. Инструменты и workflow

Выбор инструмента зависит от СУБД и масштаба проекта. Вот проверенные варианты:

Flyway (рекомендуется для средних проектов)

  • Преимущества: простой, интегрируется с CI/CD, поддерживает откаты.

  • Пример конфигурации flyway.conf:

    flyway.url=jdbc:postgresql://db-prod:5432/app_db
    flyway.user=deploy_user
    flyway.password=secure_password
    flyway.locations=filesystem:db/migration
    flyway.baselineOnMigrate=true  # Если миграции уже есть в продакшене
    flyway.outOfOrder=false
    flyway.validateOnMigrate=true
    
  • Структура миграций:

    db/migration/
    ├── V1__Add_users_table.sql
    ├── V2__Add_index_to_users.sql
    ├── V3__Add_phone_column.sql
    └── V3_down__Remove_phone_column.sql  # Откат
    

Liquibase (лучше для сложных схем)

  • Преимущества: поддерживает XML/YAML/JSON, удобно для больших схем.
  • Пример XML-миграции:
    <changeSet id="2" author="dev-team">
      <addColumn tableName="orders">
        <column name="status" type="varchar(50)" defaultValueNull="true"/>
      </addColumn>
    </changeSet>
    

Ручное управление (только для мелких проектов)

  • Использовать скрипты с версионированием и проверкой статуса:
    # Проверка, что миграция не выполнена
    if ! psql -U user -d db -c "SELECT * FROM schema_migrations WHERE migration = 'V3__Add_phone';"; then
      psql -U user -d db -f migrations/V3__Add_phone.sql
      psql -U user -d db -c "INSERT INTO schema_migrations (migration) VALUES ('V3__Add_phone');"
    fi
    

2. Стратегии миграций

Стратегия Когда использовать Риски Инструменты
Offline Маленькие таблицы (<10М записей) Блокировки на время миграции ALTER TABLE
Online (PTOSC) Большие таблицы (>100М записей) Высокая нагрузка на CPU/Disk pt-online-schema-change
Zero-downtime Критические системы Сложность реализации Gh-ost (MySQL), pg_repack (PostgreSQL)
Двойная запись Миграции с изменением логики данных Удвоение хранения Custom scripts + приложение

Пример онлайн-миграции с pt-online-schema-change (MySQL)

pt-online-schema-change \
  --alter "ADD COLUMN status VARCHAR(50)" \
  --table users \
  --execute \
  --alter-foreign-keys-method=auto \
  --max-lag 0.1 \
  --statistics \
  --ask-pass
  • Ключевые параметры:
    • --max-lag: максимальная задержка репликации (в секундах).
    • --alter-foreign-keys-method: как обрабатывать внешние ключи.

3. Тестирование миграций

Никогда не доверяйте миграциям "на глаз". Используйте:

  1. Локальное окружение:

    • Запускайте миграции на копии продакшен-данных (например, с помощью pg_dump или mysqldump --where).
  2. CI/CD проверки:

    • Добавляйте в пайплайн шаг валидации миграций:
    # Пример для GitHub Actions
    - name: Validate migrations
      run: |
        flyway validate \
          -url=jdbc:postgresql://localhost:5432/test_db \
          -user=test_user \
          -password=test_pass
    
  3. Ролинг-бэк тесты:

    • Проверяйте откатные миграции отдельно:
    flyway repair  # Удаляет метки выполненных миграций
    flyway migrate # Запускает миграции заново
    flyway migrate -X=undo  # Пробует откатить последнюю
    

Типичные ошибки и как их избежать

  1. Отсутствие версионирования

    • Проблема: Нет контроля над тем, какие миграции уже выполнены.
    • Решение: Использовать таблицу schema_migrations (Flyway) или аналогичную.
  2. Большие транзакции

    • Проблема:
      BEGIN;
      ALTER TABLE big_table ADD COLUMN new_field;
      UPDATE big_table SET new_field = 'default';
      COMMIT;
      
      • Блокирует таблицу на часы.
    • Решение: Разбивать на маленькие транзакции или использовать онлайн-инструменты.
  3. Игнорирование индексов

    • Проблема: Добавление поля без индекса приводит к падению производительности запросов.
    • Решение: Автоматически добавлять индексы в миграциях или документировать их в отдельном файле.
  4. Отсутствие откатов

    • Проблема: Если миграция падает, приходится руками чистить данные.
    • Решение: Всегда писать откатные скрипты и тестировать их.
  5. "Живые" миграции в продакшене

    • Проблема: Разработчик запускает миграцию в 3 часа ночи без предупреждения.
    • Решение: Использовать чеклисты и approval-процессы (например, через Argo Workflows или manual_job в GitLab CI).

Практический вывод: checklist для безопасных миграций

  1. Перед миграцией:

    • Проверить зависимость от других сервисов (граф зависимостей).
    • Создать бэкап данных (даже если думаете, что не нужен).
    • Оценить время блокировки таблиц (и предупредить команду).
  2. Во время миграции:

    • Запускать в отдельной транзакции для критичных изменений.
    • Мониторить нагрузку на базу (CPU, диск, latency).
    • Иметь под рукой откатный план (даже если миграция кажется простой).
  3. После миграции:

    • Проверить логи приложения на ошибки.
    • Запустить нагрузочные тесты (если миграция касалась производительности).
    • Обновить документацию (схемы, API-контракты).
  4. Для команды:

    • Документировать каждую миграцию (что изменилось, почему, риски).
    • Обучать новых членов команды процедурам миграций.
    • Регулярно ревьюить миграционные скрипты (как код).

Заключение: миграции — это инженерная задача

Миграции базы данных — это не просто SQL-команды. Это процесс, который требует внимания к деталям, тестирования и понимания последствий. Используйте проверенные инструменты, автоматизируйте рутинные задачи и никогда не запускайте миграции "на авось".

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