이 글에서 얻는 것
- “멱등(idempotent)”이 producer에만 적용되는지, consumer 처리에도 필요한지 구분할 수 있습니다.
- Kafka에서 정렬(ordering)이 어디까지 보장되는지(파티션 단위)와 키 설계가 왜 중요한지 이해합니다.
- Exactly-once라는 말을 “마케팅 용어”가 아니라, 어떤 조건에서 어떤 범위로 가능한지 현실적으로 정리할 수 있습니다.
0) 전제: Kafka에서 중복/재처리는 정상이다
Kafka 소비는 보통 at-least-once(중복 가능)로 운영합니다.
- 컨슈머 재시작/리밸런스
- 처리 후 커밋(정상적인 전략)
- 재시도 토픽
이 조합이 있으면 “같은 이벤트가 두 번 처리되는 상황”은 자연스럽게 발생합니다. 따라서 시스템 설계의 핵심은 “중복이 와도 안전한 처리”입니다.
1) Producer 멱등성: “브로커에 중복 저장”을 줄인다
Kafka의 idempotent producer는 네트워크 오류/재시도 상황에서 같은 레코드가 브로커에 중복으로 기록되는 것을 줄입니다(파티션 단위).
핵심 설정(개념):
enable.idempotence=trueacks=allretries충분히(재시도 허용)max.in.flight.requests.per.connection은 환경에 따라 제한(재시도 시 정렬 보장과 관련)
중요: producer 멱등성은 “produce 중복”을 줄여줄 뿐, 컨슈머 측 중복(커밋/재시작)까지 자동으로 없애주지는 않습니다.
2) 정렬 보장: “파티션 내부”에서만
Kafka의 정렬은 이 한 문장으로 정리됩니다.
같은 파티션에 들어간 레코드는 순서가 유지된다.
그래서 정렬이 필요한 도메인에서는 키 설계가 핵심입니다.
예:
- 주문 상태 이벤트를
orderId로 키잉하면, 같은 주문의 이벤트는 같은 파티션에 들어가 순서가 유지됩니다.
반대로:
- 키를 안 넣거나(null key),
- 파티션이 여러 개로 흩어지면,
토픽 전체 순서는 보장되지 않습니다.
3) Consumer 멱등성: “비즈니스 결과 중복”을 막는다
현업에서 진짜 중요한 건 consumer 멱등입니다.
대표 패턴:
- 처리 이력 테이블:
processed_event(event_id)를 unique로 저장하고, 이미 있으면 스킵 - 업서트/유니크 키: 결과 테이블에 유니크 제약을 두고 중복 삽입을 실패로 처리
- 외부 API: 가능한 경우 idempotency key를 사용(결제/발송 등)
핵심은 “이벤트 ID” 또는 “비즈니스 키”가 있어야 한다는 점입니다.
없다면 이벤트 스키마에 eventId를 넣는 것부터 시작하는 게 좋습니다.
4) Exactly-once: 무엇을 의미하는가(현실 버전)
Exactly-once는 범위를 분리해서 이해해야 합니다.
4-1) Producer → Broker 레벨
idempotent producer/transactional producer로 “브로커에 중복 기록”을 줄이거나 없앨 수 있습니다.
4-2) End-to-end(Consume → Process → Produce)
진짜 end-to-end exactly-once는 조건이 많습니다.
- transactional producer
- consumer offset 커밋을 트랜잭션에 포함(consume-transform-produce)
- consumer는
isolation.level=read_committed로 커밋된 트랜잭션만 읽기
Kafka Streams는 이 흐름을 비교적 쉽게 제공합니다. 하지만 일반적인 서비스에서는 “멱등 처리 + at-least-once”가 운영 난이도 대비 가장 현실적인 선택인 경우가 많습니다.
5) 자주 하는 실수
- producer 멱등을 켜면 “중복 처리가 끝났다”라고 생각(consumer 중복은 여전히 발생)
- 키 설계 없이 파티션만 늘려서 “정렬이 깨지는” 사고
- 처리 이력 없이 재시도/DLQ를 운영해서 데이터가 중복 반영
- “exactly-once”를 목표로 했는데 운영 복잡도가 커져 오히려 장애가 늘어남
연습(추천)
- 주문 이벤트에
eventId를 추가하고, 처리 이력 테이블로 중복 소비를 막는 예제를 만들어보기 - 동일
orderId키로 메시지를 보내고, 파티션 내부 순서가 유지되는지 확인해보기 - producer 재시도 상황(네트워크 오류를 가정)에서 idempotence가 어떤 효과를 주는지 로그로 관찰해보기
💬 댓글