
여기어때 정산팀이 non-blocking 재시도와 공통 DLT 라이브러리를 설계하며 정교하게 우회한 3가지 실무 함정
모든 실패가 크리티컬한 정산 도메인에서 시스템 일시 장애로 인한 메시지 누락은 용납될 수 없습니다. 본 아티클은 여기어때 정산개발팀이 개별 분산된 에러 처리 모델을 non-blocking 기반의 단일 공통 DLT 시스템으로 표준화하는 여정을 심도 있게 다룹니다. 직렬화 예외 대응부터 Spring Kafka 내부 인프라 제어에 이르기까지, 대규모 비동기 메시징 처리를 위한 고도로 최적화된 설계 인사이트를 제시합니다.
수많은 마이크로서비스에서 카프카 컨슈머를 개별 구축하여 실패 대응 방식이 일관적이지 않거나, 일시적 트래픽 집중 및 일시적 다운으로 인해 메시지 유실 리스크를 겪고 있는 대규모 백엔드 인프라 운영팀에 강력히 추천합니다.
정산 시스템의 Kafka 컨슈머들이 실패 처리를 위해 제각각 다른 방식(블로킹 재시도, 단순 로그 기록 등)을 도입하여 운영 중이었으며, 실패 메시지의 저장 위치와 포맷 또한 표준화되어 있지 않았습니다. 특히 일시적인 장애나 이벤트 순서 역전으로 인한 실패 시, 파티션을 블로킹하며 대기하는 방식은 컨슈머가 그룹에서 제외되는 2차 장애로 이어질 위험이 존재했습니다.
어노테이션 한 줄로 즉시 도입할 수 있도록 non-blocking 재시도 정책과 공통 DLT 발행 체계를 내장한 표준 라이브러리를 설계하였습니다. Spring Kafka의 RetryTopicConfigurationSupport를 직접 확장해 커스텀 네이밍과 DLT 페이로드 변환 분기를 적용하고, 꼭 필요한 실패 유형(KafkaRetryException)만 재시도하는 opt-in 방식을 도입했습니다.
재시도 시 파티션이 블로킹되지 않아 2차 장애 발생 우려가 완전히 제거되었으며, 어노테이션 추가만으로 신속한 공통 정책 적용이 가능해졌습니다. 모든 최종 실패 페이로드와 원본 메시지 위치 정보가 표준 포맷으로 단일 공통 DLT 시스템에 수집되어, Slack 알림 적재 및 추후 원활한 재처리(re-drive) 환경을 구축했습니다.
Trade-off
Non-blocking 방식을 채택함에 따라 메시지 소비의 엄격한 순서 보장이 불가능하므로, 비즈니스 로직 및 이벤트 소비 과정에서 반드시 멱등 처리가 전제되어야 합니다. 또한, Spring Kafka의 기본 설정 구성 요소를 직접 제어하는 과정에서 누락된 보조 스케줄러를 수동 등록해야 하거나 ThreadLocal을 활용해야 하는 등 인프라스트럭처 단의 구현 복잡도가 대폭 증가했습니다.
메시지 소비 실패 시 현재 처리 중인 파티션을 블로킹하지 않는 에러 대응 아키텍처입니다. 실패한 메시지만 별도의 재시도 토픽으로 전송해 비동기적으로 대기시킵니다. 이후 지정된 백오프 시간에 도달하면 별도의 재시도 컨슈머가 이를 읽어 지연 소비하도록 처리합니다.
재시도 한도를 모두 초과했거나 재처리가 무의미한 에러를 겪은 최종 실패 메시지들을 모아 격리하는 전용 카프카 토픽입니다. 이를 통해 정상 메시지 흐름이 실패 건에 방해받지 않도록 안전한 벽을 세웁니다. 수집된 실패 메시지는 보존 상태로 대기하며, 운영진의 실시간 모니터링 및 복구 작업에 활용됩니다.
Spring Kafka 라이브러리에서 제공하는 핵심 컴포넌트로, 컨슈머의 메시지 소비 및 재시도가 최종 실패했을 때 지정된 DLT로 데이터를 안전하게 전송하는 역할을 담당합니다. 내부적으로 에러 종류를 분류하고 목적지 토픽을 매핑하는 역할을 동반합니다. 해당 포인트의 확장을 통해 개발자는 최종 실패 메시지의 포맷이나 전달 로직을 고도로 유연하게 제어할 수 있습니다.




