Когда можно безопасно удалять старые колонки в БД? Фаза Contract в паттерне Expand-Contract
Вы перевели весь код приложения на новый формат колонок. Деплой прошёл гладко. В логах нет ошибок. Команда готова почистить старую схему и двигаться дальше.
Но стоит ли удалять ту колонку прямо сейчас?
Ответ почти всегда — нет. Удаление старых структур базы данных слишком рано — одна из самых частых причин production-инцидентов, которые возникают как будто из ниоткуда. Пакетный job, запускающийся раз в месяц, отчётный запрос, написанный полгода назад, или легаси-сервис, просыпающийся только при обработке конца квартала — всё это может молча падать до того момента, когда им действительно понадобится выполниться.
Финальная фаза паттерна expand-contract называется contract. Название отражает идею сжатия или удаления структур, которые больше не используются. Но чтобы перейти к этой фазе, недостаточно просто убедиться, что основное приложение переключилось.
Кто ещё работает со старой структурой?
Прежде чем что-то удалять, нужно ответить на один вопрос: кто ещё читает или пишет в старую схему?
Ваше основное приложение могло переехать. Но есть и другие потребители, о которых вы могли не подумать:
- Ночной пакетный job, который всё ещё ссылается на старую колонку
- Ручные запросы от команды данных для ad-hoc-анализа
- Отчётная система, генерирующая ежемесячные PDF с использованием старой таблицы
- Внутренний инструмент, развёрнутый полгода назад и не обновлявшийся
- Интеграция со сторонним сервисом, отправляющим данные в старом формате
Эти зависимости легко пропустить, потому что они не следуют тому же циклу деплоя, что и основное приложение. Они могут даже не находиться в том же репозитории. Некоторые из них могут быть вовсе не кодом — например, SQL-скрипты, сохранённые на чьём-то ноутбуке.
Как выявить скрытые зависимости
Самый простой способ найти такие зависимости — проверить логи базы данных. PostgreSQL, MySQL и Oracle — все они записывают выполняемые запросы. Вы можете отфильтровать запросы, которые обращаются к колонке или таблице, которую планируете удалить.
Следующая блок-схема обобщает процесс принятия решения о безопасности удаления старой колонки:
Если ваша БД не логирует запросы по умолчанию, можно временно включить эту возможность. В PostgreSQL есть pg_stat_statements для отслеживания статистики запросов. В MySQL — Performance Schema. Оба инструмента покажут, какие запросы всё ещё обращаются к старой структуре.
Второй подход — мониторинг ошибок после переключения приложения. Если что-то всё ещё пытается писать в старую колонку, вы, скорее всего, увидите ошибки в логах приложения. Но это ловит только активных потребителей. Скрипт, запускающийся раз в квартал, может не вызывать ошибку месяцами.
Именно поэтому важен период ожидания перед удалением. Некоторые команды ждут один полный цикл деплоя — обычно две недели после переключения всех приложений. Другие ждут дольше, особенно если есть ручные запросы или отчёты, используемые нетехническими командами. Универсального правила нет, но чем больше у вас неконтролируемых зависимостей, тем дольше стоит ждать.
Собственно удаление
Когда вы уверены, что зависимостей не осталось, можно приступать к удалению. SQL-команды тривиальны:
Вот практический пример SQL-запроса, который вы выполните, а также проверка, что никакие другие объекты БД больше не зависят от колонки:
-- Сначала проверьте, есть ли представления, функции или триггеры, ссылающиеся на колонку
SELECT DISTINCT
OBJECT_SCHEMA,
OBJECT_NAME,
OBJECT_TYPE
FROM INFORMATION_SCHEMA.ROUTINES
WHERE ROUTINE_DEFINITION LIKE '%old_column_name%'
UNION
SELECT
TABLE_SCHEMA,
TABLE_NAME,
'VIEW'
FROM INFORMATION_SCHEMA.VIEWS
WHERE VIEW_DEFINITION LIKE '%old_column_name%';
-- Когда безопасность подтверждена, удалите колонку
ALTER TABLE users DROP COLUMN old_legacy_status;
-- Почистите индексы, которые существовали только для старой колонки
DROP INDEX IF EXISTS idx_old_status ON users;
Сначала выполните проверку зависимостей, затем удаление. Если проверка вернула хоть одну строку — вы нашли скрытого потребителя, которого нужно обновить перед безопасным удалением.
- Для колонки:
ALTER TABLE ... DROP COLUMN - Для таблицы:
DROP TABLE - Для ограничения:
ALTER TABLE ... DROP CONSTRAINT
Но порядок важен. Удаляйте сначала колонки или таблицы, которые с наименьшей вероятностью используются, затем подождите несколько дней перед удалением остальных. Такой поэтапный подход даёт вам страховку. Если что-то сломается после первого удаления, у вас ещё будет время среагировать, пока более критичные структуры не исчезли.
Не забывайте про индексы и триггеры
Часто упускают из виду очистку связанных объектов БД. Индексы, которые существовали только для старой колонки, нужно удалить. Триггеры, ссылающиеся на старую таблицу, следует убрать. Если оставить их, база данных накапливает мёртвый груз, который замедляет запись и усложняет будущие миграции.
Краткий чек-лист перед завершением фазы contract:
- Убедитесь, что ни один активный запрос не обращается к старой структуре (проверьте логи как минимум за один полный цикл)
- Проверьте, что все пакетные job'ы, отчёты и ручные скрипты обновлены
- Удалите индексы, которые обслуживали только старую колонку
- Удалите триггеры, ссылающиеся на старую таблицу
- Удаляйте поэтапно: сначала наименее рискованное, подождите, затем продолжайте
- Мониторьте ошибки приложения в течение 24–48 часов после каждого удаления
Главный вывод
Фаза contract — это не про скорость. Это про уверенность. Удаление старой схемы слишком рано вносит риск, который трудно обнаружить, пока не стало поздно. Дополнительный период ожидания — не потеря времени, а ваш защитный буфер против скрытых зависимостей, о существовании которых вы даже не подозревали.
Когда старая схема удалена, паттерн expand-contract завершён. Вы добавили новую структуру, мигрировали всех потребителей и безопасно убрали старую, не вызвав production-инцидента. В этом и заключается суть паттерна: сделать изменения в базе данных настолько безопасными, чтобы ваша команда их не боялась.