«Совсем плохой» способ — просто сделать колонку NOT NULL:
ALTER TABLE some_big_table
ALTER COLUMN foo_field SET NOT NULL;
Почему так делать очень плохо на большой таблице?
Чтобы убедиться, что в столбце нет
NULL, Postgres сканирует всю таблицу. Такие изменения вокруг ADD CONSTRAINT/SET NOT NULL обычно требуют ACCESS EXCLUSIVE — самый сильный DDL-лок, конфликтующий со всеми операциями, включая чтения. Это легко превращается в stop the world на таблице, если операция длится долго.Правильный подход: через
NOT VALID → VALIDATEИдея — разделить «включить проверку для новых записей» и «проверить историю» на два шага.
1. Добавляем ограничение без проверки истории (не блокирует обновления надолго):
ALTER TABLE some_big_table
ADD CONSTRAINT some_big_table_foo_field_not_null
CHECK (foo_field IS NOT NULL) NOT VALID;
С флагом
NOT VALID добавление не сканирует таблицу и может быстро зафиксироваться. Новые/изменённые строки уже обязаны удовлетворять условию.2. Отдельно валидируем историю:
ALTER TABLE some_big_table
VALIDATE CONSTRAINT some_big_table_foo_field_not_null;
VALIDATE CONSTRAINT сканирует только существующие строки и берёт уже SHARE UPDATE EXCLUSIVE (гораздо мягче), так что записи не блокируются так сурово, как при прямом SET NOT NULL.Что именно могло заблокировать таблицу в нашем кейсе?
Наша SQL миграция выглядит так (все в одной транзакции):
BEGIN;
ALTER TABLE ... ADD CONSTRAINT ... CHECK (...) NOT VALID;
ALTER TABLE ... VALIDATE CONSTRAINT ...;
COMMIT;
В примере я специально указал, что оба действия выполнятся в рамках одной транзакции (многие это заметили).
Однако, когда мы используем сторонние решения для миграций (напрмер goose), мы получаем неявное повдение — миграции в одном файле будут также исполняться в рамках одной транзакции (если не укажем обратное).
В таком случае:
- Мы берем блокировку ACCESS EXCLUSIVE для добавления
CONSTRAINT- Далее эта блокировка будет удерживаться до конца работы транзакции
- Валидация нагрузки происходит долго, что приводит к тому, что транзакция превращается в долгоживующую => таблица заблокирована надолго.
Практический вывод: Делайте
ADD CONSTRAINT ... NOT VALID и фиксируйте сразу (быстрый COMMIT). А VALIDATE CONSTRAINT запускайте отдельной миграции и, желательно, в спокойный период.А чтобы не переживать насчет ваших миграций, вы можете настроить линтер: squawkhq
