Схемы и миграции: меняем модель постепенно
Клуб хочет показывать короткое имя участника, сохраняя прежнее полное name. Новая версия API умеет display_name, старая ещё работает несколько минут во время deployment. Если миграция сразу переименует колонку, старые процессы начнут ошибаться. Изменение схемы — часть перехода между версиями, а не только одна успешная команда ALTER TABLE.
Имя объекта и search_path
Схема club объединяет учебные таблицы. PostgreSQL допускает несколько одинаковых имён таблиц в разных схемах. Если запрос пишет только members, search_path определяет, где искать объект. Это удобно, но делает неявную зависимость частью исполнения. В примерах курса квалифицируем club.members, чтобы случайная другая схема не изменила смысл.
SELECT current_schema();
SHOW search_path;
SELECT id,name FROM club.members ORDER BY id;Последний запрос независимо от первого имени схемы возвращает Анну, Бориса и Веру. Первые команды показывают текущую конфигурацию вашей сессии, поэтому их точный ответ зависит от установки. Не объявляйте обязательно public результатом любой машины. Права CREATE в схемах и возможность подставить чужие объекты требуют анализа; отдельная схема сама по себе не является полной изоляцией клиента.
Миграция получает версию и определённый переход. Репозиторий хранит файл SQL, система deployment — сведения о выполненных версиях. Запуск старого setup повторно не является миграцией. IF NOT EXISTS может скрыть объект с другой структурой; «ошибки нет» ещё не доказывает соответствие нужной версии.
Expand добавляет совместимую возможность
Сначала добавляем новую необязательную колонку. Старые запросы продолжают читать name, новые умеют fallback. Не удаляем прежний путь до завершения перехода. В комплекте migration.sql проводит такой dry run в транзакции и откатывает его, сохраняя golden seed.
BEGIN;
SET LOCAL lock_timeout='1s';
ALTER TABLE club.members ADD COLUMN display_name text;
UPDATE club.members SET display_name=name
WHERE display_name IS NULL;
SELECT id,name,display_name FROM club.members ORDER BY id;
ROLLBACK;Ожидаемые пары name/display_name: Анна/Анна, Борис/Борис, Вера/Вера. После ROLLBACK новой колонки нет. lock_timeout ограничивает ожидание блокировки для команд транзакции; это не общий срок произвольной работы. На большой таблице заполнение всех строк сразу может создать длительную транзакцию и нагрузку. Маленький dry run не доказывает, что production backfill безопасно выполнить одним UPDATE.
Совместимость имеет четыре сочетания
Старый writer пишет только name; новый writer умеет display_name. Старый reader читает прежний договор; новый reader должен понимать ещё не заполненные строки. Проверяем old→old, old→new, new→old и new→new. Default и fallback не должны незаметно менять предметный смысл. Отдельно проверьте импорт и фоновые задачи: версия браузера не описывает всех writers.
В нашем простом переходе новая программа может читать COALESCE(display_name,name), пока display_name отсутствует в части строк. Но SQL с упоминанием ещё не созданной колонки не работает до expand. Deployment выбирает порядок: схема готова, потом код начинает обращаться к новому полю. Новое поле нельзя назвать существующим во всех версиях только на основании fallback.
Если короткое имя и полное имя стали разными фактами, dual write одинакового значения не навечно правильная политика. Нужно определить, какие изменения затрагивают оба и что считается источником истины. Временная совместимость решает переход, а не подменяет окончательную модель.
Backfill не должен затереть новую правку
Фоновая задача читает старое name=«Анна», новая программа сохраняет display_name=«Аня», затем backfill записывает прежнее значение. Если UPDATE не проверяет условие/версию, новая правка потеряна. Для простого заполнения только отсутствующего поля условие WHERE display_name IS NULL защищает уже заданное значение. Для более сложной копии объектов нужен version compare и правила конкурентных writers.
Backfill удобно выполнять порциями по устойчивому ключу, сохранять progress и измерять нагрузку. Пауза и restart не должны начинать повтор как новую независимую правку. Сверка проверяет содержимое и полноту, а не только число строк. Три одинаковых COUNT могут скрыть три неправильных имени.
Contract и откат
Удаление прежнего поля выполняется, когда поддерживаемые readers/writers его больше не используют и данные проверены. Backup, старые задания и длительные процессы тоже могут требовать прежнюю схему. Обратный запуск старого кода после новых записей не всегда безопасен: он может не понимать новое поле или новые ограничения. Rollback plan включает совместимую версию и данные, а не только прежний бинарник.
DDL берёт блокировки. Для большого объекта нужно знать конкретный ALTER, длительность, ожидание и возможную конкуренцию. Некоторые операции, например отдельные варианты создания индекса, имеют особые правила транзакции. Не считайте универсальным рецепт «весь DDL всегда один BEGIN»: сверяйте команду с PostgreSQL manual. Новая колонка нашего dry run не означает разрешение любой миграции рабочих данных.
Самостоятельная лаборатория
Отведите 30 минут. Выполните migration.sql и подтвердите, что после rollback SELECT display_name выдаёт отсутствие колонки. Затем на новой учебной базе проведите сценарий в открытой транзакции: добавьте колонку, заранее установите Анне «Аня», заполните остальные только WHERE IS NULL и прочитайте три строки. Закончите ROLLBACK, чтобы не менять seed.
Критерии: display_name Анны не затёрто; новые readers имеют порядок expand до использования; составлена таблица четырёх сочетаний; backfill можно продолжить без потери новой правки; contract и rollback имеют явные условия. Не выполняйте вторую половину опыта, если добавление колонки не удалось: ошибка должна остановить сценарий.
Разбор
Результат Анна/Аня, Борис/Борис, Вера/Вера показывает различие нового факта и первоначального заполнения. Условие IS NULL оставляет правку. После отката схема исходная, поэтому последующие уроки не зависят от того, выполнялся ли dry run. В production миграция должна фиксироваться отдельно, иметь журнал версии и проверенный план нагрузки; ручная история terminal не заменяет воспроизводимый deployment.