Как правильно удалять поле из базы данных и модели без поломки кода
Что проверяют
Понимаете ли вы, что при выкатке старый и новый код какое-то время работают одновременно, и знаете ли схему удаления в два релиза. Следом обычно спрашивают, как сделать это в Django или Alembic и как откатиться.
Как отвечать
Проблема в том, что ORM перечисляет все поля модели в SELECT и INSERT. Если миграция удалила колонку, а старые инстансы ещё живы, они падают с column does not exist. Поэтому удаление разносят по шагам, это схема expand/contract:
- релиз 1, код: убираете все обращения к полю: сериализаторы, формы, админку,
values()иonly(), raw SQL, фоновые задачи. Если колонкаNOT NULLбез дефолта, сначала отдельной миграцией делаете еёnull=True, иначе новый код не сможет вставлять строки; - релиз 1, состояние Django: поле убираете из модели, а миграцию оборачиваете в
SeparateDatabaseAndState: вstate_operationsидётRemoveField,database_operationsпустой. ORM про колонку забывает, а физически она остаётся; - релиз 2, база: когда старых инстансов не осталось, отдельной миграцией удаляете колонку. В Django это
RunSQLсALTER TABLE ... DROP COLUMN, в Alembicop.drop_columnв своей ревизии. Автогенерация Alembic предложит удаление сразу, его разносят руками.
Про PostgreSQL стоит сказать: DROP COLUMN быстрый, меняет только метаданные, но берёт блокировку ACCESS EXCLUSIVE. Если на таблице висит долгая транзакция, миграция встанет в очередь и заблокирует все запросы за собой, поэтому перед ней задают lock_timeout. Откатить DROP без бэкапа нельзя, ценные данные выгружают заранее.
Где спотыкаются
Удаляют поле из модели и прогоняют makemigrations одним коммитом. При rolling deploy старые поды падают на каждом запросе к таблице, пока не закончится выкатка.
Забывают про NOT NULL. Новый код делает INSERT без колонки, а база отвечает нарушением ограничения.
Ищут использования только в Python-коде. Колонку ещё читают представления в базе, триггеры, выгрузки для аналитики и соседние сервисы. На зависимом представлении DROP COLUMN упадёт, а CASCADE молча снесёт и само представление.