Idempotent Consumer 설계

3줄 요약

  • Idempotent Consumer 설계 챕터는 Kafka나 Redis consumer가 같은 메시지를 다시 받아도 DB 변경, 포인트 지급, 이메일 발송 같은 결과가 한 번만 남도록 설계한다.
  • 핵심은 message id, consumer name, 처리 이력 table, business unique key, DB transaction, offset ack, 외부 idempotency key를 한 실패 경계로 맞추는 것이다.
  • Java/Spring 백엔드 개발자는 listener 코드에서 DB commit과 ack 순서, duplicate branch, side effect 보호, 처리 이력 보관 정책을 설명할 수 있어야 한다.

핵심 정리

  • Consumer 실패 모델: consumer는 poll 전, 처리 중, DB commit 후, ack 전, side effect 후에 죽을 수 있다. 각 위치가 누락 또는 중복을 만든다.
  • Idempotency Key와 처리 이력 테이블: message id와 consumer name을 unique key로 두고, 업무 변경과 처리 이력을 같은 transaction에 넣어 중복을 정상 branch로 처리한다.
  • Transaction Boundary와 Offset Commit: offset ack는 business commit 이후에 둔다. 누락보다 중복을 선택하고 중복은 idempotent write로 흡수한다.
  • Side Effect 중복 방지: 외부 API, 이메일, 포인트 지급은 DB rollback으로 되돌릴 수 없다. provider idempotency key나 outbox 기반 side effect가 필요하다.
  • Idempotent Consumer 코드 예시: 좋은 코드는 duplicate branch, transaction boundary, ack 위치, error handling, metric을 숨기지 않는다.
  • 이 챕터는 Web-Database의 단일 transaction 설계가 아니라 message redelivery 이후에도 결과가 한 번으로 수렴하는 consumer 설계를 다룬다.

헷갈리는 지점

  • 처리 이력 table만 있으면 멱등하다고 보기 쉽다.
    • 처리 이력 insert와 business write가 다른 transaction이면 실패 사이에서 중복이나 누락이 생긴다.
    • business table의 unique key와 upsert도 함께 필요하다.
  • offset commit 성공을 business 성공으로 착각하기 쉽다.
    • offset은 broker의 checkpoint이고, DB 반영과 외부 side effect 성공을 대신하지 않는다.
    • 그래서 ack 위치를 listener 코드에서 명확히 보여줘야 한다.
  • 외부 side effect는 consumer 멱등성의 가장 약한 지점이다.
    • 이메일, 포인트, 결제 API는 같은 event가 다시 들어왔을 때 provider idempotency key나 발송 이력으로 막아야 한다.
    • 외부 시스템이 idempotency를 지원하지 않으면 outbox와 보상 절차가 필요하다.

확인 질문

  • 이 챕터의 핵심 판단 기준은 무엇인가?
    • 같은 message id가 다시 들어와도 duplicate branch로 빠지고, DB 결과와 외부 side effect가 한 번만 남는지다.
  • Web-Database와의 경계는 무엇인가?
    • Web-Database는 DB 내부 transaction과 constraint를 다루고, 이 챕터는 message redelivery와 offset ack 이후의 중복 side effect를 다룬다.
  • 개인 프로젝트에서 최소로 챙길 것은 무엇인가?
    • processed message table, unique key, transactional handler, manual ack 위치, 같은 event 두 번 입력 테스트, duplicate skip metric이다.