Тестирование миграций базы данных перед выкатом в продакшен

Вы написали скрипт миграции. Он выглядит правильно. Синтаксис валиден. Логика верна. Вы запускаете его на локальной базе — всё работает. Затем выкатываете в продакшен — и всё ломается.

В таблице были миллионы строк. В вашей локальной базе — двенадцать. Миграция добавляла NOT NULL колонку, а в продакшене оказались существующие NULL-значения, о которых вы не знали. ALTER TABLE заблокировал запись на пятнадцать минут во время пикового трафика.

Это не редкий сценарий. Он возникает потому, что окружение, в котором вы тестируете миграцию, не совпадает с тем, где она будет выполняться. Миграция, проходящая на пустой или несоответствующей базе, даёт ложную уверенность. Решение — не тестировать тщательнее на локальной машине. Решение — построить тестовое окружение, отражающее реальность продакшена.

Почему соответствие схемы имеет значение

Скрипт миграции пишется на основе предположений. Он предполагает, что определённые колонки существуют, определённые индексы присутствуют, определённые ограничения установлены. Если ваша тестовая база имеет другую схему, вы тестируете на другом наборе предположений.

Рассмотрим миграцию, которая добавляет NOT NULL колонку. На тестовой базе с пустой таблицей миграция выполняется мгновенно. В продакшене в таблице есть строки с NULL-значениями в этой колонке. Миграция падает — и у вас инцидент в продакшене.

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

Вот конкретный пример для PostgreSQL:

# Дамп только схемы (без данных) из продакшена
pg_dump --schema-only --no-owner --no-acl production_db > schema.sql

# Восстановление схемы в тестовую базу
psql test_db < schema.sql

Данные, которые вскрывают реальные проблемы

Пустая таблица или таблица со случайными тестовыми данными не выявит граничные случаи. Сбои миграций часто возникают из-за данных, которые существуют в продакшене, но отсутствуют в вашем тестовом наборе.

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

Вам не нужно копировать полный набор продакшен-данных. Для таблицы с миллионами строк достаточно нескольких тысяч хорошо подобранных строк, чтобы движок базы данных построил реалистичный план выполнения. Цель — не воспроизвести объём продакшена. Цель — воспроизвести паттерны данных, которые могут заставить миграцию вести себя иначе.

Dry-run как подстраховка

Dry-run выполняет SQL миграции без постоянного изменения базы данных. Некоторые инструменты миграции имеют встроенный режим dry-run. Если его нет, вы можете обернуть миграцию в транзакцию и откатить её в конце.

Цель dry-run — не подтвердить, что миграция успешна. Цель — увидеть предупреждения, ошибки и изменения плана выполнения, которые могут указывать на проблемы. Миграция, которая отлично работает на маленькой таблице, может показать предупреждение о полном сканировании таблицы при запуске на реалистичной схеме. ALTER TABLE на большой таблице может показать оценочное время выполнения, неприемлемое для вашего окна обслуживания.

После dry-run просмотрите вывод вручную или с помощью автоматических проверок. Ищите операции, которые могут заблокировать таблицы на длительное время. Ищите запросы, которые могут ухудшить производительность. Ищите любое неожиданное поведение, которое не проявилось при локальном тестировании.

Симуляция небольшой нагрузки во время миграции

Некоторые миграции безопасны на простаивающей базе, но вызывают проблемы под реальной нагрузкой. ALTER TABLE, который захватывает блокировку, может завершиться за секунды, когда никто больше не использует таблицу. Под продакшен-нагрузкой та же блокировка может вызвать тайм-ауты запросов и ошибки приложения.

Вам не нужен полноценный нагрузочный тест. Достаточно простого скрипта, который выполняет SELECT и INSERT запросы к затрагиваемым таблицам во время миграции. Если эти симулированные запросы завершаются ошибкой или тайм-аутом, вы нашли проблему, которая затронула бы пользователей продакшена.

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

Автоматизация тестового окружения

Ручная настройка тестового окружения для каждой миграции — это медленно и чревато ошибками. Лучший подход — автоматизировать это как часть вашего CI-пайплайна.

Когда новая миграция закоммичена, пайплайн создаёт тестовое окружение из снимка схемы продакшена. Загружает релевантные тестовые данные. Запускает dry-run. Симулирует небольшую нагрузку. Затем уничтожает окружение.

Эта автоматизация гарантирует, что каждая миграция тестируется на согласованном базисе. Никто не может пропустить тест из-за спешки. Никто не может утверждать, что тест прошёл, потому что использовалась другая схема. Пайплайн обеспечивает одинаковые условия каждый раз.

Практический чек-лист

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

  • Тестовая схема точно соответствует схеме продакшена
  • Тестовые данные включают граничные случаи, релевантные для миграции
  • Dry-run завершился без неожиданных предупреждений или ошибок
  • Симуляция небольшой нагрузки не вызвала сбоев или тайм-аутов
  • Тестовое окружение создано из свежего снимка продакшена

Что это значит для вашего пайплайна

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

Тестирование миграций в реалистичном окружении — не опция. Это разница между знанием, что миграция сработает, и надеждой, что она сработает. Окружение, которое вы строите для тестирования, не должно быть дорогим или сложным. Оно должно быть репрезентативным.

Начните с дампа схемы из продакшена. Добавьте целевые тестовые данные. Запускайте dry-run. Симулируйте небольшую нагрузку. Автоматизируйте весь процесс. Ваша продакшен-база скажет вам спасибо.