본문 바로가기
Research Notes (개인)

Kafka 스키마 전략: 호환성 규칙으로 “배포 사고”를 막고, DLQ/Replay로 “복구 가능”하게 만들기

Avro/Protobuf 중 1개를 선택해 표준화하고, 호환성 규칙을 강제하며, 실패 메시지를 DLQ로 격리하고 Replay로 복구할 수 있는 운영 체계를 구축하는 방법을 정리합니다.


TL;DR (한 줄 요약)

  • 스키마는 ~~“선택”~~이 아니라 **“규율”**입니다: 표준 포맷(Avro/Proto 택1) + Schema Registry + Compatibility 강제가 기본 세트입니다.
  • 장애는 반드시 납니다. 중요한 건 **유실 없이 격리(DLQ)**하고 **통제된 재처리(Replay)**가 가능해야 합니다.
  • Replay는 중복을 동반합니다. 멱등 키(dedup_key) 없으면 재처리는 곧 데이터 사고가 됩니다.

1) 왜 스키마 전략이 필요한가?

Kafka에서 가장 흔한 장애 시나리오는 단순합니다.

  1. 프로듀서가 먼저 배포되어 새 필드/새 타입으로 메시지 발행
  2. 컨슈머는 구버전이라 역직렬화/파싱 실패
  3. 컨슈머 크래시 → 리밸런스 폭주 → lag 급증
  4. 집계/피처 업데이트 중단 → 검색/랭킹 품질 및 운영 지표 급락

즉, 목표는 두 가지입니다.

  • 스키마 변경이 배포 장애로 이어지지 않도록 사전에 차단
  • 실패 메시지를 유실하지 않고 운영자가 복구할 수 있게 설계

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.v1
  • search_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.dlq
  • ac_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의 부작용(중복)은 멱등성으로 제거합니다.