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

Базы данных

ADD COLUMN: мгновенно в PG 11+ против перезаписи в старом Postgres

Суть Константное значение по умолчанию делает ADD COLUMN мгновенным на любой таблице начиная с PG 11; волатильные дефолты и NOT NULL без бэкфила всё ещё вызывают полную перезапись или блокируют записи на минуты.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на junior-высоте — поверхность
◷ 10 min

Команда деплоит ALTER TABLE orders ADD COLUMN status TEXT NOT NULL DEFAULT 'pending' на таблице с 50 млн строк. На Postgres 10 база зависает на 15 минут. На Postgres 11+ тот же запрос выполняется за миллисекунды. Одинаковый SQL, принципиально разный результат.

Как Postgres 11 сделал ADD COLUMN мгновенным

До PG 11 ALTER TABLE t ADD COLUMN c TEXT DEFAULT 'x' перезаписывал каждую строку, материализуя значение по умолчанию. На таблице с 100 млн строк это означало минуты удержания AccessExclusiveLock — каждый читатель и писатель блокировался на всё время.

Начиная с PG 11, если дефолт не является волатильным (строковый литерал, число, булево или стабильное выражение), Postgres хранит его в системном каталоге pg_attribute:

  • pg_attribute.atthasmissing = true
  • pg_attribute.attmissingval = 'x'

Существующие строки сохраняют исходное физическое хранилище — в них ничего не пишется. При SELECT старой строки Postgres читает attmissingval и синтезирует значение во время чтения. Новые строки после миграции хранят колонку обычным образом. Время удержания блокировки падает с минут до миллисекунд.

ОперацияPG 10PG 11+
ADD COLUMN c TEXT DEFAULT ‘pending’Полная перезапись — минуты на 100 млн строкМгновенно — дефолт в метаданных pg_attribute
ADD COLUMN c TEXT DEFAULT clock_timestamp()Полная перезаписьПолная перезапись — волатильный дефолт, нужна материализация для каждой строки
ADD COLUMN c TEXT (без дефолта)Мгновенно — NULL для существующих строкМгновенно — NULL для существующих строк
ADD COLUMN c TEXT NOT NULL (без дефолта, строки есть)Ошибка — существующие NULL нарушают NOT NULLОшибка — существующие NULL нарушают NOT NULL

Волатильные дефолты всё равно вызывают перезапись

DEFAULT clock_timestamp(), DEFAULT random(), DEFAULT gen_random_uuid() — любая волатильная функция — не может храниться как единое пропущенное значение, потому что каждая строка нуждается в разном результате. Postgres должен обойти каждую существующую строку, вызвать функцию и записать результат — полная перезапись таблицы под AccessExclusiveLock.

Решение: добавить колонку без дефолта (мгновенно), затем выполнить бэкфил существующих строк в пакетах через UPDATE, затем установить дефолт для будущих вставок через ALTER TABLE t ALTER COLUMN c SET DEFAULT gen_random_uuid().

Безопасное добавление NOT NULL на большой таблице

ADD COLUMN c TEXT NOT NULL DEFAULT 'x' нормально работает на PG 11+ с константным дефолтом — мгновенно, без перезаписи. Опасность возникает при добавлении NOT NULL к существующей nullable-колонке, в которой могут быть NULL:

-- НЕПРАВИЛЬНО на большой таблице: сканирует каждую строку под AccessExclusiveLock
ALTER TABLE users ALTER COLUMN email_verified SET NOT NULL;

Безопасный многошаговый паттерн (подробно разобран в уроке 04):

  1. Добавить колонку без NOT NULL: мгновенно.
  2. Бэкфил существующих NULL небольшими пакетами.
  3. Добавить CHECK (col IS NOT NULL) NOT VALID — применяется только для новых строк, без сканирования таблицы.
  4. VALIDATE CONSTRAINT — сканирует под более лёгкой блокировкой, которая не блокирует записи.
  5. ALTER COLUMN SET NOT NULL — быстро, так как constraint уже доказал валидность.
Стоимость ADD COLUMN
ADD COLUMN с константным дефолтом, PG 11+
Мгновенно (мс)
ADD COLUMN с волатильным дефолтом, любой PG
Минуты на 100 млн строк
ADD COLUMN без дефолта
Мгновенно (NULL для существующих строк)
Блокировка при ADD COLUMN
AccessExclusiveLock (кратко или долго)
Стоимость чтения для строк с missing-дефолтом
Незначительная (один lookup в pg_attribute)
Стоимость исчезает по мере UPDATE строк
Пассивно, через обычный трафик
Почему это работает

Почему Postgres ждал до версии 11 (2018), чтобы сделать ADD COLUMN мгновенным? Изменение потребовало нового механизма в коде чтения heap-кортежей: каждое чтение кортежа старого формата должно проверять atthasmissing и подставлять сохранённый дефолт. Это слегка усложнило путь чтения и потребовало тщательного тестирования. Результат оказался огромным — это устранило наиболее распространённую причину простоев из-за миграций до 2018 года.

Викторина

В PG 11+, какой вариант ADD COLUMN является мгновенным (без перезаписи таблицы)?

Викторина

Что PG 11 хранит в pg_attribute, чтобы сделать ADD COLUMN с константным дефолтом мгновенным?

Расставь шаги по порядку

Упорядочите шаги для безопасного добавления NOT NULL-колонки в таблицу с 100 млн строк:

  1. 1 ADD COLUMN с константным DEFAULT (мгновенно в PG 11+; NULL-дефолт, если не нужен)
  2. 2 Деплой кода приложения, который пишет новую колонку при каждом INSERT и UPDATE
  3. 3 Бэкфил существующих NULL-строк пакетами по 1к–10к с pg_sleep между пакетами
  4. 4 ADD CONSTRAINT ... CHECK (col IS NOT NULL) NOT VALID (мгновенно, без сканирования)
  5. 5 VALIDATE CONSTRAINT (сканирует под SHARE UPDATE EXCLUSIVE — не блокирует DML)
  6. 6 ALTER COLUMN SET NOT NULL (быстро — constraint уже доказал валидность всех строк)
  7. 7 Опционально DROP CHECK constraint (он избыточен после установки NOT NULL)
Вспомните перед уходом
  1. 01
    Как PG 11+ делает ADD COLUMN с константным дефолтом мгновенным и каково поведение при чтении старых строк?
  2. 02
    Почему волатильные дефолты всё равно вызывают полную перезапись таблицы даже в PG 11+?
  3. 03
    Какой многошаговый паттерн безопасно добавляет NOT NULL-ограничение к nullable-колонке на большой таблице?
Итог

Начиная с PG 11, ADD COLUMN с не-волатильным константным дефолтом хранит значение в pg_attribute.attmissingval и возвращает его синтетически при чтении — строки не затрагиваются, операция выполняется за миллисекунды на таблице любого размера. Волатильные дефолты (функции типа clock_timestamp()) всё равно требуют материализации для каждой строки и вызывают полную перезапись. Добавление NOT NULL к существующей nullable-колонке без сканирования бэкфила никогда не безопасно за один ALTER COLUMN SET NOT NULL на большой таблице; правильный путь разбивает работу на мгновенное добавление колонки, пакетный бэкфил, ограничение NOT VALID, VALIDATE CONSTRAINT под лёгкой блокировкой и наконец быстрый SET NOT NULL.

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

Trademarks belong to their respective owners. Editorial reference only.