Learning Platform
Глоссарий Troubleshooting
Урок 11.03 · 35 мин
Продвинутый
Schema RegistryConfluentArchitecture_schemas TopicSchema IDREST APINormalizationMulti-TenancyRBACDeployment

Архитектура 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: общая архитектура
Producers / ConsumersKafka-клиенты (producers и consumers) обращаются к Schema Registry через REST API для регистрации и получения схем. Обычно через KafkaAvroSerializer/Deserializer.
REST API

Schema Registry (Primary)

Schema Registry — stateful HTTP-сервис. Все схемы кэшированы в памяти для быстрых reads. Writes проходят через leader node.
Produce / Consume
_schemas topicCompacted Kafka topic — единственный persistent storage. Все версии схем, subjects, конфиги хранятся как key-value записи. Schema Registry — это KTable materialization.
Secondary 1Secondary (follower) нода: read-only. Подписана на _schemas topic, обновляет in-memory cache. Обслуживает GET запросы. Write запросы проксирует на primary.
Secondary 2Ещё одна secondary нода. Горизонтальное масштабирование reads — добавить больше secondaries.
Secondary NКоличество secondaries ограничено только нагрузкой на _schemas topic (consumer group). На практике 3-5 нод достаточно для большинства deployment.

Все ноды 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 одновременно:

_schemas topic: структура записей
KeyKafka message key — определяет тип записи. Schema Registry использует structured keys для разных типов данных.
ValueKafka message value — JSON с данными. null value = tombstone (удаление при compaction).
НазначениеЧто хранит эта запись
{"subject":"User","version":1,"magic":1}Schema version entry: привязка конкретной версии схемы к subject. Magic=1 — текущий формат.
{"subject":"User","version":1,"id":42,"schema":"...","deleted":false}Полная запись: subject, version, global schema ID, JSON schema string, флаг soft delete. Это основная рабочая лошадка — хранит все версии всех schemas.
Schema versionОдна запись = одна версия одной схемы в одном subject. Compaction сохраняет последнюю запись для каждого key.
{"keytype":"SCHEMA","id":42}Schema ID entry: обратный lookup — по global ID найти schema. Дублирует данные для быстрого GET /schemas/ids/{id}.
{"schema":"...","schemaType":"AVRO"}JSON со schema string и типом (AVRO, PROTOBUF, JSON). Нужен для быстрого lookup по ID — без сканирования всех subject versions.
ID → schema lookupConfluent Wire Format содержит только schema ID (4 байта). Consumer должен быстро получить schema по ID — эта запись обеспечивает O(1) lookup.
{"keytype":"CONFIG","subject":"User"}Config entry: compatibility level для конкретного subject. Если subject не указан — глобальный config.
{"compatibilityLevel":"BACKWARD_TRANSITIVE"}JSON с compatibility level. Один subject может иметь свой уровень, переопределяющий глобальный.
Compatibility configPer-subject конфигурация. Глобальный уровень хранится с пустым subject.
{"keytype":"DELETE_SUBJECT","subject":"User"}Delete subject marker: soft delete всех версий subject.
{"subject":"User","version":3}Версия, до которой включительно всё помечено deleted. Soft delete — данные остаются в топике для recovery.
Subject deletionМягкое удаление subject. Hard delete (permanent=true) создаёт tombstone записи с null value → Kafka compaction удалит физически.

Почему 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.

NOTE

_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.

Механизм присвоения

  1. Producer отправляет POST /subjects/{subject}/versions с телом схемы
  2. Primary нормализует schema (см. следующий раздел)
  3. Primary ищет нормализованную schema в in-memory cache
  4. Если schema уже существует (в любом subject) → возвращает существующий ID
  5. Если новая → инкрементирует глобальный счётчик → присваивает 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 нормализация.

WARNING

Normalization не идеальна. Две семантически эквивалентные JSON Schemas с разными $ref структурами могут нормализоваться по-разному и получить разные IDs. Avro PCF — наиболее предсказуемая нормализация из трёх форматов. При сомнениях — используйте POST /subjects/{subject} (lookup) для проверки, существует ли schema с данным ID, прежде чем регистрировать.

REST API: ключевые операции

Schema Registry REST API: основные endpoints
EndpointHTTP метод и путь
ОписаниеЧто делает endpoint
ОтветЧто возвращает
POST /subjects/{sub}/versionsГлавная write-операция: зарегистрировать новую версию схемы. Проверяет совместимость, назначает ID, записывает в _schemas.
Регистрация новой версииBody: JSON с 'schema' (string), 'schemaType' (AVRO|PROTOBUF|JSON). Проверяет compatibility level subject. Возвращает ошибку 409 если несовместима.
{"id": 42}Globally unique schema ID. Один и тот же для одинаковых schemas в разных subjects.
GET /schemas/ids/{id}Самый частый read: consumer получает schema по ID из Confluent Wire Format. Горячий path — обслуживается из in-memory cache.
Schema по глобальному IDНе зависит от subject — ID глобален. Consumer получает schema, кэширует локально в KafkaAvroDeserializer.
{"schema": "..."}JSON string schema. Consumer парсит и кэширует для повторного использования.
GET /subjects/{sub}/versionsСписок всех версий subject: [1, 2, 3, ...]. Для UI и tooling — не для hot path.
Все версии subjectВозвращает массив номеров версий. Не содержит schemas — для каждой нужен отдельный GET.
[1, 2, 3]Версии нумеруются от 1, монотонно. Deleted versions не показываются (если soft deleted).
POST /compatibility/...Dry-run проверка совместимости без регистрации. Ключевой для CI/CD — проверить до деплоя.
Проверка без регистрацииPOST /compatibility/subjects/{sub}/versions/latest или /versions/{version}. Body: schema для проверки. Не записывает ничего.
{"is_compatible": true}true = новая schema совместима с target version(s). false = несовместима, + messages с описанием нарушений.
PUT /config/{sub}Установить compatibility level для subject. Переопределяет глобальный level.
Per-subject configBody: {'compatibilityLevel': 'FULL_TRANSITIVE'}. Глобальный: PUT /config (без subject).
{"compatibility": "..."}Новый уровень сохраняется в _schemas topic как CONFIG entry.
DELETE /subjects/{sub}Soft delete: помечает все versions как deleted. Permanent=true — hard delete (tombstones). Permanent delete нужен для GDPR.
Soft или hard deleteSoft delete: versions не видны через API, но остаются в _schemas. Hard delete: tombstone в Kafka → compaction удалит. Для recover soft deleted: повторно зарегистрировать.
[1, 2, 3]Список удалённых версий. Hard delete необратим.

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.

TIP

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 на _schemas topic контролируют, кто может писать schemas

Без RBAC (open-source) — защита грубее: ACLs на topic level, не на subject level. Для subject-level control в open-source используют прокси (nginx/envoy) с маршрутизацией по URL path.

NOTE

Минимальная 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 агрессивно:

Schema Registry caching: producer и consumer стороны

Producer: serialize()

Producer-side: KafkaAvroSerializer при первом вызове serialize() отправляет schema в Registry, получает ID, кэширует mapping schema→ID. Последующие вызовы — из cache.
Первый вызов
1. POST /subjects/.../versionsРегистрация schema (или lookup, если уже существует). Schema Registry возвращает ID. Один HTTP-вызов per unique schema per JVM.
Cache hit
2. Local cache: schema → IDHashMap в памяти JVM. Key = normalized schema string, value = global ID. Invalidation: never (ID immutable). Все последующие serialize() — O(1) из cache.

Consumer: deserialize()

Consumer-side: KafkaAvroDeserializer читает 4-байтовый schema ID из Confluent Wire Format, ищет в local cache, при промахе — HTTP GET к Registry.
Первый вызов per ID
1. GET /schemas/ids/{id}Fetch schema по global ID. Registry отвечает из in-memory cache — latency ~1ms. Один HTTP-вызов per unique ID per JVM.
Cache hit
2. Local cache: ID → schemaHashMap в памяти JVM. Key = int ID, value = parsed Schema object. Invalidation: never (schema immutable). Все последующие deserialize() — O(1) из cache.
Импликация: Registry не на critical pathПосле warm-up все schema lookups — из локального cache. Registry downtime не влияет на работающих producers/consumers. Но: рестарт JVM или новый consumer/producer = cache miss → нужен 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 — общее количество зарегистрированных schemas
  • kafka.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:

  • _schemas topic 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.subject limit

Что дальше

Мы разобрали как 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

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

Результат: 0 из 0
Аналитический
Вопрос 1 из 4. Schema Registry хранит данные в _schemas Kafka topic (compacted, single partition). Почему single partition — не баг, а архитектурное решение?

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

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

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

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