Короткий ответ
Нужно использовать паттерн expand-contract (expand/migrate/contract): сначала добавить новую схему, оставаясь совместимой со старым кодом (expand), задеплоить код, который умеет работать с обеими схемами, затем перевести весь трафик на новую версию и только потом, отдельным релизом, убрать старые колонки/таблицы (contract) — так старая и новая версия приложения могут одновременно безопасно работать с одной базой.
Как это работает подробнее
во время rolling update или canary какое-то время одновременно работают поды со старым и новым кодом, обращаясь к одной и той же базе. Если миграция ломает совместимость со старым кодом (например, переименование или удаление колонки, NOT NULL без default), старые поды начнут падать с ошибками ещё до того, как обновление завершится.
Пример кода
example.sql
-- Релиз 1 (expand): добавляем новую колонку, не трогая старую
ALTER TABLE orders ADD COLUMN total_cents INTEGER;
-- Код обновлён: пишет и в total (старое поле), и в total_cents (новое)
-- Релиз 2: код читает только total_cents, старое поле total больше не используется
-- Релиз 3 (contract), только после полной раскатки релиза 2:
ALTER TABLE orders DROP COLUMN total;Что использовать на практике
- На практике. безопасные миграции — это аддитивные изменения (новая nullable-колонка, новая таблица), которые не ломают запросы старой версии; удаляющие или переименовывающие изменения всегда выносятся в отдельный последующий релиз, когда уже гарантированно нет подов со старым кодом. Для сложных случаев используют dual-write (запись в обе схемы одновременно) на переходный период.
- Пример. Ожидаемое поведение: на всех трёх релизах при одновременной работе старых и новых подов запросы к базе не падают с ошибкой «column does not exist», потому что обе версии кода на каждом этапе совместимы с текущей схемой.
Подводные камни
- Частые ошибки. выполняют `ALTER TABLE ... DROP COLUMN` и деплой нового кода в одном релизе — при rolling update часть ещё работающих старых подов тут же падает на запросах к удалённой колонке; делают миграции неидемпотентными, из-за чего повторный запуск (например, после сбоя пайплайна) приводит к ошибке или дублированию данных.