Какие проблемы могут возникнуть при добавлении нового поля с дефолтным значением в горячую таблицу с миллионами строк?
Что проверяют
Знаете ли вы, как ваша СУБД на самом деле выполняет ALTER TABLE, и умеете ли выкатывать миграции под живой нагрузкой. Ответ «таблица перепишется целиком» устарел, «это мгновенно» опасно неполон. Следом просят расписать план миграции по шагам.
Как отвечать
В PostgreSQL начиная с 11 версии:
- константный дефолт:
ADD COLUMN status text NOT NULL DEFAULT 'new'меняет только метаданные. Значение хранится в каталоге и подставляется старым строкам при чтении, перезаписи нет; - волатильный дефолт:
DEFAULT gen_random_uuid()илиclock_timestamp()считается для каждой строки, и таблица переписывается подACCESS EXCLUSIVE. На миллионах строк это минуты недоступности; - очередь блокировок: даже быстрый
ALTERберётACCESS EXCLUSIVE. Если на таблице висит долгая транзакция,ALTERждёт её, а за ним встают все новыеSELECT. ОтсюдаSET lock_timeout = '3s'и повтор при неудаче.
В MySQL 8 добавление колонки обычно проходит как ALGORITHM=INSTANT, на старых версиях берут gh-ost или pt-online-schema-change.
Со стороны Python:
- в Django 5 появился
db_default, он задаёт дефолт в самой базе. ОбычныйdefaultDjango держит в Python и после добавления колонки снимает в базе; - в Alembic дефолт на стороне базы задаётся через
server_default; - вычисляемое значение заполняют отдельно: колонка добавляется с NULL, бэкфилл идёт пачками с коммитом после каждой;
- индекс строят через
CREATE INDEX CONCURRENTLY, в Django этоAddIndexConcurrentlyв неатомарной миграции.
Где спотыкаются
Делают бэкфилл одним UPDATE на всю таблицу. Это одна огромная транзакция: блокировки строк, всплеск WAL, отставание реплик и раздутая таблица.
Забывают про lock_timeout и роняют сервис не самой миграцией, а очередью запросов за ней.
Включают NOT NULL на заполненной колонке обычным ALTER, который сканирует таблицу под эксклюзивной блокировкой. Безопаснее сначала CHECK (col IS NOT NULL) NOT VALID, затем VALIDATE CONSTRAINT, и только потом SET NOT NULL: с 12 версии PostgreSQL опирается на проверенное ограничение и повторно таблицу не сканирует.