펜타시스템/입사 후 첫 프로젝트 공부 내용

두 번째 과제 개념 : Kafka의 메시지 포맷에 대해

dev-lee 2026. 9. 16. 15:57

1. Kafka 메시지 포맷과 자유도

Kafka broker는 메시지를 순수 바이트 배열로만 다루며, 포맷을 검증하지 않는다는 설계를 갖고 있다.

1.1 broker가 포맷을 가리지 않는 이유

  • 바이트 처리 — broker는 key/value를 그냥 바이트 뭉치로 저장했다가 그대로 전달할 뿐, 내부에 JSON이 있는지 Avro가 있는지 파싱하거나 검증하지 않음
  • 포맷 종류 — JSON, Avro, Protobuf, plain string 등 producer/consumer가 합의만 하면 어떤 포맷도 사용 가능
포맷  특징
JSON 사람이 읽기 쉽고 스키마 없이 자유롭게 변경 가능, 용량이 크고 파싱 비용 있음
Avro 스키마 기반, 용량 작음, Schema Registry와 함께 스키마 진화 관리에 용이
Protobuf 스키마 기반, 강타입, gRPC 생태계와 잘 맞음
Plain string/CSV 가장 단순, 구조화된 데이터 다루기엔 불편

1.2 "포맷을 자유롭게 하라"는 말의 의미

이 표현은 broker의 동작을 서술하는 문장이 아니라, 그 동작으로 생기는 여지를 개발자가 채워야 한다는 지침이다.

Kafka는 형식을 강제하지 않으므로, 그 형식이 무엇이 될지는 broker가 아니라 개발자가 설계해서 정해야 한다. "자유롭다"가 아니라 "자유롭게 하라"인 이유는, 책임의 주체가 시스템이 아니라 사람이라는 데 있다.

broker 자체엔 형식 검증 기능이 없고, 형식을 통제하고 싶으면 broker 기능을 켜는 게 아니라 producer/consumer 앞단에 Schema Registry 같은 별도 도구를 세워서 관리해야 함.


2. Publish와 저장 구조

2.1 Publish의 정의

publish는 producer가 특정 topic(정확히는 topic의 partition)에 메시지를 write하는 행위이다.

2.2 버퍼가 아니라 커밋 로그

  • broker가 받은 메시지는 임시 버퍼가 아니라 커밋 로그(commit log) 형태로 디스크에 순차 append됨
  • consumer가 가져가든 안 가져가든 상관없이, retention 기간 동안 partition 안에 남아있는 구조
  • OS 페이지 캐시나 메모리 버퍼를 거쳐 디스크에 flush되는 과정은 성능 최적화를 위한 내부 동작일 뿐, 개념적으로 publish는 "메시지가 broker의 로그에 영구히 기록되는 것"을 의미함

3. Topic과 Partition

3.1 Topic

topic은 메시지를 분류하는 논리적인 채널이며, 데이터 종류(도메인)에 따라 나누는 것이 일반적이다 (예: orders, payments, user-events).

3.2 Partition — 물리적 저장 단위

topic 하나는 물리적으로 여러 partition으로 쪼개져서 저장된다.

  • topic orders를 partition 3개로 만들면 orders-0, orders-1, orders-2라는 실제 로그 파일 3개가 생기는 구조
  • 각 partition은 독립적인 커밋 로그이며, 메시지는 들어온 순서대로 append되고 고유한 offset(0부터 시작하는 순번)을 부여받음

3.3 Partition으로 나누는 이유

  • 병렬 처리 — consumer group 안의 여러 consumer가 partition을 나눠 동시에 읽어 처리량 증가
  • 분산 저장 — partition들이 여러 broker에 나뉘어 저장되어 데이터가 여러 서버에 분산됨
  • 순서 보장의 단위 — Kafka는 topic 전체의 순서를 보장하지 않고, 같은 partition 안에서만 순서를 보장함

3.4 Replication

  • topic 생성 시 replication factor를 지정하면 각 partition이 여러 broker에 replica로 존재하게 됨
  • replica 중 하나가 leader가 되어 모든 읽기/쓰기를 처리하고, 나머지는 follower로 leader를 복제함
  • leader 장애 시 follower 중 하나가 새 leader로 승격되어 가용성을 유지함

4. Partition 개수 설정과 할당

4.1 개수 설정

topic 생성 시점에 개발자/운영자가 직접 partition 개수를 지정한다. Kafka가 데이터 양을 보고 자동으로 정해주지 않는다.

kafka-topics.sh --create --topic orders \
  --bootstrap-server localhost:9092 \
  --partitions 3 \
  --replication-factor 2
  • --partitions로 늘릴 수는 있지만 줄일 수는 없음 — 줄이면 기존 데이터의 순서/키 매핑이 깨지기 때문

4.2 Partition은 저장 용량 단위가 아니다

  • partition에 "몇 개까지 담을 수 있다"는 고정 용량 개념은 없음 — 디스크 공간이 허락하는 한 계속 커질 수 있음
  • 오래된 데이터는 개수 제한이 아니라 retention 정책(시간 기준 또는 용량 기준)으로 삭제됨

4.3 Partition 개수를 정하는 실제 기준

partition 개수는 저장 용량이 아니라 처리량과 병렬성으로 정해진다.

  • partition 하나가 처리 가능한 초당 처리량에는 현실적인 상한이 있음
  • 목표 처리량이 이 상한을 넘으면 partition을 늘려 분산시켜야 함
  • consumer group에서 몇 개의 consumer가 동시에 병렬 처리할지도 기준이 됨 — partition이 3개면 consumer도 최대 3개까지만 병렬로 일함

4.4 메시지가 어느 partition으로 가는가 (producer 쪽)

  • partition 번호 직접 지정 — 지정한 번호로 바로 감
  • key가 있는 경우 — hash(key) % partition 개수로 결정, 같은 key는 항상 같은 partition으로 감
  • key가 없는 경우 — sticky partitioner가 배치 하나가 찰 때까지 같은 partition에 몰아 보내고, 배치가 차면 다른 partition으로 전환 (예전엔 라운드로빈이었으나 배치 효율 문제로 변경됨)

4.5 Consumer 쪽 할당

  • partition 1개는 같은 consumer group 내 consumer 1개에게만 할당됨
  • partition 3개, consumer 5개면 2개는 유휴 상태가 되고, partition 3개에 consumer 2개면 한 consumer가 2개를 맡게 됨
  • 할당 전략은 partition.assignment.strategy로 설정하며 Range, RoundRobin, Sticky, CooperativeSticky 등이 있음. 최근엔 리밸런싱 시 끊김을 줄이기 위해 CooperativeSticky가 많이 쓰임

5. Partition의 성격 — 처리량 단위이자 순서 보장 단위

5.1 CPU 코어와의 비교

partition은 병렬 처리 단위라는 점에서 CPU 코어와 비슷하지만, 완전히 같지는 않다.

  • 비슷한 점 — partition이 여러 개면 여러 consumer가 동시에 나눠 처리 가능, partition 하나의 처리량에도 상한이 있음
  • 다른 점 — partition은 동시에 순서가 있는 로그이기도 함. partition을 늘리면 처리량은 오르지만 그 대가로 전체 topic의 순서 보장은 포기하게 됨 — 병렬성과 순서 보장 사이의 트레이드오프

5.2 순서가 보장되지 않는 진짜 이유

각 partition이 서로 독립적으로 흐르기 때문에, 여러 partition에 걸쳐 흩어진 메시지들의 상대적인 순서를 재구성할 방법이 없다.

  • 예: 메시지 1,2,3,4,5가 들어왔을 때 partition 1에는 1,3,4가, partition 2에는 2,5가 쌓였다고 가정
  • 각 partition 안에서의 순서는 offset으로 완벽히 보장됨 (partition 1: 1→3→4, partition 2: 2→5)
  • 하지만 이 둘을 합쳤을 때 원래 순서(1,2,3,4,5)를 복원할 방법은 없음 — Kafka는 메시지마다 전역 순번(global sequence number)을 기록하지 않고, partition별 offset만 존재하기 때문

그래서 같은 key를 가진 데이터는 항상 같은 partition으로 보내서, 적어도 그 key 단위(예: 같은 사용자, 같은 주문 ID) 안에서는 순서를 지키게 만든다. 전체 topic의 순서는 포기하되, 의미 있는 단위 안에서의 순서는 지키는 절충안이다.


6. Serde — 직렬화/역직렬화

Serde는 Serializer + Deserializer의 합성어이다.

  • Serializer(직렬화) — 애플리케이션 객체를 Kafka가 저장/전송할 수 있는 바이트 배열로 변환
  • Deserializer(역직렬화) — 바이트 배열을 다시 애플리케이션이 이해할 수 있는 객체로 복원
  • 대표적으로 StringSerde(문자열 ↔ UTF-8 바이트), JsonSerde, AvroSerde(Schema Registry와 연동해 스키마 정보까지 처리)가 있음

broker는 메시지를 바이트로만 다루기 때문에, producer/consumer 양쪽이 각자 "보낼 땐 이렇게 바이트로, 받을 땐 이렇게 객체로"라는 규칙을 정해야 하는데 그 규칙 한 쌍이 Serde이다. 포맷이 자유로운 만큼, 그 포맷을 바이트로 바꾸고 되돌리는 규칙은 사용하는 쪽에서 직접 선택하고 관리해야 한다.


7. 실전 확인 — Kafbat UI와 Schema Registry

7.1 Kafbat UI로 메시지 포맷 확인

Kafbat UI에는 Message Browser 기능이 있어 topic에 쌓인 메시지를 JSON, plain text, Avro 인코딩 등으로 확인 가능하다.

  • JSON/문자열은 별도 설정 없이 바로 읽힘
  • Avro/Protobuf 같은 스키마 기반 포맷은 Schema Registry와 연동되어야 정상 디코딩됨
  • subject naming 전략(TopicNameStrategy vs RecordNameStrategy)이 producer 설정과 다르면 스키마를 못 찾아 메시지가 깨져 보이는 문제가 자주 발생함

7.2 메시지가 안 남아있을 때

메시지가 안 보이는 원인은 대부분 retention 정책이다. Kafka는 기본적으로 일정 기간(기본값 7일)이 지나면 오래된 세그먼트부터 자동 삭제한다.

  • topic Settings/Configs 탭에서 retention.ms(시간 기준) 또는 retention.bytes(용량 기준) 확인 가능
  • Message Browser에서 low watermark(시작 offset)가 0이 아니라 앞으로 밀려 있으면 일부가 삭제된 것

메시지가 이미 지워진 경우 포맷을 확인하는 대안은 다음과 같다.

  • Schema Registry에서 직접 스키마 확인 — 메시지는 지워져도 등록된 스키마는 남아있음
    curl http://<schema-registry-host>:8081/subjectscurl http://<schema-registry-host>:8081/subjects/<topic-name>-value/versions/latest
    
  • Producer(NiFi) 설정 확인 — PublishKafka 계열 Processor의 Record Writer(JsonRecordSetWriter, AvroRecordSetWriter 등) 설정을 보면 포맷이 바로 나옴
  • 새 메시지를 발생시켜 즉시 확인 — 가장 빠른 방법

7.3 Schema Registry 목록 필드

필드  의미
subject 스키마가 등록된 이름. 보통 토픽명-value 또는 토픽명-key
id 스키마 자체에 부여된 전역 고유 번호
type 스키마 언어 종류 (AVRO / JSON / PROTOBUF)
version 해당 subject 안에서 스키마가 개정된 순번
compatibility 새 버전이 이전 버전과 얼마나 호환되어야 하는지 정하는 규칙 (BACKWARD/FORWARD/FULL/NONE)

type: JSON은 그냥 JSON 문자열을 의미하는 게 아니라 JSON Schema로 등록되어 있다는 뜻이다. 실제 필드 구조(어떤 필드가 있는지)를 보려면 subject 상세 화면에서 최신 version의 스키마 본문을 열어봐야 한다.


요약

개념 핵심
메시지 포맷 broker는 형식을 강제하지 않으며, 형식 관리는 producer/consumer의 책임
Topic/Partition topic은 논리적 채널, partition은 물리적 로그 단위로 병렬 처리와 분산 저장을 담당
순서 보장 같은 partition 안에서만 보장되며, 전체 topic 순서는 보장되지 않음
Partition 설정 저장 용량이 아니라 처리량/병렬성 기준으로 개수를 정함
Serde 객체 ↔ 바이트를 오가는 producer/consumer 간의 변환 약속
Schema Registry 자유로운 포맷에 스키마 강제와 호환성 관리를 얹어주는 역할

Kafka의 핵심은 "형식과 순서를 시스템이 강제하지 않는 대신, 그만큼의 유연성과 책임을 producer/consumer 쪽에 넘기는 구조"라는 점이다.