Что такое «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}
]
}
Здесь email — union 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)
Совместимость бывает нескольких типов. Основные:
| Тип совместимости | Описание | Avro | Protobuf |
|---|---|---|---|
| Backward compatibility | Новый читатель может читать старые данные | ✅ (через default) | ✅ (optional, default) |
| Forward compatibility | Старый читатель может читать новые данные | ✅ (игнорирование неизвестных полей) | ✅ (игнорирование неизвестных полей, если они не required) |
| Full compatibility | Одновременно backward + forward | ✅ | ✅ (проще с proto3, где все поля optional) |
Основные правила эволюции (обобщённые):
- ✅ Можно добавлять поля с default в Avro или optional в Protobuf.
- ❌ Нельзя удалять required поля (ни в одном формате – приведёт к ошибкам).
- ❌ Нельзя менять тип поля (например,
int32→string) без смены номера поля (Protobuf) или переименования (Avro). - ✅ Можно переименовывать поля в Avro с помощью атрибута
aliases; в Protobuf переименование допустимо, но лучше не делать (ломает forward совместимость, если читатель использует старое имя). - ✅ Можно добавлять новые поля с номерами, не пересекающимися с зарезервированными (Protobuf).
- ❌ Нельзя удалять поля без резервации (в Protobuf) или без перехода в union (в Avro).
5. Schema Registry (Confluent Schema Registry)
Schema Registry — центральное хранилище версий схем, которое:
- Проверяет совместимость при регистрации новой схемы.
- Возвращает схему по ID, избегая передачи полной схемы с каждым сообщением.
- Поддерживает как Avro, так и Protobuf (и JSON 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 для эволюции схем
| Критерий | Avro | Protobuf |
|---|---|---|
| Язык описания | JSON | DSL (.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.
Инструменты
- Python 3.10+
- confluent-kafka (avro, protobuf)
- Docker (Kafka + Schema Registry)
- Apache Avro SDK /
protobuf(по желанию)
Шаги:
- Запустить Kafka и Schema Registry через
docker-compose. - Создать Avro-схему
Chunk_v1с полями:chunk_id,text,doc_id. - Написать продюсера, который отправляет 100 сообщений с
Chunk_v1. - Написать консьюмера, который читает и выводит схемы.
- Зарегистрировать новую схему
Chunk_v2, добавив полеscore(optional, default=0.0) и удаливdoc_id(заменив наdocument_idс union и default). - Убедиться, что консьюмер, написанный под
v2, читает данныеv1(backward‑compatibility) и наоборот (forward‑совместимость через Schema Registry). - Экспериментально попробовать нарушить правило (удалить поле без 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» и зачем он нужен? |
Навигация
- Предыдущий: 816
- Следующий: 818
- Индекс: 00. Индекс разборов