Прямой ответ
Schema registry — сервис, который хранит схемы сообщений (Avro, Protobuf, JSON Schema), присваивает им идентификаторы и проверяет совместимость новой версии с предыдущими. Он решает главную проблему событийных систем: производитель и потребитель развиваются независимо, и изменение формата не должно ломать другую сторону.
Как это работает с Kafka:
- Схема регистрируется в реестре для субъекта (обычно
<topic>-value). - Каждое сообщение сериализуется с заголовком: magic byte + 4-байтовый
schema id+ тело в бинарном формате. - Потребитель по
schema idполучает схему из реестра и десериализует сообщение. - Перед регистрацией новой версии реестр проверяет её совместимость по настроенному правилу.
Правила совместимости:
- 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
Рекомендации:
- Ставьте FULL или FULL_TRANSITIVE, если порядок деплоя не гарантирован. Это дороже на этапе проектирования, но убирает целый класс инцидентов.
- Проверяйте совместимость в CI. Отдельный шаг, который падает на ломающем изменении, эффективнее разбора аварии. Правила версионирования — в статье про версии API и совместимость.
- Не переиспользуйте номера полей и имена. Удалённое поле помечайте
reserved(Protobuf) или фиксируйте в документации, чтобы его не заняли повторно. - Различайте «схему сообщения» и «семантику». Реестр проверит структуру, но не заметит, что поле
totalтеперь включает НДС. Семантические изменения требуют отдельного оповещения потребителей.
Практический сценарий: обновление сломало половину потребителей
Контекст: производитель добавил поле discount без значения по умолчанию; режим совместимости — BACKWARD.
- Симптом. Регистрация новой схемы отклонена реестром с ошибкой совместимости — это и есть сработавшая защита. Часть команды решила «обойти», временно переключив субъект в режим
NONE. - Последствия. После выкладки старые потребители начали падать на десериализации; поток встал, лаг consumer group вырос.
- Действия. Откатываем производителя на предыдущую версию схемы; возвращаем режим
BACKWARD; в реестре проверяем историю версий (/subjects/orders-value/versions) и убеждаемся, что несовместимая версия не успела распространиться в данные. - Корректное решение. Добавляем
{"name": "discount", "type": "double", "default": 0.0}; обновляем потребителей, затем производителя; проверяем на стенде чтение старых сообщений новой схемой. - Профилактика. Запрещаем режим
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 — ссылки в шапке.