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

Если вы работали с софтом несколько лет, то, вероятно, видели одну и ту же историю: приложение редизайнится, переписывается, ребрендится — или вовсе заменяется, а база данных тихо продолжает работать.
Компания может перейти от десктопного приложения к вебу, затем к мобильному, затем к «v2» на новом фреймворке. Тем не менее карточки клиентов, заказы, счета и каталог товаров часто всё ещё лежат в той же базе (или в её прямом потомке), порой с таблицами, созданными десять лет назад.
Проще говоря: код приложения — это интерфейс и поведение, и он часто меняется, потому что его относительно легко заменить. База данных — это память, и менять её рискованно, потому что она хранит историю, от которой зависит бизнес.
Простой нетехнический пример: вы можете отремонтировать магазин — новые полки, кассы, вывески — не выбрасывая при этом записи об инвентаре и чеки. Ремонт — это приложение. Записи — это база данных.
Когда заметите эту закономерность, вы начнёте по‑другому принимать решения:
В следующих разделах вы узнаете, почему базы данных склонны оставаться, почему перемещать данные сложнее, чем код, и практические подходы к проектированию и эксплуатации баз, чтобы они выживали при многократных переписываниях приложений — без превращения каждой правки в кризис.
Приложения ощущаются как «продукт», но база данных — это то место, где продукт запоминает, что произошло.
Платёжное приложение можно переделывать пять раз, но пользователи всё равно ждут, что история покупок будет доступна. Портал поддержки может сменить поставщика, но записи тикетов, возвратов и обещаний должны оставаться непротиворечивыми. Эта непрерывность живёт в хранении: клиенты, заказы, счета, подписки, события и связи между ними.
Если фича исчезнет, пользователи расстроятся. Если исчезнут данные — вы можете потерять доверие, доход и юридическую опору.
Код можно часто восстановить из контроля версий и документации. Реальную историю так не восстановишь. Нельзя «перезапустить» платежи прошлого года, воссоздать точное согласие клиента в момент его дачи или точно восстановить, что и когда было отправлено. Даже частичная потеря — пропущенные метки времени, сиротские записи, несогласованные итоги — делает продукт ненадёжным.
Большинство данных становится полезнее с течением времени:
Поэтому команды рассматривают данные как актив, а не побочный продукт. Свежий редизайн интерфейса редко заменяет годы исторической правды.
Со временем организации невольно стандартизируют базу как общий эталон: экспортируют из неё таблицы, строят дашборды, согласовывают финансовые процессы и используют «известно‑правильные» запросы для повторяющихся задач.
Вот эмоциональная суть долговечности базы: она становится памятью, на которую опираются все — даже если вокруг постоянно меняется приложение.
Базу данных редко «владеет» только одно приложение. Со временем она становится общим источником правды для нескольких продуктов, внутренних инструментов и команд. Эта общая зависимость — одна из главных причин, почему базы остаются, а код заменяют.
Обычно один набор таблиц обслуживает:
Каждый из этих потребителей может быть написан на разных языках, выпускаться по разным графикам и поддерживаться разными людьми. Когда приложение переписывают, оно быстро адаптирует свой код — но всё равно должно читать и сохранять те же записи, на которые опираются остальные.
Интеграции «привязываются» к конкретной модели данных: именам таблиц, смыслу колонок, идентификаторам ссылок и допущениям о сущностях. Даже если интеграция идёт через API, API часто зеркалирует модель базы.
Поэтому изменение базы — не решение одной команды. Схемное изменение может распространиться на экспорты, ETL‑джобы, отчёты и downstream‑системы, которые вовсе не в основном репозитории продукта.
Если вы выпустите багнутое приложение — можно откатить. Если вы нарушите контракт базы, вы можете одновременно прервать биллинг, дашборды и отчётность. Риск растёт с числом зависимых систем.
Вот почему «временные» решения (имя колонки, значение enum, своеобразное значение NULL) становятся липкими: слишком многое от них тайно зависит.
Если хотите практичных стратегий по безопасному управлению этим, см. /blog/schema-evolution-guide.
Код приложения можно заменять по частям. Можно поменять UI, заменить сервис или перестроить фичу за API, оставив ту же базу под капотом. Если что‑то идёт не так, можно откатить deploy, направить трафик назад или запускать старый и новый код параллельно.
Данные не дают такой гибкости. Они разделены, взаимосвязаны и ожидаются корректными в любую секунду — не «в основном корректными после следующего деплоя».
При рефакторинге кода вы меняете инструкции. При миграции данных вы меняете то, на что опирается бизнес: записи клиентов, транзакции, аудиты, историю продуктов.
Новый сервис можно протестировать на части пользователей. Новая миграция затрагивает всех: текущих пользователей, старых пользователей, исторические строки, сиротские записи и странные единичные записи, созданные багом три года назад.
Миграция данных — это не просто «экспорт и импорт». Обычно это включает:
Каждый шаг требует верификации, а на неё уходит время — особенно при больших объёмах и высоких ставках ошибки.
Деплой кода частый и обратимый. Переключение данных ближе к хирургии.
Если нужен простой даунтайм — вы координируете операции, поддержку и ожидания клиентов. Если хотите ноль‑даунтайма — скорее всего, будете делать dual‑writes, change data capture или аккуратно staged репликацию — плюс план на случай, если новая система медленнее или неверна.
Откат с кодом прост; с данными — часто означает восстановление бэкапов, проигрывание изменений или признание того, что какие‑то записи оказались в «неправильном» месте и их надо сверять.
Базы накапливают историю: странные записи, унаследованные статусы, частично мигрированные строки и костыли, о которых никто не помнит. Такие случаи редко появляются в dev‑наборе данных, но всплывают во время реальной миграции.
Вот почему организации чаще переписывают код (иногда многократно), оставляя базу как есть. База — не просто зависимость, это самое сложное, что безопасно менять.
Изменение кода обычно про новую логику. Если что пойдёт не так, можно откатить деплой, прикрыть фичу флагом или быстро заплатить.
Изменение схемы другое: оно перестраивает правила для уже существующих данных, а эти данные могут быть годами старыми, непоследовательными или задействованными в нескольких отчётах и сервисах.
Хорошие схемы редко замораживаются. Задача — эволюционировать их так, чтобы исторические данные оставались валидными и полезными. В отличие от кода, данные нельзя «перекомпилировать» в чистое состояние — нужно переносить каждую старую строку, включая забытые крайние случаи.
Поэтому эволюция схемы склонна к изменениям, сохраняющим прежние смыслы и избегающим принудительной переработки уже хранимого.
Аддитивные изменения (новые таблицы, колонки, индексы) обычно позволяют старому коду продолжать работать, пока новый код использует новые структуры.
Разрушающие изменения — переименование колонки, смена типа, разбиение поля на несколько, ужесточение ограничений — требуют координированных обновлений в:
Даже если вы обновите главное приложение, забытый отчёт или интеграция всё ещё могут зависеть от старой формы.
«Просто поменять схему» звучит легко, пока не надо мигрировать миллионы строк онлайн. Нужно думать о:
NOT NULL колонок;ALTER операцияхЧасто миграция выполняется по шагам: добавить новые поля, писать в оба, выполнить backfill, переключить чтения, а затем удалить старые поля.
Изменения кода обратимы и изолированы; изменения схемы — долговечны и общие. Как только миграция применена, это становится частью истории базы — и все будущие версии продукта должны жить с этим решением.
Фреймворки приложений быстро устаревают: то, что было «модным» пять лет назад, может перестать поддерживаться или стать сложноукомплектуемым. Базы тоже развиваются, но многие базовые идеи и повседневные навыки меняются гораздо медленнее.
SQL и реляционные концепции были и остаются стабильными десятилетиями: таблицы, JOIN‑ы, ограничения, индексы, транзакции и планы запросов. Вендоры добавляют фичи, но модель мышления остаётся знакомой. Это позволяет переписать приложение на новом языке, сохранив ту же модель данных и подход к запросам.
Даже новые продукты часто предлагают «SQL‑подобные» уровни запросов, реляционные JOIN‑ы или семантику транзакций — потому что это удобно для отчётности, отладки и бизнес‑вопросов.
Поскольку базовые вещи остаются, экосистема выживает через поколения:
Эта непрерывность уменьшает необходимость «форсированных» переписок. Команда может уйти от фреймворка из‑за найма или патчей безопасности, но вряд ли откажется от SQL как общего языка для данных.
Стандарты и соглашения по базам создают общий базис: диалекты SQL ближе друг к другу, чем большинство веб‑фреймворков. Это облегчает сохранять базу стабильной, пока слой приложения эволюционирует.
Практический эффект прост: при планировании переписывания приложения команды часто могут сохранить существующие навыки работы с базой, паттерны запросов и практики эксплуатации — база остаётся стабильным фундаментом, переживающим поколения кода.
Большинство команд не остаются с одной базой потому, что им она нравится. Они остаются, потому что вокруг неё сформировались рабочие практики — и эти практики даются нелегко.
Как только база в продакшене, она входит в «always‑on» машину компании. Это то, на что вызывают в 2 утра, то, что спрашивают аудиторы, и то, с чем в итоге общаются все новые сервисы.
Через год‑два команды обычно обретают ритм:
Заменить базу — значит заново учиться во время реальной нагрузки с реальными ожиданиями клиентов.
Базы редко «настроили и забыли». Со временем в команде копится база знаний:
Эти знания живут в дашбордах, скриптах и в головах людей — не в одном документе. Переписывание кода может сохранить поведение, а замена базы вынудит одновременно восстанавливать поведение, производительность и надёжность.
Роли, права, журналы аудита, ротация секретов, шифрование и «кто что может читать» часто соответствуют требованиям комплаенса и внутренней политике.
Заменить базу — значит перестраивать модель доступа, повторно валидировать контролы и доказывать бизнесу, что чувствительные данные по‑прежнему защищены.
Операционная зрелость снижает риск. Даже если новая база обещает фичи, старая имеет мощное преимущество: историю того, как она остаётся доступной, восстанавливаемой и понятной в аварийных ситуациях.
Код приложения можно заменить новым фреймворком или архитектурой. Обязательства по комплаенсу привязаны к записям: что произошло, когда, кто утвердил и что видел клиент в конкретный момент. Поэтому база часто становится непреодолимым объектом при переписывании.
Во многих отраслях есть минимальные сроки хранения для счетов, согласий, финансовых событий, взаимодействий поддержки и логов доступа. Аудиторам обычно не подойдёт ответ «мы переписали приложение» как причина потери истории.
Даже если таблица не используется в повседневности, её могут потребовать по запросу вместе с объяснением, как она была создана.
Чарджбэки, возвраты, споры по доставке и контрактные вопросы зависят от исторических снимков: цена в момент покупки, адрес, условия или статус в конкретную минуту.
Когда база — авторитетный источник фактов, её замена становится не просто техническим проектом, а риском изменения доказательной базы. Поэтому команды чаще строят новые сервисы вокруг старой базы, а не «мигрируют и надеются, что совпадёт».
Некоторые записи нельзя удалить; другие нельзя преобразовать так, что потеряется трассируемость. Если вы денормализуете, объедините поля или удалите колонки, вы можете потерять возможность восстановить аудиторскую цепочку.
Это особенно заметно, когда требования приватности пересекаются с правилами хранения: может понадобиться селективная редакция или псевдонимизация при сохранении истории транзакций. Эти ограничения обычно живут рядом с данными.
Классификация данных (PII, финансовые, медицинские, внутренние) и политики управления остаются относительно стабильными при эволюции продуктов. Контроль доступа, определения отчётов и «single source of truth» часто реализуются на уровне базы, потому что её используют BI, финансы, регуляторы и расследования инцидентов.
Если планируете переписывание, рассматривайте отчётность и комплаенс как первоочередные: задокументируйте требуемые отчёты, графики хранения и поля аудитов до изменения схем. Простой чек‑лист помогает (см. /blog/database-migration-checklist).
Большинство «временных» решений принимают не по‑пренебрежению, а под давлением: сроки запуска, срочный запрос клиента, новая регуляция, грязный импорт. Удивительно то, как редко эти решения отменяют.
Код можно быстро рефакторить, но базе нужно одновременно обслуживать старых и новых потребителей. Унаследованные таблицы и колонки живут, потому что что‑то всё ещё от них зависит:
Даже переименовав поле, часто оставляют старое. Популярный паттерн — добавить новую колонку (например, customer_phone_e164), оставив phone навсегда, потому что ночной экспорт всё ещё его использует.
Костыли встраиваются в таблицы, дашборды и CSV‑экспорты — места, которые редко рассматривают как production‑код. Кто‑то строит отчёт выручки, который джойнит устаревшую таблицу «до тех пор, пока Финансы не мигрируют». Потом квартальный процесс Финансов на этом завязан — и удалить таблицу становится бизнес‑риском.
Вот почему устаревшие таблицы живут годами: база обслуживает привычки организации.
Поле, добавленное как быстрый фикс — promo_code_notes, legacy_status, manual_override_reason — часто превращается в точку принятия решений. Когда люди начинают ссылаться на него («Мы одобрили заказ потому что…»), оно перестаёт быть необязательным.
Если командам не нравится миграция, они сохраняют «тени»: дублированные имена клиентов, кешированные итоги или флаги‑запас. Эти дополнительные колонки кажутся безвредными, но создают конкурирующие источники правды и новые зависимости.
Чтобы избежать этой ловушки, относитесь к изменениям схем как к изменениям продукта: документируйте намерение, назначайте дату депрецирования и отслеживайте потребителей перед удалением.
Для практического чек‑листа см. /blog/schema-evolution-checklist.
База, переживающая поколения приложений, должна рассматриваться не как внутренняя деталь, а как общая инфраструктура. Цель — не предсказать каждую фичу, а сделать изменения безопасными, поэтапными и обратимыми.
Код приложения можно переписать, но договоры по данным сложно пересмотреть. Думайте о таблицах, колонках и ключах как об API, на которое будут опираться другие системы и будущие команды.
Предпочитайте аддитивные изменения:
Будущие переписывания часто терпят не из‑за отсутствия данных, а из‑за её двусмысленности.
Используйте понятные, согласованные имена, которые объясняют намерение (например, billing_address_id вместо addr2). Поддерживайте это ограничениями: PK, FK, NOT NULL, уникальности и CHECK.
Добавляйте лёгкую документацию рядом со схемой — комментарии к таблицам/столбцам или короткий живой документ. «Почему» важно не меньше, чем «что».
Каждое изменение должно иметь путь вперёд и путь назад.
Один из практических подходов — встраивать «режим планирования» и дисциплину откатов в процесс доставки. Например, команды, которые строят внутренние инструменты или новые версии приложения на Koder.ai, могут итеративно разрабатывать через чат, при этом относясь к схеме как к стабильному контракту — используя снимки и практики отката, чтобы снизить радиус поражения случайных изменений.
Если проектировать базу с устойчивыми контрактами и безопасной эволюцией, переписывания приложения станут обычным делом, а не рискованной операцией по спасению данных.
Менять базу — редкость, но это не миф. Команды, которые успешно это делают, готовятся заранее: делают данные портируемыми, видимыми зависимости и ослабляют жёсткую привязку приложения к конкретному движку.
Считайте экспорты первоклассной возможностью, а не одноразовым скриптом.
Тесная связанность превращает миграцию в переписывание.
Стремитесь к балансу:
Если быстро строите новый сервис (например, React‑админка и Go‑бэкенд с PostgreSQL), выбирайте стек, где портируемость и ясность эксплуатации — дефолт. Koder.ai опирается на эти широко принятые примитивы и поддерживает экспорт исходников — полезно, когда хотите сохранить заменяемость слоя приложения, не запирая модель данных в уникальном инструменте.
Базы питают не только главное приложение: отчёты, таблицы, расписанные ETL‑задачи, сторонние интеграции и пайплайны аудита.
Ведите живой реестр: кто читает/пишет, с какой частотой и что произойдёт при сбое. Даже простая страница в /docs с владельцами и контактами предотвращает неприятные сюрпризы.
Признаки: ограничения лицензирования или хостинга, нерешаемые проблемы надёжности, отсутствие необходимых функций комплаенса или пределы масштаба, которые требуют экстремальных обходных путей.
Основные риски: потеря данных, тонкие изменения смысла, даунтайм и дрейф отчётности.
Более безопасный подход — это обычно параллельный запуск: непрерывная миграция данных, валидация результатов (счёты, контрольные суммы, бизнес‑метрики), постепенное переключение трафика и держание плана отката, пока не нарастёт уверенность.
Потому что база данных хранит историческую правду бизнеса (клиенты, заказы, счета, аудиторские следы). Код можно заново развернуть или переписать; утраченная или повреждённая история восстановлению не поддаётся и может привести к финансовым, юридическим и репутационным проблемам.
Изменения данных — это совместные и долговременные изменения.
Одна и та же база часто становится единственным источником правды для:
Даже если вы перепишете приложение, все эти потребители по‑прежнему полагаются на стабильные таблицы, идентификаторы и значения.
Часто нет: большинство «миграций» выполняются поэтапно, чтобы контракт базы данных оставался стабильным, пока меняются компоненты приложения.
Типичный путь:
Команды обычно стремятся к аддитивным изменениям:
Неясность живёт дольше, чем код.
Практические шаги:
billing_address_id).NOT NULL, уникальности, CHECK).Ожидайте «странные» строки.
Перед миграцией планируйте:
Тестируйте миграции на данных, похожих на продакшен, и включайте шаги верификации, а не только логику преобразования.
Требования соответствия привязаны к записям, а не к интерфейсу.
Вам может потребоваться хранить и воспроизводить:
Изменения схемы, удаление или преобразование полей могут нарушить трассируемость, определения отчётов или возможность проведения аудита — даже если приложение уже заменено.
Потому что совместимость порождает скрытые зависимости:
Относитесь к депрецированию как к фиче: документируйте намерение, отслеживайте потребителей и назначайте дату удаления.
Практический чек‑лист:
Так переписывания приложений станут рутинными, а не кризисными «спасениями данных».