Почему развертывание баз данных отличается: скрытая сеть зависимостей

У вас есть production-база данных, которая работает годами. В один прекрасный день вам нужно добавить колонку в таблицу orders. Кажется, что всё просто. Основное приложение читает только те колонки, о которых знает, так что новая колонка не должна причинить вреда.

Но потом падает ночной батч-джоб. Еженедельный отчет начинает выдавать мусорные цифры. Сервис другой команды внезапно выбрасывает ошибки, которых вы раньше не видели.

Что случилось? Вы изменили одну таблицу, и всё сломалось.

У базы данных больше потребителей, чем вы думаете

Во многих организациях одна база данных редко обслуживает только одно приложение. Со временем она обрастает потребителями. О некоторых вы знаете. О некоторых забыли. Некоторые были написаны людьми, которые уволились годы назад.

Вот что может обслуживать типичная production-база данных:

Диаграмма ниже показывает, как одна production-база данных соединяется со множеством разных потребителей, каждый со своим паттерном доступа:

flowchart TD DB[Production Database] DB -->|reads/writes| Web[Main Web Application] DB -->|reads/writes| API[Internal API Services] DB -->|writes| Batch[Nightly Batch Jobs] DB -->|reads| Report[Weekly Reporting Scripts] DB -->|reads| AdHoc[Ad-hoc Queries - Data Team] DB -->|reads/writes| Legacy[Legacy Services]
  • Основное веб-приложение, которым пользуются клиенты
  • Внутренние API-сервисы, которые поддерживают другие команды
  • Ночные батч-джобы, обрабатывающие тысячи строк
  • Еженедельные скрипты отчетности, питающие дашборды
  • Ad-hoc запросы от команды данных или бизнес-аналитиков
  • Легаси-сервисы, к которым никто не хочет прикасаться, но они всё ещё работают

Каждый из этих потребителей обращается к базе данных по-своему. Веб-приложение может читать колонку status для отображения страницы. Батч-джоб может писать тысячи строк через INSERT ... SELECT *. Скрипт отчетности может полагаться на конкретное представление, которое объединяет несколько таблиц. Аналитик может иметь сохраненный запрос, который ожидает колонки в определенном порядке.

Когда вы меняете схему, вы меняете её не только для своего приложения. Вы меняете её для каждого из этих потребителей.

Настоящая проблема: вы не знаете, кто зависит от этой базы данных

Самая сложная часть изменений схемы базы данных — не техническая работа. Это проблема обнаружения.

Документация часто неполна или устарела. Код приложений, которые больше не активны, всё ещё может числиться как потребители. Батч-джобы, запускающиеся раз в год, легко пропустить. Сервисы, управляемые другими командами, могут никогда не связываться с командой, вносящей изменения.

Нельзя безопасно менять то, что вы не понимаете до конца.

Почему обратная совместимость важна

Самый безопасный подход к изменениям схемы — обеспечить обратную совместимость. Это означает, что изменение не должно ломать существующий код, который ещё не обновлён.

Вот практические паттерны, снижающие риск:

Новые колонки должны быть nullable или иметь значение по умолчанию. Обязательная колонка без значения по умолчанию сломает любой INSERT, который её не включает. Если нужно добавить обязательную колонку, сначала добавьте её как nullable, заполните данные, затем сделайте её обязательной отдельным изменением.

Избегайте прямого изменения типов колонок. Вместо этого добавьте новую колонку с новым типом, постепенно мигрируйте данные, обновите всех потребителей для использования новой колонки и только потом удалите старую. Этот процесс может занять недели или месяцы, но он предотвращает даунтайм.

Будьте осторожны с SELECT *. Запросы, выбирающие все колонки и мапящие результаты на фиксированную структуру данных, сломаются при появлении новых колонок. Если вы контролируете потребляющий код, измените эти запросы так, чтобы они выбирали только нужные колонки. Если вы не контролируете код, нужно координироваться с командой-владельцем.

Не удаляйте колонки или таблицы без проверки. Колонка или таблица, которые кажутся неиспользуемыми, могут быть задействованы в скрипте, запускающемся раз в квартал. Проверьте логи запросов, код приложений и поговорите с другими командами перед удалением.

Забытый потребитель: люди

Приложения и сервисы — не единственные потребители вашей базы данных. Люди тоже выполняют ручные запросы.

У команды данных может быть скрипт, генерирующий ежемесячный отчет для руководства. У бизнес-аналитика может быть сохраненный запрос в инструменте для работы с БД, который вытягивает данные о клиентах каждое утро понедельника. Команда эксплуатации может выполнять ad-hoc запросы во время реагирования на инциденты.

Когда вы переименовываете колонку или удаляете таблицу, эти ручные запросы ломаются молча. Никто не замечает, пока отчет не выйдет неправильным или кто-то не сможет найти данные во время сбоя.

Этих человеческих потребителей сложнее отследить, потому что они часто не появляются ни в одном репозитории кода. Лучшая защита — коммуникация. Перед изменением схемы отправьте уведомление командам, которые могут быть затронуты. Дайте им дедлайн для возражений. Документируйте изменение в месте, где люди смогут его найти.

Почему откат базы данных не похож на откат приложения

Когда развертывание приложения идет не так, вы можете откатиться к предыдущей версии. Старый код заменяет новый, и система восстанавливается.

Изменения базы данных работают не так.

Если вы добавили колонку и затем нужно откатиться, колонка не исчезает автоматически. Данные, записанные в эту колонку, остаются. Если вы изменили тип колонки и затем откатились, существующие данные могут не соответствовать старому типу. Если вы удалили колонку, данные пропадают, если у вас нет бэкапа.

Хуже того, проблемное изменение базы данных затрагивает всех потребителей одновременно. В то время как откат приложения влияет только на пользователей этого приложения, откат базы данных каскадно распространяется на каждый сервис, скрипт и человека, которые касаются базы данных.

Вот почему развертывание баз данных требует больше планирования, больше тестирования и больше координации, чем развертывание приложений. Радиус поражения больше, а возможности восстановления ограниченнее.

Практический чек-лист перед следующим изменением схемы

Прежде чем выполнить ALTER TABLE в production, пройдитесь по этому списку:

  • Перечислите каждое приложение, сервис и скрипт, которые обращаются к этой таблице
  • Проверьте запросы SELECT *, которые могут сломаться
  • Убедитесь, что новые колонки имеют значения по умолчанию или nullable
  • Подтвердите, что ни одна удаляемая колонка или таблица не используется
  • Уведомите команды, которые могут выполнять ручные запросы к этой таблице
  • Имейте план отката, учитывающий данные, записанные во время изменения
  • Протестируйте изменение в staging-среде, которая зеркалирует production-потребителей

Вывод

Изменения схемы базы данных — это не просто проблемы базы данных. Это проблемы координации, затрагивающие каждого потребителя, подключенного к этой базе. Чем больше у вас потребителей, тем осторожнее нужно быть. Обратная совместимость, тщательное обнаружение и четкая коммуникация — не опция. Это разница между гладким развертыванием и production-инцидентом, который валит несколько систем одновременно.