English translation is not available yet. Showing Russian content.

Что такое «message schema evolution» (Avro/Protobuf)?

Краткий тезис

evolution|Message schema evolution — это механизм, позволяющий изменять структуру сообщений (схему) в системе обмена данными без остановки сервисов и без потери обратной совместимости. Он критически важен для распределённых систем, потоковой обработки (Kafka) и RAG-пайплайнов, где данные проходят через несколько этапов. Две основные технологии — Apache Avro (схемы в JSON, разрешение на чтение/запись) и Protocol Buffers (Protobuf) (бинарные .proto файлы, строгие правила эволюции). В основе лежат правила: добавлять поля можно только с optional, удалять — только после reserved, а required поля менять нельзя.


1. Термин «Schema evolution» (эволюция схемы данных)

Schema evolution — это процесс изменения формата данных (схемы) в работающей системе, при котором старые и новые версии данных остаются совместимыми. Без этого механизма любое добавление поля, удаление или изменение типа приводит к падению консьюмеров и продюсеров.

Почему это важно

  • Микросервисы независимо развёртываются — схемы могут расходиться.
  • Потоковые системы (Kafka) хранят сообщения в логе — нельзя «переписать» историю.
  • RAG-системы часто используют message brokers для передачи чанков, метаданных и запросов; поломка сериализации приводит к потере данных.

2. Apache Avro: схемы как JSON, resolution

Apache Avro — формат сериализации, где схема хранится в JSON и может быть внедрена рядом с данными (embedded) или загружена из Schema Registry.

Ключевая особенность — resolution (разрешение):

  • Writer’s schema (схема писателя) — та, с которой данные были записаны.
  • Reader’s schema (схема читателя) — та, которую ожидает консьюмер.
  • Процесс resolution автоматически преобразует данные из writer’s schema в reader’s schema:
    • Поля, отсутствующие в writer’s, заполняются значениями по умолчанию (из reader’s schema).
    • Поля, отсутствующие в reader’s, игнорируются.
    • Поля с одинаковыми именами, разными типами — ошибка (если нет специальной логики).

Пример Avro-схемы

{
  "type": "record",
  "name": "User",
  "fields": [
    {"name": "id", "type": "int"},
    {"name": "name", "type": "string"},
    {"name": "email", "type": ["null", "string"], "default": null}
  ]
}

Здесь emailunion type (null или string) с default: null, что позволяет в будущем добавить или удалить это поле без сбоя.


3. Protocol Buffers (Protobuf): .proto файлы, строгий контроль

Protocol Buffers (Google) — двоичный формат, где схема описывается в .proto файлах. В отличие от Avro, схема не прикрепляется к каждому сообщению; она заранее известна обеим сторонам (обычно через общий репозиторий или Schema Registry).

Эволюция в Protobuf регулируется строгими правилами компилятора protoc:

  • Каждое поле имеет номер (field number), который не должен меняться.
  • Для опциональных новых полей можно использовать optional с явным default.
  • Поля, которые могут быть удалены в будущем, нужно помечать как reserved, чтобы их номера и имена нельзя было использовать повторно.
  • Есть механизм oneof для взаимоисключающих полей.

Пример .proto

syntax = "proto3";
message User {
  int32 id = 1;
  string name = 2;
  optional string email = 3;   // новое поле, может отсутствовать
  reserved 4, 5;               // поля, которые когда-то были и могут появиться снова? (защита)
  reserved "old_field";        // защита имени
}

4. Правила совместимости (Compatibility Rules)

Совместимость бывает нескольких типов. Основные:

Тип совместимостиОписаниеAvroProtobuf
Backward compatibilityНовый читатель может читать старые данные✅ (через default)✅ (optional, default)
Forward compatibilityСтарый читатель может читать новые данные✅ (игнорирование неизвестных полей)✅ (игнорирование неизвестных полей, если они не required)
Full compatibilityОдновременно backward + forward✅ (проще с proto3, где все поля optional)

Основные правила эволюции (обобщённые):

  • ✅ Можно добавлять поля с default в Avro или optional в Protobuf.
  • ❌ Нельзя удалять required поля (ни в одном формате – приведёт к ошибкам).
  • ❌ Нельзя менять тип поля (например, int32string) без смены номера поля (Protobuf) или переименования (Avro).
  • ✅ Можно переименовывать поля в Avro с помощью атрибута aliases; в Protobuf переименование допустимо, но лучше не делать (ломает forward совместимость, если читатель использует старое имя).
  • ✅ Можно добавлять новые поля с номерами, не пересекающимися с зарезервированными (Protobuf).
  • ❌ Нельзя удалять поля без резервации (в Protobuf) или без перехода в union (в Avro).

5. Schema Registry (Confluent Schema Registry)

Schema Registry — центральное хранилище версий схем, которое:

  • Проверяет совместимость при регистрации новой схемы.
  • Возвращает схему по ID, избегая передачи полной схемы с каждым сообщением.
  • Поддерживает как Avro, так и ProtobufJSON Schema).

Типы проверок совместимости

  • BACKWARD – новый читатель может читать старые данные.
  • FORWARD – старый читатель может читать новые данные.
  • FULL – обе.
  • NONE – без проверки (danger!).

Пример интеграции с Kafka (Avro-сериализатор):

from confluent_kafka import avro, Producer
from confluent_kafka.avro import AvroProducer, CachedSchemaRegistryClient

schema_registry = CachedSchemaRegistryClient('http://localhost:8081')
producer = AvroProducer(
    {'bootstrap.servers': 'localhost:9092'},
    schema_registry=schema_registry,
    default_key_schema=avro.loads('{"type": "string"}'),
    default_value_schema=avro.loads('{"type": "record", "name": "User", "fields": ...}')
)
producer.produce(topic='users', value={'id': 1, 'name': 'Alice'})

6. Сравнение Avro vs Protobuf для эволюции схем

КритерийAvroProtobuf
Язык описанияJSONDSL (.proto)
Переносимость схемыЧасто передаётся с данными (embedded)Требует общего репозитория или Schema Registry
Совместимость по умолчаниюВысокая (все поля имеют default)Требуется явно объявлять optional (proto3)
Размер сообщенияСредний (JSON header)Oчень малый (compact binary)
ПроизводительностьНиже (парсинг JSON)Высокая
Поддержка в Schema RegistryОтличнаяХорошая
Сложность изученияНизкаяСредняя (нужно понять систему номеров полей)

7. Проблемы и best practices при эволюции

  • Проблема: «забытые» схемы — старые схемы остаются в Schema Registry, но данные с ними уже не пишутся. Решение: настроить политику очистки (COMPATIBILITY.DELETE с осторожностью).
  • Проблема: использование required — делает эволюцию практически невозможной. Рекомендуется избегать required в любом формате.
  • Best practice задавать явные default значения для всех новых полей.
  • Best practice использовать backward‑compatible по умолчанию в Schema Registry (или FULL), чтобы предотвратить случайные изменения.
  • Best practice тестировать эволюцию в CI-пайплайне с помощью утилит типа avro-tools или protoc --check_proto3_optional.

8. Связь с Agentic RAG и распределёнными RAG-системами

В архитектуре Agentic RAG (агентные RAG-пайплайны) часто используются:

  • Message brokers (Kafka, RabbitMQ) для передачи запросов, чанков, контекстов.
  • Событийные лямбды (например, Kafka Streams) для обработки.
  • Несколько сервисов — генерации эмбеддингов, векторного поиска, LLM‑вывода.

Без schema evolution любое расширение пайплайна (добавление поля source_confidence в чанк, изменение формата метаданных) потребовало бы остановки всех сервисов или привело бы к дампу сообщений. Использование Schema Registry с Avro/Protobuf делает систему устойчивой к изменениям и поддерживает непрерывную интеграцию (CI/CD).


Пет-проект для закрепления

Задача Реализовать микросервис, который публикует чанки документов в Kafka и потребляет их, демонстрируя эволюцию схемы Avro.

Инструменты

Шаги:

  1. Запустить Kafka и Schema Registry через docker-compose.
  2. Создать Avro-схему Chunk_v1 с полями: chunk_id, text, doc_id.
  3. Написать продюсера, который отправляет 100 сообщений с Chunk_v1.
  4. Написать консьюмера, который читает и выводит схемы.
  5. Зарегистрировать новую схему Chunk_v2, добавив поле score (optional, default=0.0) и удалив doc_id (заменив на document_id с union и default).
  6. Убедиться, что консьюмер, написанный под v2, читает данные v1 (backward‑compatibility) и наоборот (forward‑совместимость через Schema Registry).
  7. Экспериментально попробовать нарушить правило (удалить поле без default) — убедиться, что Schema Registry отклоняет регистрацию.

Ожидаемый результат

  • Полностью работающий пайплайн с эволюцией схем.
  • Понимание того, как Schema Registry проверяет совместимость.
  • Способность написать код для graceful‑миграции полей.

Связь с другими вопросами

ВопросТема
815Что такое Agentic RAG?
816Как спроектировать event-driven RAG?
818Как работает schema registry в Kafka?
819Плюсы и минусы Avro vs Protobuf vs JSON Schema
820Как поддерживать версионность данных в RAG-пайплайне?
821Что такое «data contract» и зачем он нужен?

Навигация