Архитектура Confluent Schema Registry
В модуле 04 мы использовали Schema Registry как чёрный ящик: producer регистрирует схему → получает ID → вставляет ID в Confluent Wire Format → consumer по ID получает схему → десериализует. В предыдущем уроке — настроили уровни совместимости.
Теперь вскроем этот чёрный ящик. Как Schema Registry хранит данные? Почему single-primary? Как работают globally unique IDs? Что такое normalization и зачем она нужна? Как масштабировать на сотни тысяч схем?
Общая архитектура
Schema Registry — это stateful HTTP-сервис, который использует Kafka как storage backend. Не базу данных — Kafka.
Schema Registry (Primary)
Schema Registry — stateful HTTP-сервис. Все схемы кэшированы в памяти для быстрых reads. Writes проходят через leader node.Все ноды consume _schemas → in-memory cache. Write только через primary.
Почему single-primary?
Ключевая операция Schema Registry — register schema: проверить совместимость, назначить globally unique ID, записать в _schemas. Эта операция должна быть строго сериализованной: два одновременных регистрации не должны получить одинаковый ID, и не должны пропустить проверку совместимости, если одна из регистраций меняет latest version.
Single-primary гарантирует сериализацию без distributed consensus на каждый write. Primary — единственная нода, которая пишет в _schemas. Secondaries — read-only replicas, обслуживающие GET запросы.
Leader election: Schema Registry использует Kafka group protocol (ранее ZooKeeper) для выбора primary. Если primary падает — один из secondaries становится новым primary. Failover занимает секунды (Kafka group rebalance).
_schemas topic: storage backend
_schemas — обычный Kafka topic с cleanup.policy=compact. Schema Registry использует его как event log и KTable одновременно:
Почему Kafka, а не PostgreSQL?
На первый взгляд, реляционная БД — очевидный выбор. Но Schema Registry работает в Kafka-экосистеме, и Kafka как storage даёт уникальные преимущества:
Zero additional dependencies — Schema Registry зависит только от Kafka, который уже есть. Нет PostgreSQL, ZooKeeper (для storage), Redis. Меньше компонентов = проще ops.
Built-in replication — _schemas реплицируется как любой Kafka topic. replication.factor=3 даёт durability. Нет separate replication setup.
Event sourcing native — вся история изменений хранится в log. Recovery = replay _schemas от начала. Нет need для backup/restore процедур.
In-memory serving — Schema Registry материализует _schemas topic в in-memory HashMap. Все reads — O(1) из памяти. Kafka только для durability и replication, не для query serving.
_schemas topic создаётся автоматически при первом старте Schema Registry. Рекомендуемые настройки: replication.factor=3, num.partitions=1 (single partition для ordered writes), cleanup.policy=compact, min.insync.replicas=2. Single partition — ключевое ограничение: все writes сериализованы через одну partition = один partition leader.
Globally unique monotonic IDs
Каждая уникальная схема получает globally unique monotonic ID. Это не per-subject ID — это глобальный ID across all subjects.
Механизм присвоения
- Producer отправляет
POST /subjects/{subject}/versionsс телом схемы - Primary нормализует schema (см. следующий раздел)
- Primary ищет нормализованную schema в in-memory cache
- Если schema уже существует (в любом subject) → возвращает существующий ID
- Если новая → инкрементирует глобальный счётчик → присваивает ID → записывает в
_schemas
Ключевой момент: одна и та же schema (побайтово после normalization) имеет один ID, даже если зарегистрирована в 10 разных subjects. Это оптимизация: в Confluent Wire Format хранится 4-байтовый ID. Consumer кэширует ID → schema mapping — один lookup для любого subject.
ID sequencing
ID — простой автоинкремент: 1, 2, 3, … Не UUID, не hash. Primary node хранит последний выданный ID в памяти. При failover — новый primary вычитывает _schemas от начала, находит максимальный ID, продолжает с max+1.
Это означает: ID не обязательно monotonic по времени в пределах одного subject. Subject User может иметь version 1 → ID 42, version 2 → ID 107. Между ними зарегистрированы схемы других subjects, занявшие IDs 43–106.
Schema normalization
Две JSON-строки с одинаковой семантикой могут отличаться whitespace, порядком ключей, форматированием. Без normalization одна и та же schema получит разные IDs — waste of IDs и путаница.
Schema Registry нормализует schema перед сравнением и хранением. Правила нормализации зависят от формата:
Avro normalization — Parsing Canonical Form (PCF): убрать whitespace, удалить doc, aliases, default, отсортировать поля по имени, развернуть named types в полную форму. Две схемы с разным форматированием, но одинаковой семантикой → одна PCF → один ID.
Protobuf normalization — canonical representation: отсортировать imports, нормализовать package/option, убрать комментарии. Менее строгая чем Avro PCF — Protobuf schema identity зависит от больше факторов.
JSON Schema normalization — canonical JSON: убрать whitespace, отсортировать ключи. JSON Schema сложнее — $ref resolving, allOf/anyOf нормализация.
Normalization не идеальна. Две семантически эквивалентные JSON Schemas с разными $ref структурами могут нормализоваться по-разному и получить разные IDs. Avro PCF — наиболее предсказуемая нормализация из трёх форматов. При сомнениях — используйте POST /subjects/{subject} (lookup) для проверки, существует ли schema с данным ID, прежде чем регистрировать.
REST API: ключевые операции
Content type и versioning
Schema Registry API использует custom content type: application/vnd.schemaregistry.v1+json. Рекомендуется указывать при каждом запросе:
Content-Type: application/vnd.schemaregistry.v1+json
Accept: application/vnd.schemaregistry.v1+json
Без этого header Schema Registry примет application/json, но version negotiation не сработает — при будущих API changes это может сломаться.
Contexts и multi-tenancy
В крупных организациях с сотнями команд и тысячами schemas нужна изоляция: команда A не должна случайно сломать schema команды B. Schema Registry поддерживает contexts (Confluent Platform 7.0+) — логическую изоляцию schemas.
Context — это namespace-prefix для subject names. Subject User в context team-payments становится :.team-payments:User. Compatibility checks, ID allocation, и naming — всё в пределах context.
Структура contexts
Default context (.) — все schemas без explicit context. Backward compatible: существующие deployment без contexts продолжают работать.
Named contexts (:.{name}:) — изолированные namespaces. Каждый context имеет свои subjects, configs, и IDs.
Cross-context references — schema в одном context может ссылаться на schema в другом: {"name": "PaymentEvent", "references": [{"name": "User", "subject": ":.team-users:User", "version": 1}]}. Полезно для shared types.
Contexts — не замена RBAC. Contexts обеспечивают логическую изоляцию (namespace). RBAC обеспечивает access control (кто может register/read/delete). В production используйте оба: contexts для организационной структуры, RBAC для security.
Schema references: cross-schema dependencies
Schemas часто ссылаются друг на друга: Protobuf import, Avro named types в разных файлах, JSON Schema $ref. Schema Registry поддерживает references — declared dependencies between schemas:
POST /subjects/PaymentEvent/versions
{
"schemaType": "PROTOBUF",
"schema": "syntax = \"proto3\"; import \"User.proto\"; ...",
"references": [
{
"name": "User.proto",
"subject": "User",
"version": 1
}
]
}
Reference — это указание: “этот import (User.proto) разрешается через subject User, version 1”. Schema Registry хранит dependency graph и предотвращает удаление referenced schemas.
Deployment patterns
Single-primary с Kafka leader election
Production-standard deployment. Schema Registry ноды регистрируются через Kafka group protocol. Одна нода = primary (writes), остальные = secondary (reads).
Масштабирование reads: добавить secondaries за load balancer. Все GET запросы равномерно распределяются. Writes всегда проксируются на primary.
Multi-datacenter: leader-follower
Для cross-datacenter deployment Schema Registry поддерживает leader-follower topology:
Leader datacenter — primary Schema Registry, принимает writes. _schemas topic реплицируется в follower datacenter через MirrorMaker 2 или Cluster Linking.
Follower datacenter — secondary Schema Registry, read-only. Reads — из локального _schemas mirror. Writes — проксируются в leader datacenter через HTTP forward.
Sizing guidelines
Schema Registry легковесен — in-memory storage означает, что даже 100,000 schemas занимают сотни мегабайт RAM. Bottleneck — не CPU или memory, а _schemas topic startup time: при cold start Schema Registry вычитывает весь topic (все schemas с начала). Для 100K schemas это десятки секунд.
Рекомендации: 3 ноды minimum (1 primary + 2 secondary), 4GB heap для 50K+ schemas, SSD не нужен (in-memory serving), network — low-latency к Kafka brokers.
Security: ACLs и RBAC
Confluent RBAC
Confluent Platform (commercial) предоставляет fine-grained RBAC с предопределёнными ролями:
SecurityAdmin — управление ACLs и role bindings. ResourceOwner — full access к конкретному resource (subject, context). DeveloperRead — read-only доступ (GET schemas). DeveloperWrite — register schemas (POST) + read.
Роли привязываются к subjects или contexts: confluent iam rbac role-binding create --principal User:alice --role DeveloperWrite --resource Subject:User --schema-registry-cluster-id <id>.
Open-source authentication
Open-source Schema Registry поддерживает:
- HTTP Basic Auth — username/password в запросе
- TLS mutual authentication — клиентский сертификат
- Kafka-based authorization — ACLs на
_schemastopic контролируют, кто может писать schemas
Без RBAC (open-source) — защита грубее: ACLs на topic level, не на subject level. Для subject-level control в open-source используют прокси (nginx/envoy) с маршрутизацией по URL path.
Минимальная security конфигурация для production: TLS для encryption in transit, authentication (Basic или mTLS), ACLs на _schemas topic (только Schema Registry ноды могут писать). RBAC — если нужен per-subject/per-team control.
Caching: клиентская сторона
KafkaAvroSerializer/Deserializer кэшируют schema lookups агрессивно:
Producer: serialize()
Producer-side: KafkaAvroSerializer при первом вызове serialize() отправляет schema в Registry, получает ID, кэширует mapping schema→ID. Последующие вызовы — из cache.Consumer: deserialize()
Consumer-side: KafkaAvroDeserializer читает 4-байтовый schema ID из Confluent Wire Format, ищет в local cache, при промахе — HTTP GET к Registry.Cache size default: 1000 entries. Для high-cardinality environments (тысячи unique schemas) — увеличить schema.registry.cache.capacity.
Мониторинг
Key metrics для Schema Registry в production:
JMX metrics (встроенные):
kafka.schema.registry:type=SchemaRegistryResourceExtension,name=registered-count— общее количество зарегистрированных schemaskafka.schema.registry:type=SchemaRegistryResourceExtension,name=schema-created-count— новые schemas per second (alert на аномальный рост)kafka.schema.registry:type=jersey-metrics,name=request-error-rate— HTTP error rate
Operational alerts:
_schemastopic lag > 0 на secondary nodes (stale reads)- Primary node unreachable > 30s (writes blocked)
- Compatibility check failures spike (developer deploying breaking changes)
- Schema count approaching
schema.registry.max.schemas.per.subjectlimit
Что дальше
Мы разобрали как Schema Registry хранит и обслуживает schemas. Но Registry — только runtime enforcement. Для production-grade governance нужен CI/CD pipeline: проверка совместимости в build time, automated schema registration, subject naming conventions, и workflow для breaking changes. Это — тема следующего урока.
Schema management в Kafka — теория Compatibility modes — детальный разбор Schema evolution в CDC pipelines