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

Базы данных

Таксономия сбоев миграций и дисциплина продакшна

Суть Девять именованных режимов отказа — заморозка очереди блокировок, INVALID-индекс, WAL-наводнение, дрейф схемы — каждый с сигналом обнаружения и устойчивым исправлением.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min

Шесть месяцев после внедрения pipeline миграций у команды не было инцидентов с очередью блокировок. Затем обновление бэкфила генерирует 40 ГБ WAL за десять минут — лаг репликации достигает 120 секунд, чтения из standby устаревают, и две read-реплики возвращают строки, противоречащие друг другу. Миграция прошла нормально; вокруг неё упала база.

Девять режимов отказа миграций

РежимСигналУстойчивое исправление
(a) Заморозка очереди блокировокТаблица заморожена, pool исчерпан, 503lock_timeout + повторы (урок 03)
(b) INVALID-индексpg_indexes indisvalid = false после деплояDROP INDEX CONCURRENTLY + повтор; алерт на indisvalid
(c) Дедлок миграцииERROR: deadlock detected в логе миграцииСериализация через advisory lock; никогда не запускать параллельные миграции на связанных таблицах
(d) Уничтожение данных при откатеПотеря данных обнаружена после down-миграцииНикогда не использовать down-миграции в продакшне; использовать прямые исправления
(e) Дрейф схемы на репликахЗапросы к standby падают; метрика лага репликации растётБлокировать деплой кода до почти нулевого лага реплик; использовать replica-aware инструменты
(f) WAL-наводнение бэкфилаСкорость генерации WAL растёт; лаг реплик увеличивается; диск заполняетсяПакетные UPDATE по 1к–10к строк; pg_sleep между пакетами; мониторинг скорости WAL
(g) Скрытая перезапись из-за волатильного дефолтаМиграция заняла минуты; таблица неожиданно перезаписанаSquawk ловит DEFAULT now() в CI; константный дефолт + обновление после миграции
(h) NOT NULL без бэкфилаALTER COLUMN SET NOT NULL падает при примененииСначала бэкфил; использовать паттерн NOT VALID + VALIDATE (урок 04)
(i) RENAME во время rolling deployСтарые поды: ошибки column does not existExpand-contract вместо одношагового переименования (урок 05)

WAL-наводнение бэкфила подробно

Наивный бэкфил запускает один большой UPDATE:

-- НИКОГДА не делать этого на большой таблице:
UPDATE users SET handle = username WHERE handle IS NULL;

На 100 млн строк это генерирует запись WAL (Write-Ahead Log) для каждой обновлённой строки — потенциально 20–50 ГБ WAL за минуты. Реплики должны потребить этот WAL перед обслуживанием чтений; лаг репликации вырастает до минут и более. В это окно standby read-реплики возвращают устаревшие данные. Если лаг превышает max_standby_streaming_delay, Postgres отменяет конфликтующие запросы на standby.

Устойчивое исправление: пакеты по 1к–10к строк с пространством для дыхания:

DO $$
DECLARE
  batch INT;
BEGIN
  LOOP
    UPDATE users SET handle = username
    WHERE handle IS NULL
      AND ctid IN (
        SELECT ctid FROM users WHERE handle IS NULL LIMIT 5000
      );
    GET DIAGNOSTICS batch = ROW_COUNT;
    EXIT WHEN batch = 0;
    PERFORM pg_sleep(0.1);
  END LOOP;
END $$;

Мониторьте SELECT * FROM pg_stat_replication — следите, чтобы sent_lsn - replay_lsn оставалось около нуля во время бэкфила.

Дрейф схемы на репликах

Миграция, применённая на primary, распространяется на реплики через потоковую репликацию. Лаг репликации (нормальный диапазон: менее 1 с; под нагрузкой: 5–30 с) означает, что реплики могут видеть старую схему секунды после коммита миграции. Если код деплоится до нагона репликации:

  • Read-реплики обслуживают запросы к старой схеме.
  • Новый код, ожидающий новую колонку, получает NULL или ошибки от standby-чтений.

Устойчивое исправление: блокировать раскатку кода до приближения лага репликации к нулю. Мониторьте через pg_stat_replication.replay_lag на primary. В pre-deploy проверках большинства инструментов миграций включён запрос к replica-lag.

Squawk CI и стратегическая позиция по миграциям

Squawk (Linux Foundation) парсит SQL миграции и предупреждает или выдаёт ошибки на небезопасных паттернах:

  • ADD COLUMN with volatile DEFAULT → ошибка
  • ALTER COLUMN TYPE без проверки совместимости типов → ошибка
  • CREATE INDEX без CONCURRENTLY → ошибка
  • RENAME COLUMN / TABLE → предупреждение
  • DROP COLUMN без подтверждения предварительного деплоя кода → предупреждение

Запускайте Squawk на каждом PR, затрагивающем migrations/**. Стоимость: менее 30 с на PR с миграцией. Польза: ловит наиболее распространённые режимы отказа до слияния.

Стратегическая позиция: относитесь к коду миграций с той же дисциплиной, что к коду приложения — PR-ревью, CI-lint, деплой на стейджинг на данных продакшн-размера, запись в runbook, наблюдаемость по времени выполнения и получению блокировки. Старшие команды выпускают ломающие изменения регулярно; разница в том, что каждое изменение спланировано, прочёсано линтером, наблюдаемо и поддаёт прямому откату.

Целевые показатели наблюдаемости миграций
Порог алерта: повторы миграции
Больше 3 — страницовать on-call
Порог алерта: INVALID-индекс после деплоя
Любой — страницовать
Порог алерта: длительность миграции
Больше 30 с — предупредить (перезапись?)
Порог алерта: лаг репликации при бэкфиле
Больше 10 с — замедлить пакеты
Время выполнения Squawk CI
Менее 30 с на PR с миграцией
Изменения схемы в продакшне (зрелые команды)
Ежедневно
Почему это работает

Почему Postgres использует WAL для реплик, а не просто копирует изменённые строки? WAL — источник истины для восстановления после сбоя и точечного восстановления. Каждое изменение записывается как запись WAL до применения к heap. Потоковая репликация просто читает WAL и воспроизводит его на standby. Это означает, что операции бэкфила, затрагивающие миллионы строк, генерируют миллионы записей WAL — нет способа подавить генерацию WAL для DML. Пакетирование держит объём WAL управляемым, ограничивая число строк, изменяемых за транзакцию.

Викторина

Большой единственный UPDATE-бэкфил генерирует 40 ГБ WAL за минуты. Какой первый наблюдаемый симптом в схеме primary + 2 реплики?

Викторина

Squawk запускается в CI и обнаруживает `CREATE INDEX ON orders(user_id)` (без CONCURRENTLY). Какой правильный ответ?

Викторина

Миграция применяется на primary. Лаг репликации сейчас 15 секунд. Что происходит при немедленном запуске деплоя кода?

Вспомните перед уходом
  1. 01
    Почему большой однооператорный UPDATE-бэкфил наводняет WAL и какова рекомендация по размеру пакета?
  2. 02
    Что такое дрейф схемы на репликах и как блокировка деплоя кода на лаге репликации предотвращает его?
  3. 03
    Назовите четыре вещи, которые Squawk проверяет в CI, и объясните, почему каждая небезопасна без проверки.
Итог

Старшая дисциплина миграций называет девять режимов отказа и строит наблюдаемость для каждого. Заморозка очереди блокировок (режим a) — наиболее частый — исправление: lock_timeout + повторы. INVALID-индекс (b) обнаруживается мониторингом indisvalid после деплоя — исправление: DROP INDEX CONCURRENTLY + повтор. WAL-наводнение бэкфила (f) вызывает рост лага репликации — исправление: пакеты по 1к–10к строк с pg_sleep. Дрейф схемы на репликах (e) вызывает устаревшие чтения после быстро распространяющегося DDL — исправление: блокировать деплой кода до почти нулевого лага репликации. Squawk CI ловит небезопасный DDL во время PR: волатильные дефолты, неконкурентные индексы, переименования и изменения типа без проверки совместимости. Зрелые команды выпускают изменения схемы ежедневно, потому что их инструменты делают безопасность наименее затратным путём, а не исключительной дисциплиной.

Связанные уроки
встречается в258
Продолжить восхождение ↑Миграции схемы: тест с выбором ответа
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.