Kafka 스키마 전략: 호환성 규칙으로 “배포 사고”를 막고, DLQ/Replay로 “복구 가능”하게 만들기
Avro/Protobuf 중 1개를 선택해 표준화하고, 호환성 규칙을 강제하며, 실패 메시지를 DLQ로 격리하고 Replay로 복구할 수 있는 운영 체계를 구축하는 방법을 정리합니다.
TL;DR (한 줄 요약)
- 스키마는 ~~“선택”~~이 아니라 **“규율”**입니다: 표준 포맷(Avro/Proto 택1) + Schema Registry + Compatibility 강제가 기본 세트입니다.
- 장애는 반드시 납니다. 중요한 건 **유실 없이 격리(DLQ)**하고 **통제된 재처리(Replay)**가 가능해야 합니다.
- Replay는 중복을 동반합니다. 멱등 키(dedup_key) 없으면 재처리는 곧 데이터 사고가 됩니다.
1) 왜 스키마 전략이 필요한가?
Kafka에서 가장 흔한 장애 시나리오는 단순합니다.
- 프로듀서가 먼저 배포되어 새 필드/새 타입으로 메시지 발행
- 컨슈머는 구버전이라 역직렬화/파싱 실패
- 컨슈머 크래시 → 리밸런스 폭주 → lag 급증
- 집계/피처 업데이트 중단 → 검색/랭킹 품질 및 운영 지표 급락
즉, 목표는 두 가지입니다.
- 스키마 변경이 배포 장애로 이어지지 않도록 사전에 차단
- 실패 메시지를 유실하지 않고 운영자가 복구할 수 있게 설계
2) Avro vs Protobuf: “하나로 통일”이 핵심
Avro와 Protobuf는 모두 좋은 선택지입니다. 문제는 혼용입니다. 혼용하면 다음이 동시에 복잡해집니다.
- 직렬화/역직렬화 스택
- 레지스트리/IDL/코드 생성 체계
- 호환성 검증 기준
- 장애 시 디버깅 및 재처리 도구
Avro를 선택하는 경우(일반적인 이유)
- 데이터 파이프라인/OLAP 적재에 친화적
- Schema Registry 기반 schema evolution(스키마 진화) 운영이 편리
Protobuf를 선택하는 경우(일반적인 이유)
- 마이크로서비스/gRPC 생태계와 결합이 쉬움
- 바이너리 크기가 작고 빠르며 코드 생성이 강력
선택 가이드(권장)
- “Kafka 중심 데이터 파이프라인” 성격이 강하면 Avro
- “서비스 계약(Proto)까지 통일”하려면 Protobuf
- 무엇을 선택하든 택1 후 표준화가 핵심
3) 호환성 규칙(Compatibility Rules): 변경을 “가능하게” 만드는 제약
호환성은 스키마 변경이 배포 사고로 이어지지 않도록 변경 범위를 제한하는 규칙입니다.
대표 모드
- Backward: 새 producer 메시지를 옛 consumer도 읽을 수 있음
- Forward: 옛 producer 메시지를 새 consumer도 읽을 수 있음
- Full: Backward + Forward (가장 엄격)
실무 기본값은 보통 Backward 또는 Full이 안정적입니다.
4) 안전한 스키마 변경 규칙(운영 표준으로 추천)
✅ 허용(대부분 안전)
- 필드 추가: optional + default (과거 메시지에 없어도 기본값으로 처리)
- enum 값 추가: 단, 컨슈머에 unknown 처리 전략이 있어야 함
- 새 이벤트 타입 추가: 새 topic 또는
event_type확장으로 분리
❌ 금지(브레이킹 체인지)
- 필드 삭제
- 필드명 변경
- 타입 변경 (예:
string → int) - required 의미의 필드 추가(과거 메시지에 없어 실패)
브레이킹 변경이 필요하면 “같은 토픽/같은 스키마”에서 억지로 해결하지 말고, 아래의 버저닝 전략을 쓰는 것이 안전합니다.
5) 브레이킹 변경을 처리하는 버저닝 전략 3가지
전략 A) 토픽 버전 분리(가장 권장)
search_impression.v1search_impression.v2
장점: 가장 명확하고 안전
단점: 토픽 수 증가
전략 B) Upcaster/Adapter (변환 계층)
- v1 메시지를 v2 형태로 변환해 내부 로직으로 전달
- 내부 코드는 v2만 유지
포인트: “여러 버전을 읽되, 내부 표준 모델은 1개”로 고정하는 방식
전략 C) Consumer Group 병행(섀도우 검증)
- 기존 그룹은 운영 유지
- 새 그룹은 결과를 비교/검증 (섀도우 운영)
포인트: 새 로직/새 스키마의 안정성을 운영 트래픽으로 검증 가능
6) DLQ(Dead Letter Queue): 실패를 “유실”이 아니라 “격리”로 바꾸기
DLQ는 컨슈머 처리 실패 메시지를 버리지 않고 별도 토픽으로 격리하는 패턴입니다.
DLQ로 보내는 기준(예시)
- 파싱 실패(스키마/직렬화 오류)
- 유효성 실패(필드 값 이상치)
- 영구 오류(데이터 자체 문제)
- 재시도 한계 초과
DLQ에 포함할 최소 메타(권장)
운영에서 “재처리”하려면, 원인 분석 + 원복/재발행이 가능해야 합니다.
- 원본 토픽/파티션/오프셋
event_type,schema_version,dedup_key- 실패 이유(
error_code, stacktrace 요약) - 컨슈머 버전, 실패 시각, retry count
- 원문 payload(가능하면 그대로)
토픽 예시:
search_impression.dlqac_select.dlq
7) Replay(재처리): “계획적으로 다시 처리”할 수 있어야 운영이 된다
Replay는 DLQ 또는 원 토픽의 특정 구간을 통제된 방식으로 다시 처리하는 전략입니다.
Replay가 필요한 상황
- 버그 수정 후 과거 이벤트를 재반영해야 하는 경우
- 집계 로직 변경으로 과거 데이터를 재계산해야 하는 경우
- 일시 장애로 lag가 누적되었다가 복구해야 하는 경우
Replay 방법 2가지
1) 오프셋 되감기(offset reset)
- 동일 consumer group의 오프셋을 과거로 되돌려 재처리
- 주의: 멱등 처리(
dedup_key) 없으면 중복 반영 위험이 큼
2) Replayer 서비스(권장)
- DLQ를 읽어 replay 토픽 또는 원 토픽으로 재발행
- 속도 제한/필터링/샘플링이 쉬움
- 운영자가 기간/조건을 제어 가능
8) 운영 규약(Definition of Done, DoD)
팀/조직 단위로 “이 정도는 되어야 Kafka 이벤트를 운영한다”의 체크리스트입니다.
- Avro/Proto 중 1개 확정 + 공통 라이브러리 제공
- Schema Registry 도입(또는 동등한 강제 장치)
- 호환성 모드(Backward/Full) 결정 + 배포 파이프라인에서 강제
- “허용/금지 변경” 문서화 + PR 체크리스트 반영
- DLQ 토픽 + 실패 기준 + 메타 필드 표준 확정
- Replay 절차(runbook) + 멱등 처리(dedup_key) 필수화
- 알람: DLQ 증가, consumer lag, 파싱 실패율, retry 폭증
9) 가장 흔한 사고 3가지와 예방책
사고 1) Producer만 먼저 배포 → Consumer 파싱 실패 → 리밸런스 폭주
- 예방: 호환성 강제 + (가능하면) consumer-first 배포 원칙
사고 2) DLQ 부재로 실패 메시지 유실 → 집계/피처 영구 손상
- 예방: DLQ 도입 + replay 도구화/런북화
사고 3) Replay 후 중복 반영(데이터가 “두 번” 들어감)
- 예방:
dedup_key기반 멱등 처리(컨슈머 필수)
마무리
Kafka에서 “스키마”는 메시지 포맷이 아니라 배포 안정성과 복구 가능성을 묶는 운영 계약입니다.
- 스키마 변경은 호환성 규칙으로 제어하고
- 실패는 DLQ로 격리하며
- 복구는 Replay로 가능하게 만들고
- Replay의 부작용(중복)은 멱등성으로 제거합니다.