Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Версии схемы данных и обратная совместимость

Версии схемы данных: совместимость при изменении формата

Версии схемы данных: номер версии в записи, совместимые и ломающие изменения, чтение старых форматов, реестр схем и стратегии эволюции.

Прямой ответ

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

Версия схемы указывается в самой записи:

{
  "schema_version": 2,
  "order_id": 1001,
  "customer": {"id": 55, "email": "user@example.com"},
  "total": {"amount": 1500, "currency": "RUB"}
}

Без номера версии читатель определяет формат по набору полей, что ненадёжно: отсутствие поля может означать старую версию либо незаполненное значение.

Классификация изменений:

Изменение Совместимость Требование
Добавление необязательного поля Полная Значение по умолчанию у читателя
Удаление поля Обратимая для чтения Читатель не требует поле
Переименование поля Ломающая Новая версия схемы
Изменение типа поля Ломающая Новая версия схемы
Изменение смысла поля Ломающая Новая версия схемы
Добавление обязательного поля Ломающая для старых записей Значение по умолчанию либо новая версия

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

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

Три стратегии чтения данных нескольких версий.

1. Преобразование при чтении. Читатель приводит запись к актуальной версии:

def to_v2(record: dict) -> dict:
    if record["schema_version"] == 1:
        record["total"] = {"amount": record["total"], "currency": "RUB"}
        record["schema_version"] = 2
    return record

2. Хранение в исходной версии. Записи не переписываются, а читатель поддерживает все версии. Подходит при большом объёме; о хранении истории — в статье про event sourcing.

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

Реестр схем хранит определения версий и проверяет совместимость при публикации. Изменение, нарушающее совместимость, отклоняется до развёртывания; о валидации — в статье про JSON Schema.

Практика: эволюция схемы данных

1. Указывайте версию в каждой записи. Номер версии делает определение формата явным вместо вывода по набору полей.

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

3. Добавляйте поля как необязательные. Обязательное поле делает старые записи некорректными по новой схеме.

4. Не переименовывайте поля. Переименование эквивалентно удалению и добавлению, поэтому требует новой версии.

5. Игнорируйте неизвестные поля при чтении. Строгое отклонение неизвестного поля ломает прямую совместимость.

6. Проверяйте совместимость автоматически. Реестр схем либо тест на чтение данных предыдущей версии; о версиях API — в статье про версионирование API.

7. Храните определения старых версий. Читатель не может поддерживать версию, определение которой утрачено.

8. Фиксируйте срок поддержки старой версии. Бессрочная поддержка всех версий накапливает сложность.

Рекомендации:

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

Практический сценарий: сбой чтения исторических записей после изменения формата

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

  1. Диагностика. Новый код ожидал объект в поле суммы, а исторические записи содержали число. Номер версии в записях отсутствовал, поэтому определить формат по явному признаку было нельзя.
  2. Причина. Изменение типа поля выполнено без новой версии схемы и без преобразования при чтении. Отсутствие номера версии делало невозможным различение форматов.
  3. Действия. (а) Добавили поле версии схемы в формат записи; (б) реализовали преобразование при чтении: запись без версии трактуется как первая и приводится к актуальной; (в) добавили тесты на чтение данных каждой версии; (г) проверку совместимости схемы включили в конвейер: изменение типа поля отклоняется без новой версии; (д) исторические записи оставили в исходном формате, поскольку преобразование при чтении покрывало их без миграции.
  4. Результат. Чтение исторических записей восстановлено, а последующие изменения формата выполняются с увеличением версии и проверкой совместимости.

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

FAQ

Обязателен ли номер версии в записи? Формально нет, но без него формат определяется по набору полей, что ненадёжно при добавлении и удалении полей.

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

Нужно ли переписывать существующие записи при новой версии? Нет, если читатель поддерживает преобразование. Переписывание нужно при прекращении поддержки старой версии.

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

Источники: JSON Schema specification, Protocol Buffers docs (Proto3), Apache Avro specification — ссылки в шапке.