Kafka Retry와 DLQ를 도입하게 된 이유
이커머스 프로젝트에서 주문 요청을 사가 오케스트레이터로 구현을 했는데 실패한 메시지를 재시도 하려다가 서비스 자체가 죽어버리는 상황이 발생했다. 서비스 간 호출 실패를 어떻게 다뤄야 할지 고민이 되었다. 무한 재시도를 하지 못하게하고, 실패한 이유를 데이터로 남겨서 운영자나 관리자가 확인하고 통제를 할 수 있지 않을까라는 생각을 해보았다.
이 고민의 결과로 Retry와 DLQ를 함께 도입하게 되었다.
Retry란 무엇인가?
메시지 처리가 실패했을 때, 같은 메시지를 다시 처리하도록 Kafka Consumer가 재시도하는 매커니즘이라고 한다.
Spring Kafka 기준으로 Retry는
- 동일한 offset의 메시지를 일정 시간 간격을 두고 지정된 횟수만큼 다시 Consumer에 전달한다.
왜 Retry가 필요하지?
Retry는 일시적인 실패(Transient Failure)를 전제로 한다. 아래는 예시이다.
- 외부 API 일시 장애
- DB 커넥션 풀 고갈
- 네트워크 타임아웃
- 잠깐 발생한 락 충돌
위와 같은 경우에는 메시지 자체에는 문제가 없다. 즉, 조금만 기다리면 성공할 가능성이 높은 메시지이다.
Retry는 이 가능성을 살리기 위한 장치이다.
DLQ(Dead Letter Queue)란 무엇인가?
DLQ는 Retry를 모두 소진한 뒤에도 처리에 실패한 메시지를 별도의 토픽으로 격리해서 보관하는 큐라고 설명할 수 있다.
Kafka에서는 보통 아래와 같이 사용한다.
원본 토픽: order-create-request
DLQ 토픽: order-create-request.DLQ
DLQ의 목적
DLQ는 재처리를 자동으로 하지 않기 위한 공간이다.
즉 해당 메시지는 사람이 확인해야 하는 메시지이고 로그만 보고 넘어가서는 안되는 실패들이다.
대표적으로 예외가 발생했을 때인데, 서비스 간 호출 시 JSON 스키마가 불일치하거나, 필수 필드가 누락되거나
타입 자체가 다른 메시지를 보내고 있거나 논리적으로 절대 성공할 수 없는 데이터를 보냈을 때 DLQ에 보관하게 된다.
Retry + DLQ를 같이 사용하는 이유
| 구분 | Retry | DLQ |
| 전제 | 일시적 실패 | 구조적 / 논리적 실패 |
| 처리 주체 | 시스템 | 사람(운영자) |
| 자동 재시도 | O | X |
| 목적 | 빠른 복구 | 실패 원인 분석 |
같이 사용하지 않게 되면 생기는 문제는 아래와 같다.
Retry만 사용하는 경우
절대 사용하지 않는 메시지를 계속 재시도 한다. 그러다가 Consumer가 막히거나, 로그만 쌓이고 실제 원인은 추적이 불가능하다.
DLQ만 사용하는 경우
실제로는 재시도 한 번이면 해결될 문제나 일시적인 장애도 바로 실패 처리를 해버린다.
Retry + DLQ를 같이 사용하는 경우
1. 장애 격리
- 실패한 메시지가 정상 흐름을 막지 않는다.
2. 시스템 안정성
- 무한 Retry 방지된다.
- 장애 전파 최소화
3. 실패 가시성 확보
- DLQ에 남은 메시지는 언제, 어떤 토픽에서, 어떤 예외로 실패했는지 명확하게 추적이 가능합니다.
4. 운영 기준의 재처리
- 실패 메시지를 바로 재처리할지, 수정 후 재처리할지, 폐기할지 운영자가 결정할 수 있습니다.
Retry + DLQ흐름 시퀀스 다이어그램

Retry + DLQ로 해결할 수 있는 문제들
- 메시지 한 건의 실패로 전체 스트림이 멈추는 문제
- 로그만 남고 실제 데이터가 유실되는 문제
- 장애 원인을 재현할 수 없는 문제
앞으로 해야 할 일
현재 프로젝트에서 서비스간 통신 시 실패할 경우 Retry를 3번의 재시도를 주었고, 3번의 재시도를 진행 한 이후에도 실패할 경우 DLQ에 기록이 되도록 되어있습니다.
따라서, DLQ 메시지 조회하는 API와, 재처리를 한다면 retry_count를 증가시켜서 한번 재처리 시도를 해보고, 그래도 실패할 경우 slack으로 해당 큐에 대한 내용을 보내어 장애가 발생한 지점을 알림으로 보낼 생각입니다.
'BACKEND & SERVER > Spring Boot MSA' 카테고리의 다른 글
| [Spring Boot] 사가 오케스트레이터의 이벤트 신뢰성을 높이기 위한 아웃박스 패턴 적용 (0) | 2025.12.16 |
|---|---|
| [Spring Boot] MSA 분산 트랜잭션 처리 방식 비교 (0) | 2025.11.27 |
| [Spring Boot] Spring Kafka Consumer에서 JSON 처리하기 (0) | 2025.11.26 |
| [Spring Boot] Kafka 기반 주문 서비스와 상품 서비스의 비동기 재고 차감 (0) | 2025.11.22 |
| [Spring Boot] FeignClient 사용 시 발생한 예외 처리 문제 (2) | 2025.11.19 |
