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

Главная → Статьи → Schema registry: эволюция схем Avro и совместимость

Schema registry: как менять схемы сообщений без остановки потребителей

Schema registry: Avro и Protobuf, правила BACKWARD/FORWARD/FULL совместимости, версионирование топиков и практические сценарии эволюции схемы.

Прямой ответ

Schema registry — сервис, который хранит схемы сообщений (Avro, Protobuf, JSON Schema), присваивает им идентификаторы и проверяет совместимость новой версии с предыдущими. Он решает главную проблему событийных систем: производитель и потребитель развиваются независимо, и изменение формата не должно ломать другую сторону.

Как это работает с Kafka:

  1. Схема регистрируется в реестре для субъекта (обычно <topic>-value).
  2. Каждое сообщение сериализуется с заголовком: magic byte + 4-байтовый schema id + тело в бинарном формате.
  3. Потребитель по schema id получает схему из реестра и десериализует сообщение.
  4. Перед регистрацией новой версии реестр проверяет её совместимость по настроенному правилу.

Правила совместимости:

  • BACKWARD (по умолчанию в Confluent). Новая схема читает данные, записанные предыдущей. Требование: новые поля — только со значением по умолчанию; удалять можно только поля со значением по умолчанию. Позволяет сначала обновить потребителей.
  • FORWARD. Старая схема читает данные новой. Позволяет сначала обновить производителей. Ограничения зеркальны BACKWARD.
  • FULL. Совместимость в обе стороны — можно обновлять стороны в любом порядке. Самый безопасный и самый строгий режим.
  • FULL_TRANSITIVE. То же, но проверяется против всех предыдущих версий, а не только последней.
  • NONE. Проверка отключена. Годится только для экспериментов.

Практические следствия:

  • В Avro обязательны default для добавляемых полей; в Protobuf 3 значения по умолчанию заданы семантикой языка, но переиспользование номеров полей недопустимо.
  • Переименование поля в Avro — ломающее изменение (лечится алиасами aliases), а в Protobuf — безопасно, если номер поля сохранён.
  • Смена типа почти всегда ломающая; исключения ограничены (например, int → long в Avro).

Практика: пример схемы и безопасной эволюции

Исходная схема Avro:

{
  "type": "record",
  "name": "OrderEvent",
  "namespace": "shop.events",
  "fields": [
    {"name": "order_id", "type": "string"},
    {"name": "user_id",  "type": "long"},
    {"name": "total",    "type": "double"},
    {"name": "status",   "type": "string"}
  ]
}

Совместимое изменение (BACKWARD): добавляем поле со значением по умолчанию.

{"name": "currency", "type": "string", "default": "RUB"}

Старые потребители игнорируют новое поле; новые читают и старые сообщения, подставляя RUB.

Ломающее изменение: удаление status без значения по умолчанию, изменение типа total на string, переименование user_id в customer_id без алиаса. Такое изменение требует новой версии топика или отдельного субъекта схемы.

Проверка перед выкладкой:

# проверка совместимости против последней версии
curl -X POST  \
  -H "Content-Type: application/vnd.schemaregistry.v1+json" \
  -d @order-event-v2.json

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

  1. Ставьте FULL или FULL_TRANSITIVE, если порядок деплоя не гарантирован. Это дороже на этапе проектирования, но убирает целый класс инцидентов.
  2. Проверяйте совместимость в CI. Отдельный шаг, который падает на ломающем изменении, эффективнее разбора аварии. Правила версионирования — в статье про версии API и совместимость.
  3. Не переиспользуйте номера полей и имена. Удалённое поле помечайте reserved (Protobuf) или фиксируйте в документации, чтобы его не заняли повторно.
  4. Различайте «схему сообщения» и «семантику». Реестр проверит структуру, но не заметит, что поле total теперь включает НДС. Семантические изменения требуют отдельного оповещения потребителей.

Практический сценарий: обновление сломало половину потребителей

Контекст: производитель добавил поле discount без значения по умолчанию; режим совместимости — BACKWARD.

  1. Симптом. Регистрация новой схемы отклонена реестром с ошибкой совместимости — это и есть сработавшая защита. Часть команды решила «обойти», временно переключив субъект в режим NONE.
  2. Последствия. После выкладки старые потребители начали падать на десериализации; поток встал, лаг consumer group вырос.
  3. Действия. Откатываем производителя на предыдущую версию схемы; возвращаем режим BACKWARD; в реестре проверяем историю версий (/subjects/orders-value/versions) и убеждаемся, что несовместимая версия не успела распространиться в данные.
  4. Корректное решение. Добавляем {"name": "discount", "type": "double", "default": 0.0}; обновляем потребителей, затем производителя; проверяем на стенде чтение старых сообщений новой схемой.
  5. Профилактика. Запрещаем режим NONE на уровне конфигурации реестра; добавляем проверку совместимости в CI и тест на чтение «вчерашних» сообщений из CDC-потока.

Результат: изменение прошло без простоя. Вывод: схема-реестр защищает только тогда, когда правила совместимости строгие и их нельзя обойти «на месте».

FAQ

Можно ли работать без schema registry? Да: схемы можно хранить в репозитории кода и вкладывать в каждое сообщение. Цена — больший объём трафика и отсутствие централизованной проверки совместимости. Для нескольких топиков это приемлемо, для десятков — нет.

Что выбрать: Avro, Protobuf или JSON Schema? Avro компактен и удобен для потоков с частой эволюцией схемы. Protobuf силён в кросс-языковых контрактах и gRPC. JSON Schema подходит, когда сообщения должны читаться «глазами» и объём не критичен. Важнее выбрать один формат на слой данных и держать правила совместимости.

Как хранить схемы вместе с данными в lakehouse? Табличные форматы (Iceberg, Delta) несут схему в метаданных таблицы и поддерживают её эволюцию. Для потоковой части схемы живут в реестре; согласованность между ними проверяют при загрузке — несовпадение схемы должно блокировать пайплайн.

Что делать с большим числом версий схемы? Храните историю, но следите за тем, чтобы потребители читали актуальные версии: метрика «доля сообщений со старой схемой» показывает, кто застрял. Устаревшие версии удаляют только после подтверждения, что в топиках не осталось данных с их идентификаторами; основы устройства топиков — в статье про Kafka.

Источники: Confluent Schema Registry docs, Apache Avro spec, Protobuf guides — ссылки в шапке.