Безопасная миграция БД
Большие изменения схемы без даунтайма и блокировок: backfill, swap, очистка.
Спланируй безопасную миграцию: {{change}}.
Принцип: любое изменение схемы в продакшене — в несколько шагов. Никаких "один большой ALTER".
1. Категория изменения
| Тип | Сложность | Стратегия |
|---|---|---|
| Добавить nullable колонку | Простая | Один ALTER |
| Добавить NOT NULL колонку | Сложно | Многошаговая |
| Переименовать колонку | Сложно | Через duplicate |
| Изменить тип | Сложно | Через duplicate |
| Удалить колонку | Простая (в плане SQL) | Многошаговая (код перестроить) |
| Добавить foreign key | Средняя | CONCURRENTLY |
| Добавить индекс | Средняя | CONCURRENTLY |
| Разбить таблицу | Очень сложно | Многоэтапная |
2. Pattern: добавить NOT NULL колонку
Шаг 1: ALTER TABLE ... ADD COLUMN x text NULL (nullable!)
Шаг 2: deploy код который ПИШЕТ в x но не требует
Шаг 3: backfill старых строк (batch'ами!)
Шаг 4: deploy код который ЧИТАЕТ из x
Шаг 5: ALTER TABLE ... ALTER COLUMN x SET NOT NULL (после проверки что нет NULL)
Каждый шаг — отдельный deploy. Между ними можно откатиться.
3. Pattern: переименовать колонку
Шаг 1: ALTER TABLE ... ADD COLUMN new_name (тип)
Шаг 2: deploy: код пишет В ОБА (старое + новое)
Шаг 3: backfill: copy old → new
Шаг 4: deploy: код читает из new
Шаг 5: deploy: код не пишет в old
Шаг 6: ALTER TABLE ... DROP COLUMN old (через несколько дней)
Никогда не делай ALTER ... RENAME в одной транзакции с код-деплоем.
4. Pattern: backfill больших таблиц
Не делай:
UPDATE huge_table SET x = ...; -- 10 минут lock на всё
Делай batches:
-- Скрипт повторяется
UPDATE huge_table
SET x = ...
WHERE id IN (
SELECT id FROM huge_table
WHERE x IS NULL
LIMIT 1000
);
С pg_sleep(0.1) между батчами чтобы не задушить БД.
5. Pattern: индексы без даунтайма
-- Используй CONCURRENTLY
CREATE INDEX CONCURRENTLY idx_foo ON huge_table (foo);
-- НЕ внутри транзакции!
-- Может занять часы — это нормально
Если индекс не создался (CONCURRENTLY может оставить invalid index) — дропни и пересоздай.
6. Pattern: удалить колонку
Шаг 1: deploy: код больше не использует
Шаг 2: подожди неделю чтобы убедиться что никто не использует
Шаг 3: ALTER TABLE ... DROP COLUMN
Не торопись с DROP — DROP моментален но если ошибся — данные потеряны.
7. Locking
Каждый ALTER берёт lock. Узнай какой:
- ALTER TABLE ... ADD COLUMN nullable — AccessExclusive, но быстрый
- ALTER TABLE ... ADD CONSTRAINT — может быть долгий (валидация)
- ALTER TABLE ... ALTER COLUMN TYPE — rewrite таблицы, очень долго
Для долгих:
SET lock_timeout = '5s';
ALTER TABLE ...;
Если не успело — отваливается, не блокирует прод.
8. Откат
Для каждой миграции — план отката:
- Можно ли откатить SQL? (DROP COLUMN — нельзя, данные потеряны)
- Какие коммиты в коде надо откатить?
- В каком порядке?
9. Тестирование
- Прогон на staging с продакшен-волюмом
- Замеры lock и времени
- Smoke-тест после миграции
10. Чек-лист на запуск
- Migration script готов
- Rollback script готов (или отмечено что нельзя откатить)
- Тестировано на staging
- Time-окно низкой нагрузки выбрано
- Кто-то на линии для мониторинга
- Метрики БД (locks, query duration) видны на дашборде
Анти-паттерны
- ❌
ALTER TABLE x ALTER COLUMN y TYPE int USING y::intна 100M строк - ❌ Code deploy + migration в одной транзакции
- ❌ Backfill единым UPDATE
- ❌ Удаление колонки на следующий день после прекращения использования
- ❌ Миграция без отката (для критичных изменений)
В конце дай
- Пошаговый план с конкретным SQL
- Estimated lock duration на каждом шаге
- Откатный план
- Чек-лист на запуск
Подтверждение destructive-действий
Типы (delete / leave / cancel / destroy), inline vs modal, type-to-confirm, undo как альтернатива. Без «Are you sure?» на каждый клик.
Процесс депрекации компонента
Пометить deprecated (badge, console warn, types), дать миграцию (codemod, before/after), удалить. Версии, support window, коммуникация.
План миграции дизайн-токенов
Рефактор существующих токенов без поломок прод-компонентов: codemod, opt-in flag, deprecation window, коммуникация.