<? phpukraine СПІВБЕСІДИ
Пошук по платформі
DEVOPS · SENIOR

Як безпечно застосовувати міграції БД при zero-downtime деплої?

Схема змінюється у фази expand і contract: спершу зміни, сумісні зі старим кодом, потім деплой коду, який працює з обома варіантами, і лише після повного викочування прибирання старого.

Як перейменувати колонку без простою?
Чому не можна додавати NOT NULL без default на великій таблиці?
Що таке expand and contract?
міграції zero-downtime деплой

Під час zero-downtime деплою стара й нова версії коду певний час працюють одночасно з однією базою. Тому кожна міграція має бути сумісна з попереднім кодом, а кожна версія коду з попередньою і наступною схемою. Цей принцип називають expand and contract: спершу розширення схеми, сумісне з усім, потім переключення коду, і лише після повного викочування прибирання старого.

Безпечні операції: додати nullable-колонку, нову таблицю, індекс через CONCURRENTLY. Небезпечні: видалення, перейменування, зміна типу, NOT NULL на існуючій колонці. Небезпечні операції розкладаються на кілька релізів. Перейменування колонки займає чотири: додати нову, писати в обидві й читати стару, читати нову й писати в обидві, писати лише в нову й видалити стару. Між другим і третім кроком іде backfill батчами по первинному ключу, щоб не тримати довге блокування й не роздувати WAL.

Окрема категорія — DDL, який блокує таблицю. У PostgreSQL додавання NOT NULL сканує таблицю під блокуванням; обхід через CHECK ... NOT VALID, VALIDATE CONSTRAINT без блокування запису і потім SET NOT NULL. Індекс без CONCURRENTLY зупиняє запис на час побудови. Будь-який DDL запускається з lock_timeout, щоб не чекати блокування вічно й не ставити в чергу всі запити за собою.

Міграції виконуються один раз, окремим кроком до перемикання трафіку, а не в entrypoint кожної репліки, і мають план відкату. Успіх міграції є умовою розкатки нового образу.

-- Задача: перейменувати users.name у users.full_name без простою

-- Реліз 1 (expand): нова nullable-колонка, миттєво
ALTER TABLE users ADD COLUMN full_name text;

-- Реліз 2: код пише в обидві колонки, читає стару. Backfill батчами у фоні
UPDATE users SET full_name = name
WHERE id BETWEEN 1 AND 10000 AND full_name IS NULL;
-- ... наступні діапазони з паузою

-- Реліз 3: код читає full_name. NOT NULL без довгого блокування (PostgreSQL)
ALTER TABLE users ADD CONSTRAINT users_full_name_nn CHECK (full_name IS NOT NULL) NOT VALID;
ALTER TABLE users VALIDATE CONSTRAINT users_full_name_nn;   -- сканує без блокування запису
ALTER TABLE users ALTER COLUMN full_name SET NOT NULL;      -- PG 12+ використає CHECK, без скану
ALTER TABLE users DROP CONSTRAINT users_full_name_nn;

-- Індекс без блокування запису
CREATE INDEX CONCURRENTLY users_full_name_idx ON users (full_name);

-- Реліз 4 (contract): код більше не торкається name
ALTER TABLE users DROP COLUMN name;

-- Запобіжник для будь-якого DDL: не чекати блокування вічно
SET lock_timeout = '3s';
SET statement_timeout = '30s';
Назву патерну expand and contract і розуміння, що під час деплою одночасно працюють стара й нова версія коду з однією базою.
Що кожна міграція має бути сумісна з попередньою версією коду: додавання nullable-колонки або нової таблиці безпечне, видалення й перейменування ні.
Що перейменування робиться в кілька релізів: нова колонка, подвійний запис, backfill батчами, перемикання читання, видалення старої.
Що деякі DDL блокують таблицю або переписують її: ALTER з NOT NULL без default, зміна типу, індекс без CONCURRENTLY у PostgreSQL, і як це обійти.
Що міграції запускаються один раз до перемикання трафіку, окремим кроком із lock_timeout, а не всередині кожного контейнера на старті.
Видаляти або перейменовувати колонку в тому ж релізі, що й код: під час перекриття версій одна з них падає.
Додавати NOT NULL колонку без default на таблиці з мільйонами рядків і ловити повне переписування або блокування.
Створювати індекс без CONCURRENTLY у PostgreSQL і блокувати запис на хвилини.
Робити backfill одним UPDATE на всю таблицю замість батчів і тримати блокування й WAL.
Запускати migrate у entrypoint кожного контейнера й отримувати гонку між репліками.
ПОРАДА

Ключове слово — expand and contract. Назвіть його, опишіть обидві фази і наведіть приклад з перейменуванням колонки на чотири релізи.

оновлено 3 вересня 2026 · ліцензія CC-BY-SA-4.0 Знайшли неточність? Напишіть →
ПЕРЕВІРТЕ СЕБЕ

Expand-migrate-contract: нова схема має працювати і зі старим, і з новим кодом під час викочування.