5. Kafka 메시지 처리 실패 시 대처 방법 (feat. Spring) (DLT, 재시도) (+실습)

2025. 11. 24. 17:10·개발 공부/kafka

서론

  • Kafka를 이용하여 비동기 통신을 할 경우 사용자에게는 성공 여부와 상관없이 바로 성공이라고 보내지기 때문에 실패할 시 서버에서 처리를 해주어야한다.
    • 이를 위해 재시도, DLT 등의 방식이 쓰인다
  • 본 실습을 하기 전 EC2에 kafka가 설치 되어야하며 Producer와 Consumer가 구축된 로컬 스프링 서버가 필요하다. 만약 구축이 안되어 있거나 관련 이론을 먼저 공부하고 싶다면 아래의 링크를 참고하자

[환경 세팅]

 

2. Kafka 간단히 설치해보기 (feat. EC2) (+ 실습)

환경 세팅 전 갖추어야할 것AWS 계정적당한 성능의 EC2이번 실습은 AWS 프리티어에서 진행하기 위해 EC2 t2.micro (메모리 1GB)에서 진행함EC2에 Kafka 설치 / 실행하기1. JDK 17 설치하기Kafka를 실행시키려

student-developer-story.tistory.com

 

4. Kafka의 Producer, Consumer 서버 만들기 (feat. Spring) (+ 실습)

사전 준비이번에는 Kafka의 메시지를 주는 Producer와 받는 Consumer 서버를 SpringBoot로 만들어 볼 것이다.이에 앞서 AWS EC2에 Kafka가 설치되어 있어야 한다.만약 필요하다면 밑의 링크를 참고하면 됩니

student-developer-story.tistory.com

 

[관련 이론]

 

1. Kafka와 메시지큐 (정의, 형태)

Kafka와 메시지 큐Kafka란?대규모 데이터를 처리할 수 있는 메시지 큐메시지 큐란?큐 형태에 데이터를 일시적으로 저장하는 임시 저장소메시지 큐를 사용하면 데이터를 비동기로 처리할 수 있어서

student-developer-story.tistory.com

 

3-1. Kafka의 핵심 요소와 작동 (Topic, Consumer, Producer, Consumer Group, OffSet, CURRENT OFFSET) (+ 실습)

Kafka의 기본 구성Producer: Kafka에 메시지(데이터)를 전달하는 주체Consumer: Kafka에 메시지(데이터)를 처리하는 주체Topic: Kafka에 넣을 메시지의 종류를 구분하는 개념 (카테고리와 비슷)작동 과정Produce

student-developer-story.tistory.com

 

Kafka에서 처리에 실패한 메시지를 재시도(Retry)하도록 만들기

1. 의도적으로 실패하는 상황을 만들기 위해 Consumer 코드 수정하기

EmailSendConsumer

@Service
public class EmailSendConsumer
{

    @KafkaListener(
            topics = "email.send",
            groupId = "email-send-group" // 컨슈머 그룹 이름
    )
    public void consume(String message) {
        System.out.println("Kafka로부터 받아온 메시지: " + message);

        EmailSendMessage emailSendMessage = EmailSendMessage.fromJson(message);

        if (emailSendMessage.getTo().equals("fail@naver.com")) {
            System.out.println("잘못된 이메일 주소");
            throw new RuntimeException("잘못된 이메일 주소");
        }

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("이메일 발송 완료");
    }
}

2. Postman으로 실패 조건에 해당하는 API 요청 보내기

  • 실패해도 성공이라고 뜸

3. Consumer 서버에 찍힌 로그 확인하기

  • 요청을 보냈더니 Consumer 서버에서 재시도(Retry)에 대해서 아무런 코드를 작성하지 않았는데도 여러 번 재시도를 한 흔적이 로그로 남았다. 그리고 에러 메시지 바로 직전의 로그를 살펴보면 "Backoff FixedBackOff{interval=0, currentAttempts=10, maxAttempts=9} exhausted for email.send-0@5"라고 작성되어 있다.
    • 기본적으로 10번 바로 재시도가 기본값이기에 아무것도 설정하지 않았다면 그렇게 작동하는 것이다.
  • 로그에 찍힌 각 용어의 의미는 다음과 같다.
    • interval : 재시도를 하는 시간 간격 (ms)
      • interval=0일 경우 실패하자마자 즉시 재시도 한다는 뜻이다.
    • maxAttempts : 최대 재시도 횟수
      • maxAttempts=9인걸로 봐서 재시도를 9번까지 했다는 뜻이다.
    • currentAttempts : 지금까지 시도한 횟수 (최초 시도 횟수 + 재시도 횟수)
      • currentAttmpes=10인 이유는 최초 시도를 1번 하고 재시도를 9번 했기 때문이다.

재시도(Retry) 정책 변경하기

1. Consumer 코드 수정하기

EmailSendConsumer

@Service
public class EmailSendConsumer
{

    @KafkaListener(
            topics = "email.send",
            groupId = "email-send-group" // 컨슈머 그룹 이름
    )
    @RetryableTopic(
            attempts = "5", // 시도 횟수
            backoff = @Backoff(delay = 1000, multiplier = 2) // 재시도 간격 (1초, 2배씩 늘려나감) 1초.. 2초.. 4초..
    )
    public void consume(String message) {
        System.out.println("Kafka로부터 받아온 메시지: " + message);

        EmailSendMessage emailSendMessage = EmailSendMessage.fromJson(message);

        if (emailSendMessage.getTo().equals("fail@naver.com")) {
            System.out.println("잘못된 이메일 주소");
            throw new RuntimeException("잘못된 이메일 주소");
        }

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("이메일 발송 완료");
    }
}
  • 현업에서 보통 재시도 횟수는 3~5회 사이로 정하는 편이다. 왜냐하면 재시도를 너무 많이 할 경우 시스템 부하가 커질 수 있고, 너무 적으면 일시적인 장애에 대응하기 어렵기 때문이다.
  • 첫 재시도 간격은 짧게 설정하는 편이고, 그 이후 재시도 간격은 지수적(exponential)으로 증가하도록 설정하는 편이다. 그래야 일시적인 장애에 대해서는 첫 빠른 재시도로 대응이 가능하고, 장애가 조금 길어지는 경우라도 무의미하게 재시도하는 걸 방지하기 위함이다.

2. 테스트해보기

  • 로그를 보면 횟수가 5회이며 계속 시간이 증가하는 것을 볼 수 있음

 

Spring Boot로 Kafka에서 재시도조차 실패한 메시지를 따로 보관하기 (DLT, Dead Letter Topic)

  • DLT(Dead Letter Topic)는 오류로 인해 처리할 수 없는 메시지를 임시로 저장하는 토픽이다. Kafka에서는 재시도까지 실패한 메시지를 다른 토픽에 따로 저장해서 유실을 방지하고 후속 조치를 가능하게 만든다.

그럼 DLT는 왜 사용하는 걸까?

  1. 실패한 메시지를 DLT 토픽에 저장해놓기 때문에, 실패한 메시지가 유실되는 걸 방지할 수 있다.
  2. DLT 토픽에 실패한 메시지가 저장되어 있기 때문에, 사후에 실패 원인을 분석할 수 있다.
  3. DLT 토픽에 실패한 메시지가 저장되어 있기 때문에, 처리되지 못한 메시지를 수동으로 처리할 수 있다.

DLT를 활용해 재시도에 실패한 메시지 따로 보관하기

사실 Spring Kafka는 @RetryableTopic을 사용하면 자동으로 DLT 토픽을 생성하고 메시지를 전송해준다. 기본적으로 만드는 DLT 토픽 이름은 {기존 토픽명}-dlt 형태로 지어진다. 일관적인 DLT 토픽 이름을 위해 직접 DLT 토픽명을 별도로 다시 설정해주자.

1. Consumer 코드 수정하기

EmailSendConsumer

@Service
public class EmailSendConsumer
{

    @KafkaListener(
            topics = "email.send",
            groupId = "email-send-group" // 컨슈머 그룹 이름
    )
    @RetryableTopic(
            attempts = "5", // 시도 횟수
            backoff = @Backoff(delay = 1000, multiplier = 2), // 재시도 간격 (1초, 2배씩 늘려나감) 1초.. 2초.. 4초..
            dltTopicSuffix = ".dlt" // email.send.dlt로 저장이 됨
    )
    public void consume(String message) {
        System.out.println("Kafka로부터 받아온 메시지: " + message);

        EmailSendMessage emailSendMessage = EmailSendMessage.fromJson(message);

        if (emailSendMessage.getTo().equals("fail@naver.com")) {
            System.out.println("잘못된 이메일 주소");
            throw new RuntimeException("잘못된 이메일 주소");
        }

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("이메일 발송 완료");
    }
}

2. 서버 실행 후 API 요청 보내기

  • 실패한 후 dlt 토픽으로 잘 가는 모습을 볼 수 있음

3. CLI로 정말 DLT 토픽이 잘 생성됐는 지 확인해보기

# 토픽 전체 조회
bin/kafka-topics.sh \
	--bootstrap-server localhost:9092 \
	--list

  • email.send.dlt가 잘 저장된 모습을 볼 수 있다
  • email.send-retry-…은 재시도시에 저장된 토픽이다.
# 토픽 내부 메시지 전체 조회
bin/kafka-console-consumer.sh \
	--bootstrap-server localhost:9092 \
	--topic email.send.dlt \
	--from-beginning

  • 잘 저장된 것을 볼 수 있다.

그림으로 이해하기

  • Conusmer 서버가 메시지를 처리하다가 실패하면 정해진 횟수까지 재시도를 한다. 여기서 끝까지 재시도 처리에 실패한 메시지는 DLT 토픽으로 옮겨서 보관하게 된다.

 

Spring Boot로 재시도조차 실패한 메시지 사후 처리하기

DLT에 저장된 메시지를 사후 처리하는 방식

DLT에 저장된 메시지는 여러 번의 재시도를 거쳤음에도 불구하고 실패한 메시지이기 때문에 분명 문제가 있는 메시지이다. 따라서 이 메시지는 수동으로 체크하고 조치를 취해야 한다. 따라서 현업에서는 DLT에 저장된 메시지를 아래와 같은 방식으로 주로 처리한다.

  1. DLT에 저장된 실패 메시지를 로그 시스템에 전송해 장애 원인을 추적할 수 있도록 한다.
  2. DLT에 메시지가 저장되자마자 수동으로 대처할 수 있게 알림을 설정한다.
  3. 알림을 받은 관리자는 로그에 쌓인 내용을 보고 장애 원인을 분석하고, 그에 맞게 메시지를 수동으로 처리한다.

여기서 3번째 내용 중에 ‘메시지를 수동으로 처리한다’라는 부분을 더 자세히 살펴보자. 도대체 메시지를 수동으로 어떻게 처리한다는 걸까? 대표적인 수동으로 처리하는 방식의 예시를 알아보자.

DLT에 저장된 메시지 처리하기

  1. DLT에 저장된 메시지 처리하는 Consumer 로직 작성하기

EmailSendDltConsumer

@Service
public class EmailSendDltConsumer {
  @KafkaListener(
      topics = "email.send.dlt",
      groupId = "email-send-dlt-group"
  )
  public void consume(String message) {

    // ...로그 시스템에 전송하는 로직은 생략...
    System.out.println("로그 시스템에 전송 : " + message);
    
    // ...Slack에 알림 발송하는 로직은 생략...
    System.out.println("Slack에 알림 발송");
  }
}
  • Slack과 같이 알림을 발송하게 만들어놓으면 DLT로 빠진 메시지를 바로 인식할 수 있게 된다. 그러고 로그 시스템에 저장된 로그를 확인하면서 문제의 원인을 파악해 그에 맞게 메시지를 처리하면 된다.

2. 서버 재시작해서 테스트해보기

  • Slack에 정상적으로 보내지는 모습을 볼 수 있다.

출처: https://inf.run/qzJua

저작자표시 (새창열림)

'개발 공부 > kafka' 카테고리의 다른 글

Kafka 고가용성 확보하기 (node, broker, controller, cluster, replication) (+실습)  (0) 2025.12.05
6. Kafka 파티션 이용해서 성능 향상 시키기 (+실습)  (0) 2025.12.01
4. Kafka의 Producer, Consumer 서버 만들기 (feat. Spring) (+실습)  (0) 2025.11.23
3-2. Kafka 토픽 네이밍 규칙  (0) 2025.11.21
3-1. Kafka의 핵심 요소와 작동 (Topic, Consumer, Producer, Consumer Group, OffSet, CURRENT OFFSET) (+ 실습)  (0) 2025.11.21
'개발 공부/kafka' 카테고리의 다른 글
  • Kafka 고가용성 확보하기 (node, broker, controller, cluster, replication) (+실습)
  • 6. Kafka 파티션 이용해서 성능 향상 시키기 (+실습)
  • 4. Kafka의 Producer, Consumer 서버 만들기 (feat. Spring) (+실습)
  • 3-2. Kafka 토픽 네이밍 규칙
Jamey
Jamey
  • Jamey
    컴공 대학생의 이야기
    Jamey
  • 전체
    오늘
    어제
    • 분류 전체보기 (36)
      • 개발 공부 (33)
        • k8s (24)
        • kafka (8)
        • AI (1)
      • 개발기 (2)
      • 프로젝트 홍보 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 깃허브
  • 공지사항

  • 인기 글

  • 태그

    Producer
    Jenkins
    topic
    카프카
    AI
    Kubernetes
    Linux
    current offset
    Kafka
    serialDB
    조인 쿼리
    Rag
    llm최적화
    consumer
    langchain
    토픽
    K8S
    Graphana
    sql자동화
    cloudflare workers
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
Jamey
5. Kafka 메시지 처리 실패 시 대처 방법 (feat. Spring) (DLT, 재시도) (+실습)
상단으로

티스토리툴바