Learning Platform
Глоссарий Troubleshooting
Урок 11.02 · 35 мин
Продвинутый
Compatibility LevelsBACKWARDFORWARDFULLTRANSITIVESchema RegistryAvro CompatibilityProtobuf CompatibilityJSON Schema Compatibility

Уровни совместимости

В предыдущем уроке мы увидели, какие операции каждый формат поддерживает. Но “поддерживает” — это не “безопасно”. Добавление поля в Avro безопасно, только если новое поле имеет default. Удаление поля безопасно, только если consumers его не используют. Compatibility levels формализуют эти правила.

В модуле 04 мы определили три базовых уровня — BACKWARD, FORWARD, FULL. Там же была ссылка: “Подробнее о TRANSITIVE вариантах — в модуле 10”. Вот мы и здесь. Разберём все 7 уровней плюс NONE, с конкретными правилами для каждого формата.

Модель: кто обновляется первым?

Уровень совместимости — это ответ на вопрос: в каком порядке можно обновлять producer и consumer? В распределённой системе с N producers и M consumers одновременный деплой невозможен. Кто-то обновляется первым — и в этот момент в системе сосуществуют старая и новая версии схемы.

Три модели обновления: кто первый?

BACKWARD

BACKWARD: consumer обновляется первым. Новый reader читает старые данные. Producer обновляется позже, когда все consumers готовы.
Consumer firstПорядок деплоя: 1) деплой новых consumers (они читают старые И новые данные), 2) деплой новых producers. В переходный период — новый reader + старый writer.

FORWARD

FORWARD: producer обновляется первым. Новый writer пишет данные, которые старый reader может прочитать. Consumer обновляется позже.
Producer firstПорядок деплоя: 1) деплой новых producers (старые consumers всё ещё читают), 2) деплой новых consumers. В переходный период — старый reader + новый writer.

FULL

FULL: порядок не важен. И producer, и consumer могут обновляться в любом порядке — совместимость в обе стороны.
Any orderFULL = BACKWARD + FORWARD одновременно. Любой порядок деплоя безопасен. Самый строгий уровень — допускает наименьший набор изменений.

7+1 уровней: полная картина

Schema Registry поддерживает 7 настраиваемых уровней плюс специальный NONE:

Все уровни совместимости Schema Registry
УровеньНазвание уровня совместимости в Schema Registry
ПроверкаПротив каких версий проверяется новая схема
ГарантияЧто гарантирует этот уровень
Use caseТипичный сценарий использования
BACKWARDDefault в Confluent Schema Registry. Новая схема проверяется только против последней зарегистрированной. Самый распространённый уровень.
Последняя версияНовая схема (v3) проверяется только против непосредственно предыдущей (v2). Совместимость v3→v1 НЕ проверяется.
Новый reader → старые данныеConsumer с новой схемой может прочитать данные, записанные с предыдущей схемой.
Простые пайплайныConsumer обновляется первым, producer — вторым. Подходит для простых топологий с одним producer.
BACKWARD_TRANSITIVEСтроже BACKWARD: новая схема проверяется против ВСЕХ предыдущих версий, не только последней.
ВСЕ предыдущиеНовая v5 проверяется против v4, v3, v2, v1. Гарантирует, что новый consumer прочитает данные ЛЮБОЙ предыдущей версии.
Новый reader → все старыеConsumer с v5 прочитает данные v1, v2, v3, v4. Критично для retention: Kafka с 30-дневным retention содержит данные всех версий за месяц.
Kafka с retention > 1 версииЕсли данные в топике живут дольше одного цикла деплоя, BACKWARD недостаточно — нужен TRANSITIVE.
FORWARDПротивоположность BACKWARD: гарантирует, что старый reader прочитает новые данные.
Последняя версияКак BACKWARD, но в обратном направлении: новая схема проверяется как writer против предыдущей reader.
Старый reader → новые данныеConsumer со старой схемой может прочитать данные, записанные с новой. Новые поля пропускаются.
Producer обновляется первымКогда невозможно обновить consumers первыми (например, мобильные клиенты с задержкой апдейтов).
FORWARD_TRANSITIVEFORWARD + проверка против всех предыдущих версий.
ВСЕ предыдущиеГарантирует, что любой старый reader (любой версии) прочитает данные новой версии.
Все старые readers → новыеДаже consumer на v1 прочитает данные v5. Жёсткое ограничение — почти ничего нельзя удалять.
Никогда не ломать consumersКогда consumers не контролируются — external API, third-party integrations.
FULLBACKWARD + FORWARD одновременно: и новый reader → старые данные, и старый reader → новые данные.
Последняя версияПроверка в обе стороны, но только против последней версии.
Двусторонняя совместимостьЛюбой порядок деплоя безопасен. Самое строгое ограничение на допустимые изменения.
Shared schemasКогда одну схему используют и producers, и consumers, и порядок деплоя не контролируется.
FULL_TRANSITIVEСамый строгий уровень: BACKWARD_TRANSITIVE + FORWARD_TRANSITIVE. Новая схема совместима со ВСЕМИ предыдущими в обе стороны.
ВСЕ предыдущиеv5 совместима с v1, v2, v3, v4 в обе стороны. Максимальная безопасность, минимальная гибкость.
Полная история совместимаЛюбая версия может читать данные любой другой версии. Практически: только add optional fields с defaults.
Критичные системыФинансовые системы, healthcare, regulated environments — где несовместимость = инцидент.
NONEНет проверки совместимости. Любая схема принимается. Опасно в production, полезно для development.
НичегоSchema Registry не проверяет совместимость. Любое изменение проходит.
НичегоНикаких гарантий. Новая схема может быть полностью несовместима со старой.
Dev / migrationDevelopment environment, одноразовые миграции, prototyping. Никогда для production.

TRANSITIVE vs non-TRANSITIVE: ключевая разница

Разница между BACKWARD и BACKWARD_TRANSITIVE — не косметическая. Она определяет, насколько далеко в историю гарантируется совместимость.

Без TRANSITIVE — проверка только против непосредственно предыдущей версии. Проблема: если v1 → v2 совместимы, и v2 → v3 совместимы, это не гарантирует, что v1 → v3 совместимы. Совместимость не транзитивна по умолчанию!

С TRANSITIVE — проверка против всех предыдущих версий. Гарантирует, что цепочка совместимости не разорвётся.

TRANSITIVE: зачем проверять всю историю
Сценарий: BACKWARD без TRANSITIVEПример, где BACKWARD (non-transitive) создаёт скрытую несовместимость между v1 и v3
BACKWARD_TRANSITIVE предотвращает это
BACKWARD_TRANSITIVE: v3 проверяется против v2 И v1TRANSITIVE проверка обнаружит, что v3 не может прочитать v1 данные (age без default в v3). Регистрация v3 будет отклонена.
WARNING

BACKWARD (non-transitive) — default в Confluent Schema Registry. Для production Kafka с retention больше одного цикла деплоя этого недостаточно. Если ваш retention = 7 дней, а деплой раз в неделю — в топике данные двух последних версий. BACKWARD работает. Если retention = 30 дней и деплой ежедневно — нужен BACKWARD_TRANSITIVE. Правило: если количество активных версий схемы в retention > 2, используйте TRANSITIVE.

Допустимые изменения по уровням

Каждый уровень ограничивает набор допустимых изменений. Чем строже уровень — тем меньше можно менять:

Допустимые изменения по уровням совместимости
ОперацияТип изменения схемы
BACKWARDНовый reader → старые данные
FORWARDСтарый reader → новые данные
FULLBACKWARD + FORWARD
Add field + defaultДобавить новое поле со значением по умолчанию
ДаНовый reader: если поля нет в старых данных, подставляет default. Старый writer: не знает о поле — OK.
ДаСтарый reader: не знает о новом поле — пропускает. Новый writer: пишет поле — старый reader игнорирует.
ДаОбе стороны — работает: новый reader подставит default, старый reader пропустит.
Add field БЕЗ defaultДобавить поле без значения по умолчанию
НетНовый reader ожидает поле — в старых данных его нет. Без default → ошибка десериализации.
ДаСтарый reader не знает о поле — просто пропустит. Новый writer пишет его — OK.
НетНе проходит BACKWARD часть: новый reader не сможет прочитать старые данные без default.
Remove field + defaultУдалить поле, у которого есть default в предыдущей версии
ДаНовый reader: не ожидает поле — если оно есть в данных, пропустит. Работает.
НетСтарый reader ожидает поле — в новых данных его нет. Нужен default в старой схеме для fallback.
НетНе проходит FORWARD часть: старый reader может зависеть от поля.
Remove field БЕЗ defaultУдалить поле без значения по умолчанию в предыдущей версии
ДаНовый reader не ожидает поле — пропустит его в старых данных.
НетСтарый reader ожидает поле, его нет в новых данных, default нет → ошибка.
НетFORWARD нарушен — старый reader сломается.
Type wideningРасширение типа: int → long, float → double
ДаНовый reader с long прочитает int из старых данных (type promotion).
НетСтарый reader с int не сможет прочитать long из новых данных (narrowing = потеря).
НетFORWARD нарушен: старый reader не справится с более широким типом.
Rename (Avro alias)Переименование поля через alias
ДаНовый reader с alias найдёт поле по старому имени в данных.
ДаСтарый reader найдёт поле по оригинальному имени — alias не мешает.
ДаОбе стороны работают: alias покрывает и forward, и backward.

Закономерность: BACKWARD разрешает удаление, но запрещает добавление без default. FORWARD — наоборот. FULL — только операции, безопасные в обе стороны.

Правила по форматам: Avro vs Protobuf vs JSON Schema

Schema Registry поддерживает три формата, и правила совместимости для каждого различаются:

Avro

Avro compatibility определяется ResolvingDecoder — его мы детально разобрали в модуле 04. Ключевые правила:

  • Add field: обязателен default для BACKWARD (reader подставит его для старых данных)
  • Remove field: безопасно для BACKWARD (reader пропустит неизвестное поле). Для FORWARD — нужен default в удаляемом поле (old reader подставит default)
  • Type promotion: int→long, long→float, float→double, string→bytes — одностороннее, только widening
  • Union evolution: добавление типа в union — FORWARD compatible (старый reader может получить новый тип, который не знает). Удаление — BACKWARD compatible
  • Enum evolution: добавление символа — FORWARD compatible. Удаление — BACKWARD compatible

Protobuf

Protobuf compatibility базируется на wire format (varint, fixed, length-delimited) — не на .proto определениях:

  • Add field: всегда безопасно — новый field number, старый ридер пропускает unknown fields
  • Remove field: безопасно если field number помечен reserved. Без reserved — будущий разработчик может переиспользовать номер с другим типом
  • Type change: совместимы пары с одинаковым wire type. int32/int64/sint32/sint64/bool — все varint. fixed32/sfixed32/float — все 32-bit. fixed64/sfixed64/double — все 64-bit. string/bytes — оба length-delimited
  • Enum: добавление значения — forward compatible. Удаление — backward. allow_alias = true разрешает несколько имён для одного числового значения (аналог Avro aliases)
  • oneof: добавление поля в oneof — совместимо. Перемещение поля из обычного в oneof — breaking change

JSON Schema

JSON Schema compatibility — самая сложная из трёх, потому что JSON Schema описывает constraints, а не структуру:

  • additionalProperties: по умолчанию true — новые поля разрешены. Если false — добавление поля = breaking для FORWARD (старый reader с additionalProperties: false отклонит новые поля)
  • required: добавление в required array — breaking для BACKWARD (старые данные не содержат поле). Удаление из required — breaking для FORWARD
  • Type restriction: сужение "type": ["string", "number"] до "type": "string" — breaking для FORWARD. Расширение — breaking для BACKWARD
  • Pattern/format: добавление pattern — breaking для FORWARD (данные, ранее валидные, могут не пройти). Удаление — breaking для BACKWARD
TIP

JSON Schema compatibility — единственная из трёх, где additionalProperties кардинально меняет семантику. С additionalProperties: true (default) JSON Schema максимально гибка — добавление полей всегда безопасно. С false — каждое добавление = potential breaking change. Recommendation: не используйте additionalProperties: false в schemas, которые будут эволюционировать.

Выбор уровня: decision tree

Как выбрать правильный уровень для конкретного pipeline:

Decision tree: выбор уровня совместимости

Контролируете обе стороны?

Начальный вопрос: контролируете ли вы и producers, и consumers? Если нет — нужен более строгий уровень.
Да: одна командаОдна команда владеет и producer, и consumer. Можно координировать порядок деплоя.

Retention > 1 deploy cycle?

Следующий вопрос: данные в retention дольше одного цикла деплоя?
НетДанные живут недолго — только последняя и предыдущая версия в retention.
ДаДанные живут долго — много версий в retention одновременно.
Нет: consumers не контролируютсяExternal consumers, мобильные клиенты, third-party integrations. Нельзя заставить обновиться.

Можно ломать old consumers?

Вы можете сломать старых consumers?
НетOld consumers не должны ломаться — нужна forward compatibility.
Да, с грациейDeprecation period: старые consumers уведомляются заранее, потом — breaking change.

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

Kafka-топик внутри одного сервиса — BACKWARD. Одна команда, координированный деплой, retention обычно короткий.

Kafka-топик с множеством consumers — BACKWARD_TRANSITIVE. Consumers обновляются не одновременно, в retention могут быть данные нескольких версий.

Public API / event contract — FULL_TRANSITIVE. Consumers вне вашего контроля, breaking change = инцидент.

Development / prototyping — NONE. Схема меняется часто и кардинально. Переключить на BACKWARD_TRANSITIVE перед production.

CDC pipeline (Debezium) — BACKWARD_TRANSITIVE для value schema (структура данных эволюционирует). FULL для key schema (ключ не должен ломаться ни в каком направлении).

NOTE

Schema Registry позволяет устанавливать уровень per-subject, не только глобально. Используйте это: ключевые shared schemas на FULL_TRANSITIVE, internal service schemas на BACKWARD_TRANSITIVE, development topics на NONE. Глобальный уровень — fallback default.

Subject naming и уровни

Выбор subject naming strategy напрямую влияет на scope compatibility checks:

TopicNameStrategy (default) — subject = {topic}-value или {topic}-key. Все producers в один топик обязаны использовать совместимые схемы. Один топик = один контракт.

RecordNameStrategy — subject = fully qualified record name (com.example.User). Одна логическая сущность = один контракт, независимо от топика. Один сервис может писать User в разные топики — совместимость проверяется по сущности.

TopicRecordNameStrategy — subject = {topic}-{record}. Один рекорд в одном топике = один контракт. Самый гранулярный: User в orders и User в analytics — два независимых контракта.

Naming strategy определяет scope: при TopicNameStrategy два разных record types в одном топике должны быть совместимы между собой — что часто нежелательно. RecordNameStrategy снимает это ограничение.

Конфигурация в Schema Registry

Уровень устанавливается через REST API:

# Глобальный уровень (default для всех subjects)
PUT /config
{"compatibility": "BACKWARD_TRANSITIVE"}

# Per-subject уровень (переопределяет глобальный)
PUT /config/{subject}
{"compatibility": "FULL_TRANSITIVE"}

# Проверка текущего уровня
GET /config/{subject}
{"compatibilityLevel": "FULL_TRANSITIVE"}

# Проверка совместимости вручную (dry run)
POST /compatibility/subjects/{subject}/versions/latest
Body: {"schema": "...", "schemaType": "AVRO"}
Response: {"is_compatible": true}

Dry-run проверка — ключевая операция для CI/CD: проверить совместимость до регистрации, в build pipeline. Подробнее о CI/CD интеграции — в уроке 04.

Что дальше

Уровни совместимости — это правила. Enforcement этих правил обеспечивает Schema Registry — централизованный сервис, хранящий все версии схем и проверяющий совместимость при каждой регистрации. В следующем уроке мы разберём архитектуру Confluent Schema Registry: _schemas topic, single-primary leader election, monotonic IDs, REST API, и deployment patterns.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. BACKWARD (non-transitive): v1→v2 совместимы, v2→v3 совместимы. Kafka retention = 30 дней, деплой ежедневно. В топике данные v1, v2, v3. Consumer v3 читает данные v1 — что произойдёт?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 6