awesome-everything EN
↑ Обратно к восхождению

Базы данных

Expand-contract: нулевой простой для ломающих изменений схемы

Суть Любое ломающее изменение — переименование, удаление, смена типа — разбивается на фазу expand (добавление) и фазу contract (удаление), каждая независимо деплоится и отменяется.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 14 min

Команда переименовывает users.username в users.handle одним запросом ALTER TABLE во время деплоя. Миграция коммитится мгновенно. Но rolling deploy ещё продолжается — старые поды всё ещё выполняют SQL, ссылающийся на users.username. Каждый запрос от старого пода завершается ошибкой “column does not exist”. Быстрая миграция вызвала реальный инцидент.

Почему rename/drop/type-change нельзя сделать за один шаг

Rolling deploy — единственная безопасная стратегия деплоя в любом масштабе — запускает старый и новый код одновременно от секунд до минут. Если миграция закоммичена до того, как все старые поды завершили работу:

  • Переименование: старый код ссылается на старое имя колонки → ошибки column does not exist.
  • Удаление: старый код ссылается на удалённую колонку → то же самое.
  • Смена типа: старый код ожидает int, колонка теперь text → ошибки приведения типов.

Инвариант, который должен соблюдаться в каждый момент: текущая схема одновременно совместима с предыдущей версией приложения и следующей. Одношаговое переименование, удаление или смена типа нарушает этот инвариант.

Общий фреймворк expand-contract

Каждое ломающее изменение разбивается на шесть фаз:

ФазаДействие в миграцииДействие в кодеОбе версии совместимы?
1. ExpandДобавить новый элемент рядом со старымДа — старый код не затронут
2. Dual-writeПисать и в старую, и в новуюДа — старый читает старую, новый пишет обе
3. BackfillКопировать исторические данные старая → новая пакетамиДа
4. Переключение чтенийЧитать из новой, всё ещё писать обеДа — полный откат ещё возможен
5. Остановка старых записейПисать только в новуюДа — старый элемент всё ещё присутствует
6. ContractУдалить старый элементДа — ни один код не ссылается на старый элемент

Пошаговый разбор переименования колонки: username → handle

Канонический пример. Общая длительность: 3–7 дней.

День 1 — Expand:

-- Миграция 1: мгновенно, без дефолта
ALTER TABLE users ADD COLUMN handle TEXT;

День 1 — Деплой dual-write: Код приложения теперь пишет и username, и handle при каждом INSERT/UPDATE. Старый код всё ещё читает username. Новый код читает username (пока что).

День 2 — Backfill:

-- Вне транзакции миграции (пакетный цикл):
UPDATE users SET handle = username
WHERE handle IS NULL
  AND id BETWEEN :batch_start AND :batch_end;
-- Повторять до SELECT COUNT(*) FROM users WHERE handle IS NULL = 0

Пакеты по 1к–10к строк, каждый в короткой транзакции с pg_sleep(0.1) между ними.

День 3 — Переключение чтений: Деплой кода, который читает только handle. Записи всё ещё идут в обе колонки. Мониторинг дашбордов 24 часа.

День 5 — Остановка dual-write: Деплой кода, который пишет только в handle. Ожидание завершения rolling deploy.

День 7 — Contract:

-- Миграция 4: быстро (только метаданные)
ALTER TABLE users DROP COLUMN username;

Каждый шаг независимо деплоится и отменяется. Нулевой простой на всём пути.

Down-миграции — не откат

Down-миграции (обратный SQL к up-миграции) есть во многих инструментах (файлы-партнёры down.sql). Они работают в разработке, но опасны в продакшне:

  • Down-миграция, дропающая handle, уничтожает все данные, которые новый код в неё записал.
  • Если up-миграция была ADD COLUMN, down — это DROP COLUMN — деструктивно.

Откат в продакшне идёт вперёд: напишите новую миграцию, исправляющую проблему, задеплойте её и двигайтесь вперёд. Рассматривайте down-миграции только как scaffolding для разработки.

Факты о деплое через expand-contract
Типичная длительность переименования колонки
3–7 дней
Количество деплоев при переименовании колонки
5+ отдельных PR
Размер пакета бэкфила
1к–10к строк
pg_sleep между пакетами
0.1 с (пространство для дыхания)
Каждая фаза: независимо откатываема?
Да
Направление отката в продакшне
Вперёд (новая миграция)
Почему это работает

pgroll (от Xata) автоматизирует expand-contract через Postgres-представления. При объявлении миграции, “переименовывающей username в handle”, pgroll создаёт представление v1 (раскрывает username) и представление v2 (раскрывает handle), оба поверх одной физической колонки, с триггерами для трансляции записей. Старые поды подключаются с search_path = v1; новые — с search_path = v2. По завершении миграции представление v1 дропается. Это сворачивает 5+ деплоев в одну декларативную миграцию — ценой сложности view-слоя при отладке и в выводе pg_dump.

Викторина

Почему прямая миграция RENAME COLUMN может вызвать инцидент во время rolling deploy?

Викторина

На какой фазе expand-contract откат наиболее прост и почему?

Викторина

Команда запускает down-миграцию в продакшне, которая дропает новую колонку, добавленную в up-миграции. Какие данные теряются?

Вспомните перед уходом
  1. 01
    Сформулируйте главный инвариант expand-contract и объясните, почему пропуск фазы dual-write его нарушает.
  2. 02
    Пройдите по шести фазам переименования: username → handle, назвав действие в миграции или коде на каждом шаге.
  3. 03
    Почему down-миграции небезопасны в продакшне и какова правильная стратегия отката в продакшне?
Итог

Expand-contract — единственный правильный паттерн для миграций с нулевым простоем при переименовании, удалении или смене типа. Он поддерживает суперсет схемы в каждый момент: и старая, и новая версии кода работают одновременно со схемой, поддерживающей обе. Переименование колонки превращается в пять независимых деплоев за 3–7 дней: добавить новую колонку (expand), задеплоить код с dual-write, выполнить бэкфил исторических строк пакетами, задеплоить код с переключением чтений, задеплоить код с остановкой старых записей, затем дропнуть старую колонку (contract). Down-миграции кажутся безопасными, но уничтожают данные, уже записанные новым кодом; откат в продакшне — всегда прямая миграция, исправляющая проблему.

Связанные уроки
встречается в140
Продолжить восхождение ↑Advisory-блокировки, инструменты миграций и координация деплоя
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.