Data & Platform/Event Streaming

Kafka - 09. schema registry

Schema Registry — 메시지 구조를 안전하게 바꾸는 방법

producer가 {"id":1,"name":"alice"}를 보내고 consumer가 읽는다. 그런데 producer가 필드를 추가했다 — {"id":1,"name":"alice","email":"..."}. 옛 consumer는 이걸 못 읽고 크래시난다. Kafka는 value를 바이트로만 저장하고 구조(schema)를 모른다. producer와 consumer가 각자 해석하다가 구조가 어긋나면 깨진다. 이걸 푸는 게 Schema Registry다.

여기서 한 가지 전제를 먼저 고정해야 한다 — Schema Registry는 Confluent 확장이며 Apache Kafka core가 아니다. 별도 설치가 필요하고, ASF 공식 문서가 아닌 Confluent 문서를 따른다. 이 구분을 안 하면 "Kafka에 스키마 관리가 내장됐다"고 오해하고, core 배포판에서 Schema Registry를 찾다가 헤맨다. Schema Registry가 무엇을 하고, 어떻게 설정하며, 호환성을 어떻게 강제하는지 — 그리고 왜 "core 밖"에 있는지 — 를 추적한다.

Kafka가 스키마를 모른다는 것 — 왜 문제인가

Kafka는 value를 바이트로만 저장한다. 그래서:

  • 스키마(필드명·타입)는 producer/consumer 코드에만 존재.
  • 스키마 변경(필드 추가/삭제)이 양쪽에 동시 적용되지 않으면 consumer 크래시.
  • "스키마 안 바꾸면 되지" → 현실에선 불가능(요구사항 변화). 안전하게 바꾸는 규칙이 필요.

바이트 저장이 만드는 두 가지 함정

첫째, producer와 consumer가 "이 바이트가 뭘 의미하는지" 따로 따로 안다. producer는 Java User 클래스로 직렬화하고, consumer는 같은 User 클래스로 역직렬화한다고 가정한다. 하지만 producer가 새 버전(필드 추가)으로 배포되고 consumer가 아직 옛 버전이면, 옛 consumer는 새 필드를 모르고 역직렬화 에러를 낸다. 이걸 막으려면 양쪽을 동시에 배포해야 하는데, 현실에선 consumer가 여러 팀·여러 서비스에 흩어져 있어서 불가능하다.

둘째, 바이트는 자기 설명적(self-describing)이지 않다. 같은 바이트 [0x01, 0x02, 0x03]이 int 3바이트일 수도, string "abc"일 수도 있다. 스키마 없이는 consumer가 바이트를 어떻게 해석할지 알 수 없다. 그래서 "스키마를 어딘가에 두고, 양쪽이 참조해서 일관되게 해석한다"는 메커니즘이 필요하다 — 그게 Schema Registry의 역할이다.

Kafka 자체가 이 문제를 풀지 않는 이유는 "core는 가볍게"라는 설계 철학 때문이다. 스키마 관리는 선택적(Confluent 확장)으로 두고, core는 메시지 전달에만 집중한다. 이게 "core vs 생태계 확장"을 나눈 경계의 핵심이다.

Schema Registry는 스키마를 별도 서비스로 두어 진화 규칙을 강제한다

Schema Registry는 스키마를 저장·버전 관리·호환성 검사하는 외부 서비스다(기본 포트 8081). 메시지를 보낼 때:

  1. producer가 스키마를 Registry에 등록.
  2. Registry가 호환성을 검사 → 통과하면 schema ID 발급.
  3. 메시지엔 schema ID만 붙여 보냄(0x00 + ID + payload).
  4. consumer가 ID로 Registry에서 스키마를 조회해 역직렬화.

이렇게 하면 "새 스키마가 옛 consumer를 깨뜨리는지"를 등록 시점에 검사해서, 불호환 스키마 등록 자체를 거부한다. 단순 문서화가 아니라 운영 가드레일이다.

Avro — 스키마와 데이터를 분리하는 포맷

Avro의 핵심은 스키마와 데이터 분리다. 데이터 자체엔 필드명이 안 들어감(스키마로 해석) → 크기 작음. 진화 규칙(필드 default 등)을 지원한다.

이게 왜 Kafka에 잘 맞는지 보자. JSON 메시지는 필드명을 매 레코드마다 반복하므로 크기가 크다({"id":1,"name":"alice"}에서 id, name이 매번 들어감). Avro는 payload에 값만 들어가므로 훨씬 작다 — 대신 스키마는 별도(Registry)에 있어야 한다. producer/consumer가 Registry로 스키마를 공유하므로, "payload는 작게 + 스키마는 한 번만"이라는 효율적 조합이 된다. 이게 Confluent 생태계에서 Avro가 기본 포맷으로 쓰이는 이유다.

Schema Registry의 내부 — 스키마는 어디에 저장되나

Schema Registry는 스키마를 자체 디스크가 아니라 Kafka의 내부 topic(_schemas, compact 정책)에 저장한다. 이게 "Schema Registry는 Kafka 위에 올라간 서비스"라는 말의 의미다 — Registry 자체는 stateless에 가깝고, 상태(스키마)는 Kafka에 있다.

flowchart LR
    P[producer/consumer] -->|"REST API (8081)"| SR[Schema Registry]
    SR -->|"스키마 저장"| K[(Kafka<br/>_schemas topic<br/>compact)]
    SR -->|"스키마 조회"| K

이 구조가 만드는 이점:

  • HA: Registry 인스턴스가 죽어도, 다른 인스턴스가 Kafka에서 스키마를 읽어 동작. Registry 자체 HA를 Kafka가 담보.
  • 내결함성: _schemas topic이 복제돼 있으면 스키마도 안전.

단점은 Registry가 Kafka 클러스터에 의존한다는 점 — Kafka 클러스터 자체가 죽으면 Registry도 죽는다(스키마를 읽을 수 없으므로). 따라서 Schema Registry HA는 결국 Kafka HA에 의존한다.

Schema Registry 설정 — 어떻게 기동하나

Schema Registry는 Confluent 확장이라 Kafka core 배포판에 없다. Docker로 기동하는 게 가장 쉽다:

Docker로 Schema Registry 기동

# Kafka(KRaft)가 이미 localhost:9092에서 기동 중이라 가정

# Schema Registry 컨테이너 기동
docker run -d --name schema-registry \
  -p 8081:8081 \
  -e SCHEMA_REGISTRY_HOST_NAME=localhost \
  -e SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS=PLAINTEXT://host.docker.internal:9092 \
  -e SCHEMA_REGISTRY_LISTENERS=http://0.0.0.0:8081 \
  confluentinc/cp-schema-registry:latest

또는 Confluent Platform 전체를 Docker Compose로:

# docker-compose.yml (발췌)
services:
  schema-registry:
    image: confluentinc/cp-schema-registry:latest
    ports:
      - "8081:8081"
    environment:
      SCHEMA_REGISTRY_HOST_NAME: schema-registry
      SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: "kafka:9092"
      SCHEMA_REGISTRY_LISTENERS: "http://0.0.0.0:8081"
    depends_on:
      - kafka

기동 확인:

curl http://localhost:8081/subjects
[]  (빈 배열 — 아직 등록된 스키마 없음)

빈 배열 []가 돌아오면 정상이다. 아직 스키마를 등록하기 전이므로. 여기서 버전 확인도 같이 한다:

curl http://localhost:8081/config
{"compatibilityLevel":"BACKWARD"}  (기본 호환성 모드)

이 값이 BACKWARD면 기본 호환성 정책이 켜진 것이다. subject별로 다르게 설정할 수도 있다(PUT /config/{subject}).

Schema Registry는 Kafka broker에 연결해 스키마를 내부 topic(_schemas, compact 정책)에 저장한다. 단일 인스턴스는 SPOF이므로 프로덕션에선 다중 인스턴스로 HA를 잡는다. 이때 모든 인스턴스가 같은 Kafka 클러스터를 바라보게 해야 스키마가 일관된다.

Avro 스키마 정의 — 메시지 구조를 JSON으로

Avro 스키마는 JSON으로 정의한다. User 레코드 예:

{
  "type": "record",
  "name": "User",
  "namespace": "com.example",
  "fields": [
    {"name": "id", "type": "int"},
    {"name": "name", "type": "string"},
    {"name": "email", "type": "string", "default": ""}
  ]
}
  • type: record — Avro 레코드(구조체).
  • fields — 필드 배열. 각 필드는 이름·타입·(선택)default.
  • default: "" — 이 필드가 없는 옛 데이터를 읽을 때 기본값 사용 → 호환성의 핵심.

namespace는 Java 패키지처럼 스키마 이름 충돌을 막는다. com.example.Usercom.other.User는 다른 스키마로 취급된다. 필드 타입은 단순형(int, long, string, boolean, bytes) 외에 복합형(record, array, map, enum, union)을 지원한다. nullable 필드는 ["null", "string"] 같은 union으로 표현(Java의 Optional<String> 대응) — "값이 없거나 string"을 의미한다.

스키마 등록과 호환성 검사

스키마 등록

# v1 등록
curl -X POST -H "Content-Type: application/vnd.schemaregistry.v1+json" \
  --data '{"schema": "{\"type\":\"record\",\"name\":\"User\",\"fields\":[{\"name\":\"id\",\"type\":\"int\"},{\"name\":\"name\",\"type\":\"string\"}]}"}' \
  http://localhost:8081/subjects/users-value/versions
{"id": 1}  (schema ID 발급)

호환성 위반 시나리오

  • v1: {id: int, name: string} (email 없음).
  • v2 시도: {id: int, name: string, email: string}(default 없음) → BACKWARD 위반 → Registry가 거부(409 Conflict).
  • v2': {id: int, name: string, email: string, default: ""} → default로 BACKWARD 호환 → 등록 성공(새 ID 발급).

이 "등록 시점 검사"가 단순 문서화가 아니라 운영 가드레일인 이유다.

왜 default가 호환성을 만드는가

"email 필드를 추가했는데 default가 없다"가 왜 BACKWARD 위반인지를 이해하면 진화 규칙이 보인다. BACKWARD는 "새 스키마가 옛 데이터를 읽을 수 있다"는 뜻이다. 옛 데이터(스키마 v1로 쓰인 레코드)는 email 필드가 없다. 새 스키마로 이 옛 데이터를 읽으려면, "email이 없으면 어떻게 할까"가 결정돼야 한다 — default가 있으면 default로 채우고, 없으면 역직렬화 에러. 그래서 default 없는 필드 추가는 "옛 데이터를 못 읽는다" = BACKWARD 위반이다.

반대로 필드 삭제는 FORWARD와 관련된다. 옛 consumer(삭제 전 스키마)가 새 데이터(삭제 후 스키마)를 읽을 때, 자기가 아는 필드가 데이터에 없다. default가 있었던 필드면 "없으면 default로" 처리 가능, default 없었으면 역직렬화 에러. 그래서 "삭제하려면 그 필드에 default가 있어야 한다"는 규칙이 나온다.

이 두 가지를 합치면 "안전한 진화 = default로 채울 수 있는 변경만 허용"이라는 단순한 원칙이 된다. default가 진화의 핵심 도구인 이유다.

스키마 버전과 schema ID의 차이

스키마는 두 가지 식별자를 갖는다:

  • version: subject별 버전 번호(1, 2, 3...). users-value subject의 첫 스키마가 v1, 다음이 v2.
  • schema ID: 전역 고유 ID(1, 2, 3...). 같은 내용의 스키마는 같은 ID로 재사용될 수 있다(중복 스키마 방지).

메시지에는 schema ID가 붙는다(wire format 0x00 + ID + payload). consumer는 이 ID로 Registry에서 스키마를 조회한다. version은 사람이 읽기 위한 용도, schema ID는 wire format용 — 둘이 다르다는 걸 헷갈리지 말 것.

호환성 모드

모드 의미 전제
BACKWARD 새 스키마가 옛 데이터를 읽을 수 있음 흔한 기본. consumer 먼저 업그레이드
FORWARD 옛 consumer가 새 데이터를 읽을 수 있음 producer 먼저 업그레이드
FULL backward + forward 가장 안전
NONE 검사 없음 위험

각 모드가 실제로 어떤 상황에 맞는지 보자. consumer를 먼저 업그레이드하고 producer를 나중에 배포하는 흐름(점진적 rollout)이면 BACKWARD가 맞다 — 새 consumer가 옛 데이터를 읽을 수 있으므로, consumer 배포 중에는 옛 producer가 보내는 옛 스키마 데이터도 처리 가능. 반대로 producer를 먼저 배포하면 FORWARD가 맞다 — 옛 consumer가 새 데이터를 무리없이 읽어야 하므로.

FULL은 "양쪽 순서에 상관없이 호환"이므로 가장 안전하지만, 제약이 가장 빡빡하다 — 새 스키마는 옛 데이터를 읽을 수 있어야 하고 동시에 옛 consumer도 새 데이터를 읽을 수 있어야 한다. 필드 추가 시 default를 두는 것만으로 BACKWARD는 충족되지만, FORWARD까지 채우려면 옛 consumer가 새 필드를 무시하도록 설계돼야 한다(Avro는 알아서 무시). NONE은 "아무 검사 안 함"으로, 사고의 원인이 되므로 프로덕션에선 거의 쓰지 않는다.

_TRANSITIVE 변형(BACKWARD_TRANSITIVE 등)은 "직전 버전"이 아니라 "모든 과거 버전"과 호환되는지 검사한다. 스키마가 여러 번 진화한 topic에서 유리하다.

Avro vs Protobuf vs JSON Schema — 무엇을 쓸까

Schema Registry는 Avro만 지원하는 게 아니라 세 가지 포맷을 지원한다:

  • Avro: 스키마와 데이터가 분리(스키마는 Registry에, 데이터는 payload만). 진화 규칙이 풍부(default, alias, type widening). Confluent 생태계에서 가장 널리 쓰임.
  • Protobuf: 필드 번호 기반. 스키마 진화가 필드 추가(새 번호)로 자연스럽게 일어남. gRPC와의 일관성이 필요한 조직에서 선호.
  • JSON Schema: JSON 메시지 구조 검증. 이미 JSON을 쓰는 시스템에서 추가 설치 부담이 적지만, Avro/Protobuf만큼 크기 효율이 좋지는 않다.

선택은 기존 기술 스택에 따라 갈린다. Avro가 Confluent 튜토리얼의 사실상 기본이라 많이 쓰이지만, 조직이 이미 Protobuf/gRPC를 쓰면 Protobuf가 자연스럽다. 어느 쪽이든 Schema Registry가 "스키마를 외부에 두고 호환성을 강제한다"는 역할은 같다.

subject 명명 전략

  • TopicNameStrategy(기본): <topic>-key / <topic>-value. 한 topic = 한 스키마 타입.
  • RecordNameStrategy: Avro record 이름 기준. 한 topic에 여러 타입 허용(이벤트 소싱에 유리).
  • TopicRecordNameStrategy: <topic>-<recordName>. topic별로 여러 타입을 허용하되 출처 topic도 추적.

왜 명명 전략이 중요한가 — Schema Registry는 "subject 단위로 호환성을 검사"한다. subject가 <topic>-value면, 그 topic에 들어가는 모든 메시지가 같은 스키마 진화선상에 있어야 한다. 한 topic에 서로 다른 타입의 이벤트(예: UserCreated, UserUpdated, UserDeleted)를 넣고 싶으면 TopicNameStrategy로는 한 스키마로 통합해야 한다. 반면 RecordNameStrategy는 각 record 타입마다 별도 subject를 두므로, 한 topic에 여러 타입이 공존하면서 각 타입별로 독립 진화가 가능하다 — 이벤트 소싱 아키텍처에서 자연스럽다.

producer/consumer Avro serde 설정

Schema Registry를 쓰려면 producer와 consumer가 Avro serde를 써야 한다.

producer 설정 (Java)

# Avro 직렬화 + Schema Registry
value.serializer=io.confluent.kafka.serializers.KafkaAvroSerializer
schema.registry.url=http://localhost:8081

# (선택) 등록 시 호환성 확인 안 함(개발용)
auto.register.schemas=true

producer가 send()할 때 Avro 객체를 직렬화하며 자동으로 Schema Registry에 스키마를 등록/조회한다. 메시지엔 0x00(magic byte) + schema ID(4바이트) + Avro payload가 붙는다.

consumer 설정 (Java)

value.deserializer=io.confluent.kafka.serializers.KafkaAvroDeserializer
schema.registry.url=http://localhost:8081
specific.avro.reader=true  # GenericRecord 대신 생성된 클래스 사용

Maven 의존성

<dependency>
    <groupId>io.confluent</groupId>
    <artifactId>kafka-avro-serializer</artifactId>
    <version>7.7.0</version>
</dependency>
<dependency>
    <groupId>org.apache.avro</groupId>
    <artifactId>avro</artifactId>
    <version>1.11.0</version>
</dependency>

serde 불일치 주의: Schema Registry serializer로 쓰고 plain deserializer로 읽으면(또는 역) "Unknown magic byte!" 에러. 양쪽 모두 Avro serde + 같은 Schema Registry여야 한다.
이 에러가 왜 나는지를 이해하면 wire format이 보인다 — Schema Registry serializer는 0x00 + schema ID + payload 형식으로 쓴다. plain deserializer(StringDeserializer 등)는 그냥 바이트를 UTF-8 문자열로 읽으려 하는데, 첫 바이트가 0x00이라 깨진 문자가 된다. "Unknown magic byte!"는 deserializer가 "이 바이트는 내가 아는 포맷이 아니다"라고 거부하는 에러다. 양쪽 모두 Avro serde로 맞춰야 wire format이 일치한다.
의존성 버전: kafka-avro-serializer 버전(7.7.0)은 Confluent Platform 버전이고, avro 버전(1.11.0)은 Apache Avro 버전이다. 둘은 별개 버전이므로 헷갈리지 말 것. Confluent Platform 버전 → 호환되는 Avro 버전 매핑은 Confluent 문서에서 확인한다(미검증: 정확 매핑표는 공식 문서 참조).

전체 워크플로우 — schema에서 consume까지

  1. 스키마 정의: Avro JSON으로 User 레코드 작성(default 필드 포함).
  2. Schema Registry 기동: Docker로 localhost:8081에 기동.
  3. 스키마 등록: curl POST /subjects/users-value/versions → schema ID 발급.
  4. producer 설정: KafkaAvroSerializer + schema.registry.url.
  5. produce: producer가 Avro 객체를 보내면 자동 직렬화 + ID 부여.
  6. consumer 설정: KafkaAvroDeserializer + 같은 Registry.
  7. consume: consumer가 ID로 스키마 조회 → 역직렬화 → Avro 객체 획득.
  8. 스키마 진화: v2에 default 필드 추가 → BACKWARD 호환 확인 → 등록.

이 워크플로우의 핵심은 6단계(consume)에서 consumer가 Registry에 조회한다는 점이다. consumer는 메시지의 schema ID를 보고 Registry에 "ID=3인 스키마가 뭔가?"를 묻는다. Registry가 스키마를 반환하면, 그 스키마로 payload를 역직렬화한다. 즉 consumer는 스키마를 코드에 하드코딩하지 않고 Registry에서 동적으로 가져온다 — 그래서 producer가 스키마를 바꿔도 consumer가 Registry에서 새 스키마를 조회할 수 있으므로(호환성 범위 내에서) 깨지지 않는다.

프로덕션에선 Registry 장애에 대비해 consumer가 스키마를 로컬에 캐시한다(schema.registry.cache.capacity, 기본 1000 스키마). Registry가 잠깐 죽어도 캐시된 스키마로 동작. 단, 새 스키마를 처음 보는 consumer는 Registry 장애 시 역직렬화를 못 하므로 Registry HA가 여전히 중요하다.

흔히 묻는 것, 흔히 틀리는 것

오해 정정
"Schema Registry는 Kafka core의 일부" Confluent 확장. ASF 배포판에 없음
"Avro는 압축 포맷" 아님. 스키마 기반 직렬화 포맷
"스키마 바꾸면 무조건 깨진다" 호환성 모드 + 진화 규칙 지키면 안전
"magic byte는 장식" wire format 식별자. plain serde와 섞으면 "Unknown magic byte!"
"Schema Registry 없이 Avro 써도 된다" 가능은 하나 schema ID/호환성 검사 불가 → 진화 관리가 수동
"Protobuf/JSON Schema는 Schema Registry가 안 지원" 지원함. Avro만 있는 게 아니다
"스키마 등록이 실패하면 메시지가 날아간다" 아니다. 등록 실패 = producer가 send 거부. 메시지는 안 써짐(안전)
"BACKWARD 호환이면 아무 필드나 추가해도 된다" 아니다. default 없는 필드 추가는 BACKWARD 위반

Avro 진화 규칙 — 무엇이 호환적이고 무엇이 아닌가

스키마를 바꿀 때 "이 변경이 호환되는가"를 판단하는 규칙:

  • 필드 추가: default 값을 주면 BACKWARD 호환(옛 consumer가 새 데이터를 읽을 때 default로 채움). default 없으면 BACKWARD 위반.
  • 필드 삭제: default 값을 줬던 필드면 FORWARD 호환. default 없었으면 FORWARD 위반.
  • 타입 widening: int → long, long → float/double, string → bytes 등은 호환(정보 손실 없는 확장). 반대 방향(narrowing)은 비호환.
  • 필드명 변경: 기본적으로 비호환. aliases로 옛 이름을 등록하면 제한적 호환.
  • 필드 타입 변경(int → string 등): 비호환. widening이 아닌 타입 변경은 새 필드 추가+옛 필드 삭제로 우회.

이 규칙을 지키면 "스키마를 바꿔도 consumer가 깨지지 않는다". 지키지 못하면 Registry가 등록을 거부하므로, 사고가 운영에 가기 전에 막힌다 — 이게 Registry를 "운영 가드레일"이라고 부르는 이유다.

더 깊이

  • Confluent wire format: 0x00 + schema ID(4B) + payload. 모든 메시지에 붙음.
  • _TRANSITIVE 모드: 직전 버전뿐 아니라 모든 과거 버전과 호환.
  • Avro 진화 규칙: 필드 추가→default 필수, 타입 widening(int→long)만, 필드명 변경은 비호환(alias로 일부 완화).
  • 비-Confluent 대안: Apicurio Registry(Apache 2.0, ASF), AWS Glue Schema Registry, Buf Schema Registry(Protobuf 중심). Confluent 종속을 피하려는 조직에서 쓴다. 모두 비슷한 역할(스키마 저장 + 호환성 검사)을 하지만 wire format과 클라이언트 통합이 다르다.
  • Schema Registry HA: 단일 인스턴스는 SPOF. 보통 다중 인스턴스로 띄우고, Kafka의 내부 topic(_schemas)에 스키마를 저장해 HA를 확보한다. 이것도 "Kafka 위에 올라간 서비스"라는 점에서 core가 아니라 생태계 확장임을 보여준다.

요약 — 이 글의 결론

  • Kafka = value를 바이트로만 저장. 스키마 모름 → 진화 문제.
  • Schema Registry(Confluent 확장, 비 core): 스키마 저장·버전·호환성 강제(등록 시점 검사).
  • 설정: Docker로 기동(포트 8081), Kafka broker에 연결.
  • Avro: 스키마-데이터 분리(JSON 스키마 정의). 크기 작음. 진화 규칙(default).
  • producer/consumer: KafkaAvroSerializer/Deserializer + schema.registry.url.
  • 호환성: BACKWARD(흔한 기본)/FORWARD/FULL/NONE. 필드 추가 시 default 필수.
  • 포맷 선택: Avro(Confluent 기본) / Protobuf(gRPC 일관성) / JSON Schema(기존 JSON 시스템). 포맷이 달라도 Registry의 역할은 같다.
  • 운영 표준: BACKWARD + consumer 먼저 배포 + default 필수.
  • 비-Confluent 대안: Apicurio Registry, AWS Glue Schema Registry 등. Confluent 종속을 피하는 옵션.

생각해 볼 문제

  1. Kafka가 value를 바이트로만 본다는 것이 왜 스키마 문제를 만드는가?
  2. Schema Registry를 Docker로 기동하는 방법을 설명하라.
  3. Avro 스키마에서 필드 추가 시 default가 필요한 이유는?
  4. producer/consumer에 Avro serde를 설정하려면 어떤 프로퍼티가 필요한가?
  5. "Unknown magic byte!" 에러가 발생하는 원인과 해결책은?
  6. Schema Registry가 ASF core가 아닌 것이 왜 중요한가? 대안은?
  7. BACKWARD 호환에서 consumer를 먼저 배포하는 이유는? FORWARD와의 차이는?
  8. Avro에서 int → long 타입 변경은 호환되지만 string → int는 안 되는 이유는?
  9. Schema Registry 없이 Avro를 쓰면 어떤 일이 벌어지는가? 어떤 기능을 잃나?

참고

'Data & Platform > Event Streaming' 카테고리의 다른 글

Kafka - 11. kafka streams  (0) 2026.07.09
Kafka - 10. kafka connect  (0) 2026.07.09
Kafka - 08. delivery semantics  (0) 2026.07.09
Kafka - 07. log retention  (0) 2026.07.09
Kafka - 06. replication  (0) 2026.07.09