TestForge Blog
← 전체 포스트

Kafka Consumer 멱등 처리 플레이북 - 중복 메시지를 무서워하지 않는 백엔드 설계

Kafka 기반 비동기 시스템에서 메시지 중복은 왜 피할 수 없는지, 컨슈머 멱등 처리를 어떻게 설계해야 하는지, DB 트랜잭션과 상태 저장을 어떤 기준으로 나눠야 하는지 정리합니다.

TestForge Team ·

중복 메시지는 예외가 아니라 기본값이다

Kafka를 처음 도입한 팀이 가장 자주 하는 오해는 “메시지는 한 번만 처리될 것”이라는 기대입니다.
실무에서는 오히려 반대로 생각해야 합니다.

  • 메시지는 다시 올 수 있다
  • 컨슈머는 같은 이벤트를 두 번 볼 수 있다
  • 재시도와 리밸런싱은 언제든 일어난다

즉, 컨슈머 설계의 출발점은 “중복은 정상”이어야 합니다.

이 인식이 없으면 아래 문제가 반복됩니다.

  • 주문이 두 번 생성된다
  • 포인트가 두 번 적립된다
  • 알림이 여러 번 발송된다
  • 외부 API 호출이 중복 수행된다

Kafka 운영에서 중요한 것은 중복을 없애는 것이 아니라,
중복이 와도 결과가 한 번만 반영되게 만드는 것입니다.

멱등 처리의 핵심 질문

컨슈머 멱등 설계를 시작할 때는 아래 질문부터 정리해야 합니다.

1. 무엇이 “같은 처리”인가

이벤트 ID가 같으면 같은 처리인지,
비즈니스 키가 같으면 같은 처리인지 정의가 필요합니다.

예:

  • orderCreatedEventId
  • paymentId
  • userId + campaignId

기술 ID와 비즈니스 중복 기준이 항상 같지는 않습니다.

2. 한 번 처리되었다는 사실을 어디에 저장할 것인가

보통 후보는 세 가지입니다.

  • 비즈니스 테이블 자체
  • 별도 processed_events 테이블
  • Redis 같은 단기 저장소

3. 중복 처리 시 어떤 응답을 할 것인가

중복이면 실패로 볼 것인지,
이미 성공한 처리로 간주할 것인지 결정해야 합니다.

실무에서는 대체로 “이미 처리됨”으로 간주하는 편이 안전합니다.

가장 현실적인 방식: processed_events 테이블

많은 팀이 처음에는 Redis로 가볍게 막고 싶어 합니다.
하지만 운영 안정성을 보려면 DB 기반 기록이 가장 현실적일 때가 많습니다.

대표 구조:

  • event_id
  • consumer_name
  • processed_at
  • result_status

처리 흐름은 아래처럼 가져갑니다.

  1. 이벤트 ID로 processed_events를 조회한다
  2. 없으면 비즈니스 로직을 실행한다
  3. 같은 트랜잭션 안에서 처리 기록을 남긴다
  4. 이미 있으면 비즈니스 로직을 건너뛴다

핵심은 “비즈니스 반영”과 “처리 완료 기록”이 같은 트랜잭션 안에 있어야 한다는 점입니다.

비즈니스 테이블로 멱등을 흡수할 수 있는 경우

모든 경우에 별도 processed_events가 필요한 것은 아닙니다.

예를 들어 주문 생성 이벤트에서 order_id 자체가 유니크하고, 같은 주문이 두 번 INSERT 되지 않도록 설계되어 있다면
비즈니스 테이블 제약만으로도 멱등을 흡수할 수 있습니다.

이 방식이 좋은 경우:

  • 비즈니스 키가 명확하다
  • 유니크 제약이 자연스럽다
  • 처리 결과가 단일 테이블에 직접 매핑된다

이 방식이 어려운 경우:

  • 여러 테이블을 함께 변경한다
  • 외부 API 호출이 포함된다
  • 알림, 적립, 감사 로그가 함께 실행된다

즉, 단순 도메인은 비즈니스 제약으로,
복합 도메인은 별도 처리 기록으로 가는 편이 좋습니다.

외부 API 호출이 섞이면 더 조심해야 한다

가장 위험한 케이스는 컨슈머가 외부 시스템을 호출할 때입니다.

예:

  • 결제 승인 API
  • 문자 발송 API
  • 이메일 발송 API
  • 서드파티 적립 API

여기서 중복이 발생하면 내부 DB 문제로 끝나지 않고 실제 사용자 영향으로 이어집니다.

이 경우에는 아래 방어선이 필요합니다.

  • 외부 호출 전에 멱등 키 생성
  • 외부 시스템이 멱등 키를 지원하면 반드시 활용
  • 외부 호출 결과를 로컬에 저장
  • 재시도 시 이미 호출된 요청인지 먼저 확인

외부 시스템이 멱등 키를 지원하지 않는다면,
호출 직전 상태 저장과 사후 보상 전략까지 함께 고민해야 합니다.

exactly-once에 집착하지 말고 결과 일관성에 집중하자

Kafka의 exactly-once semantics는 강력하지만,
모든 비즈니스가 그것만으로 안전해지는 것은 아닙니다.

실제 운영에서는 아래가 더 중요합니다.

  • 이 이벤트가 다시 와도 결과가 바뀌지 않는가
  • 재처리 시 외부 영향이 중복되지 않는가
  • 장애 복구 후 상태가 일관적인가

즉, 메시징 계층의 보장보다 비즈니스 결과의 일관성이 핵심입니다.

운영 체크리스트

컨슈머 멱등 처리를 설계할 때 아래 항목을 같이 보면 좋습니다.

  • 이벤트 고유 키가 명확한가
  • 중복 판단 기준이 비즈니스 의미와 맞는가
  • 처리 기록과 비즈니스 반영이 같은 트랜잭션인가
  • 외부 API 호출에 멱등 키가 있는가
  • 실패 후 재처리 시 안전한가
  • DLQ로 보낸 뒤 재투입해도 결과가 안정적인가

마무리

Kafka 컨슈머 멱등 처리의 본질은 복잡한 이론이 아닙니다.
”같은 이벤트가 다시 와도 결과는 한 번만 반영되게 하라”는 단순한 원칙입니다.

정리하면 아래 세 가지가 핵심입니다.

  • 중복 메시지는 예외가 아니라 기본값으로 가정할 것
  • 처리 완료 기록과 비즈니스 반영을 같은 단위로 묶을 것
  • 외부 API 호출이 섞이면 멱등 키와 재시도 전략을 반드시 함께 설계할 것

이 원칙만 지켜도 Kafka 기반 백엔드는 훨씬 덜 불안해집니다.
멱등 처리는 성능 최적화보다 먼저 갖춰야 하는 운영 안정성의 기본 체력입니다.