콘텐츠로 이동
Study NoteKafka

6. 전달 보장

“정확히 한 번”을 약속하는 시스템은 없다 — “중복이 와도 무해한 시스템”을 만드는 것이다

이 장에서 처음 나오는 말5개
at-most-once
최대 한 번 — 유실될 수 있지만 중복은 없다. "놓쳐도 되는" 데이터의 태도다.
at-least-once
최소 한 번 — 유실은 없지만 중복될 수 있다. 실무의 기본값이다.
exactly-once
정확히 한 번. Kafka 안에서는 트랜잭션으로 성립하지만, 외부 시스템까지 자동으로 이어지지는 않는다.
멱등 소비Idempotent Consumer
같은 메시지를 두 번 처리해도 결과가 한 번과 같게 소비자를 만드는 것. at-least-once를 실질적 exactly-once로 만드는 실무 해법이다.
트랜잭션Transaction
여러 파티션 쓰기 + 오프셋 커밋을 원자적으로 묶는 Kafka 기능. "읽고-변환하고-다시 쓰는" 파이프라인용이다.

문제 — 장애는 항상 “사이”에서 난다

섹션 제목: “문제 — 장애는 항상 “사이”에서 난다”

유실도 중복도 근원은 같다. 두 동작이 원자적이지 않은데 그 사이에 장애가 나는 것이다.

프로듀서 쪽에서는 응답 유실 재시도로 중복이, 컨슈머 쪽에서는 커밋과 처리 순서에 따라 중복이나 유실이 생기는 틈
  • 프로듀서 쪽 틈은 멱등성이 이미 막았다 (4장 — 기본 켜짐)
  • 컨슈머 쪽 틈은 커밋 시점 선택의 문제다 (5장) — 처리와 커밋 중 무엇을 먼저 하든 반대쪽 사고가 남는다. 중복이냐 유실이냐를 고르는 것이다

세 가지 보장 — 무엇을 포기하는가

섹션 제목: “세 가지 보장 — 무엇을 포기하는가”
보장유실중복만드는 조합어울리는 데이터
at-most-once있음없음acks=0/1 · 커밋 먼저, 처리 나중놓쳐도 되는 지표 · 샘플링 로그
at-least-once없음있음acks=all + 3장 표준 조합 · 처리 후 커밋대부분의 업무 데이터 — 기본값
exactly-once없음없음(범위 내)at-least-once + 트랜잭션 (Kafka 안) 또는 + 멱등 소비 (외부)집계 파이프라인 · 금전 처리

“유실 없음”은 한 군데 설정이 아니라 세 층이 전부 조여져야 성립한다. 하나라도 빠지면 그 층에서 샌다 —

층나사빠지면
프로듀서acks=all + 멱등성 (기본값 유지) + 콜백 확인전송 실패를 모른 채 지나간다
브로커RF=3 + min.insync.replicas=2성공 응답 후 브로커 장애로 유실
컨슈머처리 후 수동 커밋자동 커밋 틈의 미처리 구간 유실
보존랙 최대치 < 보존 기간 (7장)밀린 사이 데이터가 지워진다

“Kafka는 exactly-once를 지원한다”는 문장은 범위를 붙여야 참이 된다.

  • Kafka → Kafka (읽어서 변환해 다른 토픽에 쓰는 파이프라인): 트랜잭션이 “결과 쓰기 + 원본 오프셋 커밋”을 원자적으로 묶어 준다. Kafka Streams가 설정 하나로 이 모드를 쓴다 — 여기서는 exactly-once가 실제로 성립한다
  • Kafka → 외부 시스템 (DB에 넣기, 메일 보내기, API 호출): 트랜잭션이 외부까지 묶어 주지 못한다. “DB에 넣고 커밋하기 전에 죽음”의 틈은 그대로다 — exactly-once는 성립하지 않고, at-least-once + 멱등 처리가 정답이다

멱등 처리의 상투적 수단 —

수단방법
고유 키 upsert이벤트 ID를 유니크 키로 INSERT … ON CONFLICT DO NOTHING
처리 이력 테이블처리한 이벤트 ID를 기록하고, 있으면 건너뜀 — 업무 처리와 같은 DB 트랜잭션으로
자연 멱등“상태를 X로 설정” 같은 연산은 원래 두 번 해도 같다 — 이벤트를 증분(“+100”)이 아니라 상태(“잔액=1100”)로 설계

순서 — 보장 범위를 정확히 알기

섹션 제목: “순서 — 보장 범위를 정확히 알기”
  • 보장되는 것: 한 파티션 안, 즉 같은 키끼리의 순서 (2장)
  • 보장 안 되는 것: 파티션 사이, 즉 다른 키 사이의 순서. 토픽 사이는 말할 것도 없다
  • 컨슈머가 재시도·병렬 처리를 직접 짤 때 애플리케이션 층에서 순서를 깨기 쉽다 — 파티션 내 레코드를 스레드 풀에 흩뿌리면 Kafka가 지켜 준 순서가 그 자리에서 사라진다. 파티션 단위로 순차 처리하거나, 키 단위로 직렬화하는 구조를 유지한다
  • 유실·중복의 근원은 “두 동작 사이”의 장애 — 프로듀서 틈은 멱등성이(기본), 컨슈머 틈은 커밋 시점이 정한다
  • 실무 기본값: at-least-once (acks=all + RF3/min.insync=2 + 처리 후 커밋) — 그리고 랙 < 보존 기간
  • exactly-once는 Kafka 안(트랜잭션 · Streams)에서만 자동 — 외부로 나가면 멱등 소비가 답
  • 순서는 같은 키까지만 — 컨슈머의 병렬 처리 코드가 순서를 깨지 않는지도 봐야 한다