[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
---
|
||||
id: data-eng-schema-registry
|
||||
title: Schema Registry — Avro / Protobuf / 호환성
|
||||
category: Coding
|
||||
status: draft
|
||||
source_trust_level: B
|
||||
verification_status: conceptual
|
||||
created_at: 2026-05-09
|
||||
updated_at: 2026-05-09
|
||||
tags: [data-engineering, schema, avro, protobuf, vibe-coding]
|
||||
tech_stack: { language: "Various", applicable_to: ["Data Engineering"] }
|
||||
applied_in: []
|
||||
aliases: [Confluent Schema Registry, Avro, Protobuf, JSON Schema, BACKWARD compatibility, schema evolution]
|
||||
---
|
||||
|
||||
# Schema Registry
|
||||
|
||||
> Streaming / messaging 의 schema 진화 관리. **Producer = schema register, Consumer = schema fetch**. **Confluent Schema Registry, Apicurio**. Avro / Protobuf / JSON Schema.
|
||||
|
||||
## 📖 핵심 개념
|
||||
- Schema: 메시지 format.
|
||||
- Subject: schema 의 namespace.
|
||||
- Version: 진화 단계.
|
||||
- Compatibility: 옛 / 새 호환.
|
||||
|
||||
## 💻 코드 패턴
|
||||
|
||||
### Avro schema
|
||||
```json
|
||||
{
|
||||
"type": "record",
|
||||
"name": "Order",
|
||||
"namespace": "com.acme.events",
|
||||
"fields": [
|
||||
{ "name": "id", "type": "string" },
|
||||
{ "name": "user_id", "type": "string" },
|
||||
{ "name": "amount", "type": { "type": "bytes", "logicalType": "decimal", "precision": 10, "scale": 2 } },
|
||||
{ "name": "created_at", "type": { "type": "long", "logicalType": "timestamp-millis" } }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 등록
|
||||
```bash
|
||||
curl -X POST -H "Content-Type: application/vnd.schemaregistry.v1+json" \
|
||||
--data '{"schema": "..."}' \
|
||||
http://schema-registry:8081/subjects/orders-value/versions
|
||||
```
|
||||
|
||||
### Producer (KafkaJS + Avro)
|
||||
```ts
|
||||
import { SchemaRegistry, SchemaType } from '@kafkajs/confluent-schema-registry';
|
||||
import { Kafka } from 'kafkajs';
|
||||
|
||||
const registry = new SchemaRegistry({ host: 'http://schema-registry:8081' });
|
||||
|
||||
const schema = `
|
||||
{
|
||||
"type": "record",
|
||||
"name": "Order",
|
||||
"fields": [...]
|
||||
}`;
|
||||
|
||||
const { id } = await registry.register({ type: SchemaType.AVRO, schema });
|
||||
|
||||
const kafka = new Kafka({ brokers: ['kafka:9092'] });
|
||||
const producer = kafka.producer();
|
||||
await producer.connect();
|
||||
|
||||
const message = await registry.encode(id, { id: '...', user_id: '...', amount: '99.50', created_at: Date.now() });
|
||||
await producer.send({ topic: 'orders', messages: [{ key: id, value: message }] });
|
||||
```
|
||||
|
||||
### Consumer
|
||||
```ts
|
||||
const consumer = kafka.consumer({ groupId: 'orders-processor' });
|
||||
await consumer.subscribe({ topic: 'orders' });
|
||||
|
||||
await consumer.run({
|
||||
eachMessage: async ({ message }) => {
|
||||
const decoded = await registry.decode(message.value!);
|
||||
console.log(decoded); // typed object
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Protobuf
|
||||
```proto
|
||||
syntax = "proto3";
|
||||
package com.acme.events;
|
||||
|
||||
message Order {
|
||||
string id = 1;
|
||||
string user_id = 2;
|
||||
double amount = 3;
|
||||
google.protobuf.Timestamp created_at = 4;
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
# Code generation
|
||||
buf generate
|
||||
# 또는 protoc
|
||||
```
|
||||
|
||||
```ts
|
||||
const { id } = await registry.register({ type: SchemaType.PROTOBUF, schema: protoSchema });
|
||||
```
|
||||
|
||||
### JSON Schema
|
||||
```json
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Order",
|
||||
"type": "object",
|
||||
"required": ["id", "user_id", "amount", "created_at"],
|
||||
"properties": {
|
||||
"id": { "type": "string", "format": "uuid" },
|
||||
"user_id": { "type": "string" },
|
||||
"amount": { "type": "string", "pattern": "^\\d+\\.\\d{2}$" },
|
||||
"created_at": { "type": "integer" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Compatibility 정책
|
||||
```
|
||||
BACKWARD: 새 schema 가 옛 데이터 read 가능 (consumer first 업그레이드)
|
||||
FORWARD: 옛 schema 가 새 데이터 read 가능 (producer first)
|
||||
FULL: BACKWARD + FORWARD
|
||||
NONE: 검사 X
|
||||
TRANSITIVE: 모든 옛 version 호환
|
||||
|
||||
→ 보통 BACKWARD 가 안전 default.
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -X PUT -H "Content-Type: application/json" \
|
||||
--data '{"compatibility": "BACKWARD"}' \
|
||||
http://schema-registry:8081/config/orders-value
|
||||
```
|
||||
|
||||
### Schema 변경 — Backwards-compatible
|
||||
```
|
||||
✅ 새 optional field 추가 (default value)
|
||||
✅ 새 enum value 추가 (default 케이스 있으면)
|
||||
✅ Field 이름 alias (Avro)
|
||||
✅ 더 큰 type (int → long)
|
||||
|
||||
❌ Required field 추가
|
||||
❌ Field 제거
|
||||
❌ Type 변경 (int → string)
|
||||
❌ Enum 값 제거
|
||||
```
|
||||
|
||||
### Buf (Protobuf modern tool)
|
||||
```yaml
|
||||
# buf.yaml
|
||||
version: v1
|
||||
breaking:
|
||||
use:
|
||||
- FILE
|
||||
|
||||
lint:
|
||||
use:
|
||||
- DEFAULT
|
||||
```
|
||||
|
||||
```bash
|
||||
buf lint
|
||||
buf breaking --against '.git#branch=main'
|
||||
buf generate
|
||||
|
||||
# Schema registry push
|
||||
buf push --tag v1.0.0
|
||||
```
|
||||
|
||||
→ Schema 도 monorepo + git workflow.
|
||||
|
||||
### Code generation
|
||||
```bash
|
||||
# Avro → TS
|
||||
npx avsc avro2ts schemas/order.avsc -o src/types/order.ts
|
||||
|
||||
# Protobuf → TS (ts-proto)
|
||||
protoc --plugin=protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto \
|
||||
--ts_proto_out=. order.proto
|
||||
|
||||
# Buf
|
||||
buf generate
|
||||
```
|
||||
|
||||
→ Type-safe consumer.
|
||||
|
||||
### Kafka serialization
|
||||
```
|
||||
Wire format = magic byte (1) + schema id (4) + payload
|
||||
|
||||
→ Consumer 가 schema id 로 registry fetch + decode
|
||||
```
|
||||
|
||||
### Multiple subjects per topic
|
||||
```
|
||||
키 / value 별 schema:
|
||||
- orders-key: 단순 string id
|
||||
- orders-value: 위 Order schema
|
||||
|
||||
또는 multi-event topic:
|
||||
- orders.user-orders-value
|
||||
- orders.fraud-detected-value
|
||||
```
|
||||
|
||||
### Schema 폐지
|
||||
```bash
|
||||
# Soft delete
|
||||
curl -X DELETE http://schema-registry:8081/subjects/orders-value/versions/1
|
||||
|
||||
# Hard delete (admin only)
|
||||
curl -X DELETE http://schema-registry:8081/subjects/orders-value/versions/1?permanent=true
|
||||
```
|
||||
|
||||
→ Consumer 가 옛 version 안 사용 보장 후.
|
||||
|
||||
### CI 검증
|
||||
```yaml
|
||||
- name: Schema breaking check
|
||||
run: |
|
||||
buf breaking --against 'git://example.com/repo.git#branch=main'
|
||||
|
||||
- name: Lint schemas
|
||||
run: |
|
||||
buf lint
|
||||
```
|
||||
|
||||
→ PR 가 breaking schema 차단.
|
||||
|
||||
### Apicurio (open-source 대안)
|
||||
```
|
||||
Confluent Schema Registry 라이센스 / 가격 부담 시.
|
||||
Apicurio = open Apache, Kafka / multi-protocol (Avro/Proto/JSON).
|
||||
```
|
||||
|
||||
### REST API client (별 streaming)
|
||||
```ts
|
||||
// Schema 정보로 generated DTO 사용
|
||||
import type { Order } from './generated/order';
|
||||
|
||||
app.post('/orders', async (req, res) => {
|
||||
const order: Order = req.body;
|
||||
// type-safe
|
||||
});
|
||||
```
|
||||
|
||||
### Datacontract (마이크로서비스)
|
||||
```
|
||||
"내 Kafka topic 가 이 schema 보장" — 다른 팀 / service 가 의존.
|
||||
schema = data contract.
|
||||
변경 시 communication + breaking check.
|
||||
```
|
||||
|
||||
## 🤔 의사결정 기준
|
||||
| 상황 | 추천 |
|
||||
|---|---|
|
||||
| Kafka + 큰 throughput | Avro / Protobuf |
|
||||
| Type safety 강 | Protobuf + buf |
|
||||
| Polyglot (다언어) | Avro / Protobuf |
|
||||
| 단일 언어 + 단순 | JSON Schema 또는 Zod |
|
||||
| Confluent Cloud | Schema Registry built-in |
|
||||
| Self-host | Apicurio |
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **JSON without schema**: drift, 검증 X.
|
||||
- **Schema 변경 + 등록 안 함**: consumer 깨짐.
|
||||
- **NONE compatibility**: 모든 변경 OK — 카오스.
|
||||
- **Required field 추가**: BACKWARD 깨짐.
|
||||
- **Field 제거**: BACKWARD 깨짐.
|
||||
- **Schema 다양 location**: 한 곳 (registry) 만.
|
||||
- **Code generation 안 함**: type drift.
|
||||
|
||||
## 🤖 LLM 활용 힌트
|
||||
- Avro / Protobuf + Schema Registry.
|
||||
- BACKWARD default.
|
||||
- Code generation 매 schema 변경.
|
||||
- Buf / Apicurio = modern.
|
||||
|
||||
## 🔗 관련 문서
|
||||
- [[Messaging_Kafka_Patterns]]
|
||||
- [[Backend_gRPC_Patterns]]
|
||||
- [[API_Versioning_Strategies]]
|
||||
Reference in New Issue
Block a user