При поэтапном обновлении сервиса старая и новая версии одновременно работают с одной базой. Какой подход к автотестам выявит несовместимую миграцию схемы до переключения трафика?
Используйте тестирование совместимости схемы в сочетании со стратегией expand-and-contract. Сначала добавляют новые структуры, не ломая старую версию, затем проверяют работу обеих версий приложения с общей схемой и только после вывода старой версии из эксплуатации удаляют устаревшие элементы.
При традиционном обновлении приложение и схема базы менялись одновременно. Это было приемлемо при остановке системы, но стало рискованным при rolling, blue-green и canary-развёртываниях, где несколько версий сервиса некоторое время работают параллельно.
Исходная проблема состоит в том, что миграция может быть корректной для новой версии, но несовместимой со старой. Автотесты на одну версию приложения такую ошибку часто не обнаруживают.
Предположим, новая версия переименовывает столбец или меняет формат данных, пока старая версия ещё обрабатывает запросы. Часть экземпляров начнёт получать ошибки, а уже записанные данные могут стать нечитаемыми для другой версии.
Неверно полагаться только на успешное выполнение миграции или на тесты новой версии. Нужно проверить совместимость в переходном состоянии, когда старая и новая версии используют одну базу одновременно.
Стратегия expand-and-contract состоит из этапов:
В CI обычно проверяют несколько комбинаций: старая версия приложения с новой схемой, новая версия с промежуточной схемой и новая версия с целевой схемой. Проверки должны охватывать чтение, запись, обновление существующих записей и обработку данных, созданных другой версией.
Важный принцип — не смешивать изменение схемы и немедленное удаление старого контракта в одной необратимой операции. Это увеличивает длительность переходного периода, зато позволяет откатить приложение без немедленного отката данных.
Подход не устраняет все риски. Двойная запись может привести к расхождению данных, а обратная совместимость не гарантирует корректность бизнес-правил. Поэтому нужны проверки согласованности, метрики ошибок и отдельный план удаления старых структур.
Сервис заказов заменял строковое поле статуса на нормализованную таблицу. Вариант с немедленным переименованием поля был простым, но ломал старые экземпляры приложения при постепенном обновлении. Вариант с полной остановкой системы снижал технический риск, но был неприемлем из-за требований к доступности.
Команда выбрала промежуточную миграцию: добавила новую структуру, выпустила код с чтением старого и нового представления, включила двойную запись и добавила CI-проверки для старой и новой версий против промежуточной схемы. После сверки данных трафик перевели на новую версию, а удаление старого поля вынесли в отдельный релиз.
Такой вариант сложнее и временно увеличивает объём кода, но обнаруживает несовместимость до переключения трафика и сохраняет возможность безопасного отката приложения.
Нет. При поэтапном развёртывании старая версия может обращаться к уже изменённой базе. Поэтому минимум нужно проверить старую версию с промежуточной схемой и новую версию с этой же схемой. Иначе основной риск переходного периода остаётся непроверенным.
Старая версия ещё может читать или записывать старый столбец. Немедленное удаление нарушит её работу и лишит команду простого отката приложения. Удаление безопаснее выполнять отдельным этапом после подтверждения, что старые экземпляры остановлены и данные перенесены.
Нет. Одна запись может успешно попасть в старую структуру и не попасть в новую, либо операции могут завершиться в разном порядке. Двойную запись следует дополнять проверками расхождений, идемпотентной обработкой повторов, мониторингом ошибок и процедурой восстановления; при возможности обе записи выполняют в одной транзакции, если это поддерживается выбранной архитектурой и не создаёт неприемлемую нагрузку.