Kafka

Kafka 메세지 처리에 대한 고민

문쿼리 2025. 5. 25. 16:02

배경

이커머스 기반 주문 처리 시스템에서 Kafka를 도입하며, 대량 메시지 처리 환경에서도 안정적인 소비와 중복 방지, 장애 복구 구조를 어떻게 구현할지에 대한 실전 고민이 있었습니다.
Kafka는 at-least-once 전달을 보장하지만, 이는 곧 중복 소비의 가능성이 있다는 뜻입니다. 실제 장애 상황에서 메시지가 두 번 처리되며 중복 주문이 발생하는 문제를 경험하게 되었고, 이를 계기로 구조 개선을 시작하게 되었습니다.


문제 상황 1 - 중복 메세지 소비

Kafka 컨슈머가 메시지를 처리 중 예외가 발생하면, 해당 오프셋이 커밋되지 않아 같은 메시지를 다시 소비합니다.
단순히 보면 메시지를 놓치지 않기 위한 설계지만, 이로 인해 실제 주문이 두 번 처리되는 문제가 발생했습니다.

 

대응 1 – 수동 커밋 도입

Kafka의 자동 커밋 기능은 메시지 처리 완료 여부와 무관하게 오프셋을 주기적으로 저장하므로, 장애 발생 시 처리되지 않은 메시지가 유실될 수 있습니다.
이를 방지하고자 수동 커밋 방식으로 전환하여, 메시지 처리 완료 후에만 명시적으로 오프셋을 커밋하도록 했습니다.


문제 상황 2 – 수동 커밋의 복잡성과 운영 부담 증가

수동 커밋 전환은 중복 소비를 막는 데 효과가 있었지만,

  • 메시지 실패 시 재처리 여부 판단
  • 트랜잭션 경계 내에서 커밋 타이밍 조절
  • 처리 성공 여부에 따라 커밋 분기
    등 다양한 예외 처리를 직접 구현해야 했습니다.
    결과적으로 처리 로직이 복잡해지고 운영 부담이 증가했습니다.

초기 접근 – 메시지 생성 시 락으로 중복 차단

이 복잡성을 줄이기 위해, 메시지 생성 시점에서 Redisson 분산 락을 고유 키 기준으로 설정해 중복 메시지 자체를 아예 생성하지 않도록 설계했습니다. 하지만 테스트 중 처리 실패 시 락이 해제되지 않아, 이후 동일 메시지가 아예 생성되지 않는 문제가 발견되었습니다.

대응 2 – 소비 시점 락으로 구조 전환

구조를 수정하여 Kafka 메시지를 먼저 발행한 후, 소비 시점에 Redisson 락을 고유 키 기준으로 설정하고, 락을 획득한 컨슈머만 메시지를 처리하도록 했습니다.
이 방식은 다음을 가능하게 했습니다:

  • 메시지는 항상 Kafka에 들어감 → 복구 가능성 확보
  • 중복 소비는 락으로 제어 → 정합성 유지
  • 처리 실패 시에도 이후 재시도 가능 → 운영 유연성 확보

문제 상황 3 – 실패 메시지 복구 구조 부재

메시지 처리 중 예외가 발생했을 때, 실패 메시지를 별도로 추적하거나 재처리할 수 있는 흐름이 부재했습니다.

대응 3 – DLQ(Dead Letter Queue) 도입

Kafka의 DLQ 구조를 활용해 처리 실패 메시지를 별도 토픽으로 분리 저장하고, 후속 수동 재처리나 관리자 확인을 통해 서비스 신뢰성과 품질을 유지할 수 있도록 개선했습니다.

 


결과 및 교훈

이 구조 개선 과정을 통해 메시지 처리의 정합성과 안정성을 확보할 수 있었고,
Kafka의 메시지 보장 방식과 오프셋 커밋 구조, Redisson 락의 적용 위치에 대한 이해도 함께 얻을 수 있었습니다.