Настройка schema evolution для NoSQL баз данных

Структура данных меняется быстрее, чем успевают обновиться сервисы, и прод первым получает сбой. Мы настраиваем управляемое изменение структуры записей под ключ: правила совместимости, безопасные откаты, регламент для команды. Изменения проходят без простоя, а доля отказов при выпуске сокращается кратно.
Часто задаваемые вопросы

Schema evolution в NoSQL: где ломается прод

В документных базах схема живёт в самих записях, а не в отдельной таблице: миграция не блокирует запись — и это главная причина, почему schema evolution в NoSQL откладывают до первого сбоя. Код обновили, правила валидации коллекции включили, а данные остались в двух-трёх формах одновременно. В MongoDB достаточно добавить обязательное поле в правило валидации — и часть существующих записей перестанет проходить проверку при обновлении.

Первое место отказа — падение чтения документа. Новый код ожидает атрибут, которого в старой записи нет: объект не проходит проверку, обработчик отдаёт ошибку. Пока такие записи редки, сбой выглядит случайным — отказывает один запрос из сотни, и воспроизвести его на свежих данных не получается.

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

Третье — пакетная обработка на смешанных версиях записей. Задание читает выборку, спотыкается на первой же записи старого формата и либо падает целиком, либо дописывает половину набора в новом виде — набор остаётся в промежуточном состоянии, и разбирать его приходится вручную.

Разбираем, как это закрывается: версионирование записей, чтение с учётом обеих форм и обратное заполнение без остановки сервиса.

Что даёт управляемое изменение структуры записей

Совместимость проверяется до применения

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

Откат изменения — минуты вместо часов

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

Отказы на чтении переходят в управляемый класс

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

Данные переписываются лениво, по обращению

Массовая миграция не нужна: преобразование применяется к документу в момент чтения, а в новой структуре он сохраняется при следующем обновлении. Объём переписываемых данных сокращается до доли реально используемых записей, и длительных блокировок на коллекции не возникает.

Ручные правки уходят в регламент, выпуск ускоряется

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

Подходы к schema evolution: сравнение вариантов

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

Вариант Когда подходит Чем ограничен
Проверка на чтении Разнородные источники, где остановка записи недопустима: документ пишется как есть, разбирается на стороне читателя Ошибка проявляется позже, а логика совместимости расползается по коду всех потребителей
Жёсткая проверка на записи Один владелец данных, высокая цена неверного документа — отказ виден автору сразу Расширение схемы требует одновременного обновления всех пишущих сервисов, старые версии начинают отклоняться
Явная версия записи Долгоживущие данные и выгрузки: нужно читать документы, записанные месяцами ранее Нужны правила преобразования между версиями и дисциплина их актуализации
Гибридная валидация Ядро полей проверяется жёстко на записи, необязательные расширения — при чтении Требует явной границы между обязательной и расширяемой частью документа

На практике выбор определяет не удобство разработки, а то, где мы готовы платить за ошибку. Гибридный режим закрывает большинство сценариев: обязательные поля не дают записать заведомо битый документ, а свободная часть позволяет добавлять атрибуты без релиза всех сервисов сразу. Если данные читаются дольше, чем живёт код, версия записи становится обязательной — иначе восстановление старой выгрузки превращается в археологию.

Этапы внедрения: от аудита до передачи

Структуру данных в NoSQL нельзя поменять одним релизом: документы прежних форм живут в тех же коллекциях месяцами, и сервис всё это время обязан читать обе формы. Поэтому переход ведём последовательными шагами, где каждое состояние совместимо с предыдущим.

  1. Инвентаризация версий записей. Считаем, какая доля документов соответствует какой форме: где тип поля разошёлся, где остались поля от откатившихся релизов, где отличается вложенность. Получаем карту фактического состояния данных, а не предположений по коду.
  2. Схема совместимости и план изменения структуры. Делим правки на обратно совместимые и ломающие: необязательное поле сервис переживает сразу, переименование или смена типа — только двухфазно, через период чтения обоих вариантов.
  3. Настройка правил проверки. Валидатор включаем в мягком режиме: он пишет несоответствия в журнал и не блокирует запись. Через несколько суток видно, что именно нарушает новые правила, и правки в код идут по факту.
  4. Обкатка на копии данных. Снимок рабочей базы разворачиваем в изолированном контуре и прогоняем преобразование вместе с обратным откатом: время прохода, доля записей, не преобразованных автоматически, поведение сервиса на смешанных данных.
  5. Постепенное включение правил. Переводим проверки из мягкого режима в строгий по коллекциям, начиная с низконагруженных. Контроль — доля записей, проходящих проверку, и частота ошибок чтения на каждом шаге.
  6. Передача регламента. Отдаём документ: как добавить поле, что считается ломающим изменением, как оформляется двухфазный переход, кто запускает откат, какие проверки стоят в сборке. Дальше команда меняет схему сама.

Комплект поставки: правила, регламенты, документация

Комплект поставки — это не презентация и не папка со скриншотами, а рабочие файлы в репозитории и вики команды, по которым следующий инженер продолжит работу без устных пояснений. Каждый документ отвечает на конкретный вопрос: что сейчас в структуре данных, что можно менять безопасно и как действовать, если изменение пошло не так.

  • Описание версий структуры: какие поля появляются, какие перестают заполняться, что остаётся читаемым для записей прошлых версий и до какого момента их нужно поддерживать.
  • Правила совместимости версий: что допустимо добавлять без остановки сервисов, а какие изменения требуют параллельной записи двух версий и промежуточного слоя чтения.
  • Схема обработки записей: путь документа от записи до чтения — как читатель разбирает разные версии в одной коллекции и куда добавляется новая ветка разбора.
  • Политики доступа к данным: кто и через какую роль меняет структуру, где нужен второй подтверждающий, как ограничен доступ к чувствительным полям и выгрузкам.
  • Регламент выпуска изменений: порядок выкладки, допустимые окна, набор автоматических проверок на копии данных и критерий, при котором выпуск останавливают.
  • Регламент отката: заранее описанные шаги возврата к прошлой версии структуры и отдельно — что делать с записями, которые уже успели записаться в новом формате.
  • Документация по структуре: актуальная карта коллекций, назначение полей, связи между сущностями и дата последнего пересмотра, чтобы не приходилось восстанавливать её по коду.
  • Набор проверочных сценариев на копии данных: типовые и пограничные случаи, на которых изменение тестируют до продакшена.

Всё это передаётся вместе с настройкой, а не остаётся у нас: команда заказчика может сама вносить правки в структуру, не нарушая совместимость.

Кейс: изменение структуры без простоя сервиса

Что было

Сервис заказов хранил документы в NoSQL-хранилище MongoDB без поля версии схемы: приложение читало их напрямую и ожидало ровно тот набор полей, который когда-то записало. Как только в структуру добавили вложенный блок адреса доставки, часть обработчиков начала падать на разборе — доля отказов чтения документов выросла до 4–6% запросов. Перевод версий записей делали разовым скриптом с остановкой записи новых: изменение структуры занимало 2–3 недели, и на каждой итерации вручную правились десятки документов.

Что изменили

В каждый документ добавили поле версии, а на чтение — слой трансформации: запись хранится в исходном виде, приложение приводит её к текущему контракту и не зависит от того, когда документ создан. Зафиксировали правила совместимости: новые поля только необязательные, переименование — через дублирующее поле на период перехода, удаление — лишь после того, как записей старой версии не осталось.

Доля записей старой версии за первый месяц упала со 100% до менее 1% — фоновая задача переписывает документы по мере обращения к ним, без пауз в обслуживании. Отказы чтения на новых полях исчезли, потому что контракт чтения проверяется тестами на обеих версиях схемы. Время выпуска изменения структуры сократилось с двух-трёх недель до двух дней, объём ручных правок — до нуля, а откат приложения на предыдущую версию больше не требует миграции данных.

Что делать, если хранилище уже в проде с разными версиями

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

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

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

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

Валидация, откаты и совместимость: частые вопросы

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

Откат изменения структуры — не бинарная операция. Пока приложение пишет в двух форматах, надёжнее вернуть предыдущую версию правил: она принимает и старые, и новые записи, а частично обновлённые данные не блокируют чтение. Граница ответственности обычно такая: хранилище проверяет структуру и типы, приложение — бизнес-правила и согласованность полей.

Что будет со старыми документами в базе?

Ничего не переписываем в один проход: миграция идёт порциями, а чтение подставляет значение по умолчанию, пока документ не обновлён. Отставание миграции при этом не влияет на работу сервисов.

Можно ли откатить изменение структуры, если данные уже частично записаны в новом формате?

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

Где заканчивается ответственность хранилища и начинается ответственность приложения?

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

Как убедиться в совместимости версий до выкатки?

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

Обсудим задачу и вернёмся с оценкой и планом

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

Взамен присылаем предметный документ, а не набор общих практик:

  • план работ по данным: инвентаризация вариантов схемы по коллекциям, доля документов каждого вида и места, где расхождение уже ломает чтение;
  • состав этапов: поле версии в документе, правила чтения для нескольких версий, выкат по схеме «читатели раньше писателей», фоновое дообогащение старых записей;
  • точки контроля: доля документов старой версии, счётчик отказов разбора, алерт на неизвестную версию, тесты на чтение каждого поддерживаемого вида;
  • политика обратной совместимости: смена типа или имени поля — только через период двойной записи, удаление — после того как счётчик старой версии ушёл в ноль;
  • оценка трудоёмкости по этапам: что закрываем своими силами, а где нужны решения с вашей стороны;
  • план отката: какие шаги обратимы и на каком этапе точка возврата.

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

Наши компетенции:

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1393
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1368
  • Разработка логотипа компании B2B Advance
    Разработка логотипа компании B2B Advance
    815
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1139