강의

멘토링

커뮤니티

인프런 커뮤니티 질문&답변

gnstjrdlsla님의 프로필 이미지
gnstjrdlsla

작성한 질문수

실전에서 바로 써먹는 Kafka 입문

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

잘못된 이메일 주소로 인해 발송 실패 코드

작성

·

81

0

POSTMAN에
to: fail@naver.com 으로 변경하여 send할 시

총 5번 재시도 후 강사님 처럼 멈추지 않고 아래 에러가 무한 반복이 됩니다.

 

2026-01-04T20:45:42.814+09:00 ERROR 20160 --- [ner#0.dlt-0-C-1] k.r.DeadLetterPublishingRecovererFactory : Record: topic = email.send.dlt, partition = 0, offset = 2, main topic = email.send threw an error at topic email.send.dlt and won't be retried. Sending to DLT with name email.send.dlt.

org.springframework.kafka.listener.ListenerExecutionFailedException: Listener failed

at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.decorateException(KafkaMessageListenerContainer.java:3011) ~[spring-kafka-4.0.1.jar:4.0.1]

at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeOnMessage(KafkaMessageListenerContainer.java:2911) ~[spring-kafka-4.0.1.jar:4.0.1]

at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeOnMessage(KafkaMessageListenerContainer.java:2877) ~[spring-kafka-4.0.1.jar:4.0.1]

at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeRecordListener(KafkaMessageListenerContainer.java:2787) ~[spring-kafka-4.0.1.jar:4.0.1]

at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeWithRecords(KafkaMessageListenerContainer.java:2629) ~[spring-kafka-4.0.1.jar:4.0.1]

at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeRecordListener(KafkaMessageListenerContainer.java:2523) ~[spring-kafka-4.0.1.jar:4.0.1]

at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeListener(KafkaMessageListenerContainer.java:2161) ~[spring-kafka-4.0.1.jar:4.0.1]

at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeIfHaveRecords(KafkaMessageListenerContainer.java:1538) ~[spring-kafka-4.0.1.jar:4.0.1]

at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.pollAndInvoke(KafkaMessageListenerContainer.java:1476) ~[spring-kafka-4.0.1.jar:4.0.1]

at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.run(KafkaMessageListenerContainer.java:1345) ~[spring-kafka-4.0.1.jar:4.0.1]

at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1804) ~[na:na]

at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]

Caused by: org.springframework.kafka.listener.TimestampedException: Exception thrown at 2026-01-04T11:45:42.814281400Z

at org.springframework.kafka.listener.adapter.KafkaBackoffAwareMessageListenerAdapter.onMessage(KafkaBackoffAwareMessageListenerAdapter.java:100) ~[spring-kafka-4.0.1.jar:4.0.1]

at org.springframework.kafka.listener.adapter.KafkaBackoffAwareMessageListenerAdapter.onMessage(KafkaBackoffAwareMessageListenerAdapter.java:49) ~[spring-kafka-4.0.1.jar:4.0.1]

at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeOnMessage(KafkaMessageListenerContainer.java:2898) ~[spring-kafka-4.0.1.jar:4.0.1]

... 10 common frames omitted

Caused by: org.springframework.kafka.listener.ListenerExecutionFailedException: invokeHandler Failed

at org.springframework.kafka.listener.adapter.MessagingMessageListenerAdapter.checkAckArg(MessagingMessageListenerAdapter.java:519) ~[spring-kafka-4.0.1.jar:4.0.1]

at org.springframework.kafka.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:496) ~[spring-kafka-4.0.1.jar:4.0.1]

at org.springframework.kafka.listener.adapter.MessagingMessageListenerAdapter.invoke(MessagingMessageListenerAdapter.java:425) ~[spring-kafka-4.0.1.jar:4.0.1]

at org.springframework.kafka.listener.adapter.RecordMessagingMessageListenerAdapter.onMessage(RecordMessagingMessageListenerAdapter.java:92) ~[spring-kafka-4.0.1.jar:4.0.1]

at org.springframework.kafka.listener.adapter.RecordMessagingMessageListenerAdapter.onMessage(RecordMessagingMessageListenerAdapter.java:52) ~[spring-kafka-4.0.1.jar:4.0.1]

at org.springframework.kafka.listener.adapter.KafkaBackoffAwareMessageListenerAdapter.invokeDelegateOnMessage(KafkaBackoffAwareMessageListenerAdapter.java:106) ~[spring-kafka-4.0.1.jar:4.0.1]

at org.springframework.kafka.listener.adapter.KafkaBackoffAwareMessageListenerAdapter.onMessage(KafkaBackoffAwareMessageListenerAdapter.java:97) ~[spring-kafka-4.0.1.jar:4.0.1]

... 12 common frames omitted

Caused by: java.lang.IllegalStateException: No Acknowledgment available as an argument, the listener container must have a MANUAL AckMode to populate the Acknowledgment.

... 19 common frames omitted

답변 3

0

안녕하세요, 저도 같은 코드로 실습하면서 동일한 현상이 발생하였습니다!

 

consume() 메서드에는 별도의 Acknowledgment를 파라미터를 요구하지는 않습니다.

 

조금 알아보니 spring boot가 4.0.2 버전으로 올라오면서 @RetryableTopic의 dltStrategy 설정 기본 값이 기존에 FAIL_ON_ERROR 에서 ALWAYS_RETRY_ON_ERROR 로 변경 된 것으로 보입니다!

 

값이 ALWAYS_RETRY_ON_ERROR 일 경우, 생성된 dlt 토픽의 메시지를 consume() 메서드를 통해 처리하는 동작인 것 같습니다. 그렇기에 무한 루프를 통해 예외가 무한정으로 쌓이는 것이구요..!저는 아래와 같이 dltStrategy 값을 별도로 설정하니 해결되었습니다!

dltStrategy = DltStrategy.FAIL_ON_ERROR


질문을 새로 올리기보다는 해당 글에 답글로 추가 문의 드리면 좋을 것 같아서 답글 남깁니다!

JSCODE 박재성님의 프로필 이미지
JSCODE 박재성
지식공유자

안녕하세요! 강훈님 !

원인 파악과 해결책 공유해 주셔서 정말 감사합니다~~

말씀해 주신 대로 dltStrategy = DltStrategy.FAIL_ON_ERROR로 설정하시면 무한 루프 문제가 해결되는 게 맞아요 !

DLT 처리가 실패했을 때 ALWAYS_RETRY_ON_ERROR 설정이면 해당 레코드를 다시 DLT로 보내서 재시도하게 되는데, DLT 리스너에서 계속 실패하면 무한 반복이 발생하게 되거든요 ~

FAIL_ON_ERROR로 설정하면 DLT 처리 실패 시 재전송 없이 종료되어서 루프가 끊기게 돼요 !

다만 한 가지 보충 드리자면, "Spring Boot 4.0.2에서 기본값이 변경되었다"는 부분은 조금 달라요~

Spring Kafka 공식 문서와 Javadoc을 확인해 보니 @RetryableTopic이 처음 도입된 2.7 버전부터 dltStrategy의 기본값은 ALWAYS_RETRY_ON_ERROR였어요 !

Spring Boot 4.0에서 Spring Kafka의 retry 기능이 기존 Spring Retry에서 Spring Framework 코어로 이전되면서 내부 에러 핸들링 동작 방식이 달라졌어요! 이 과정에서 3.x에서는 DLT 처리 실패 시 조용히 넘어갔던 부분이 4.0에서는 더 엄격하게 재시도하도록 변경된 것으로 보여요 ! 그래서 기본값은 같지만 실제 동작이 달라져서 3.x에서는 문제없던 코드가 4.0에서 무한 루프가 발생하게 된 것 같네요 :)

제가 처음 원인 파악을 할 때는 버전을 미처 고려하지 못했는데, 해결책을 직접 찾아서 공유해 주신 덕분에 같은 문제를 겪으실 다른 수강생분들께도 큰 도움이 될 것 같아요 ! 감사합니다 😊

아 그렇군요! 제가 해결한 이슈에 대해 급하게 공유하느라 정확한 정보를 전달하지 못했네요..! 주신 피드백 덕분에 더 깊이 있는 학습이 된 것 같습니다 감사합니다 😁

0

JSCODE 박재성님의 프로필 이미지
JSCODE 박재성
지식공유자

안녕하세요 ! 질문 잘해주셨어요 !

질문해 주신 내용에 대해 답변드려볼게요 ~

 

에러 로그를 보니 DLT 토픽으로 메시지를 보내는 과정에서 문제가 발생하고 있네요!

에러 메시지에 No Acknowledgment available as an argument, the listener container must have a MANUAL AckMode to populate the Acknowledgment.라고 나오는 걸 보면, DLT Consumer 코드에서 Acknowledgment를 파라미터로 사용하려고 했는데 Kafka 리스너 설정이 MANUAL AckMode로 되어있지 않아서 발생하는 문제예요 !

해당 실습과 관련 없는 코드로 인해 발생한 문제 같아요 ~

우선 EmailSendConsumer 클래스의 consume 메서드를 확인해 보시는 걸 추천드려요 ! 이 메서드는 String message 파라미터 하나만 받아야 해요 !

혹시 Acknowledgment 같은 다른 파라미터가 추가되어 있다면 제거해 주셔야 해요 !

다시 한번 수업자료와 코드 비교해 보시고 수업 자료대로 코드를 작성하셨는데도 같은 에러가 발생한다면 사용하신 코드들, application.yml과 함께 에러가 발생했을 때의 전체 로그들을 알려주시면 더 정확하게 도와드릴 수 있을 것 같아요 :)

 

추가로 또 궁금하신 점 있으시면 언제든 편하게 질문 남겨주세요~~

0

안녕하세요, 인프런 AI 인턴입니다. gnstjrdlsla님!

Kafka를 이용한 이메일 전송 시 ListenerExecutionFailedException이 무한 반복되는 문제를 겪고 계신 것 같습니다. 이 문제는 주로 잘못된 이메일 주소로 인해 메시지가 전송되지 않으면서 발생할 수 있습니다. 여러 가지 가능한 원인과 해결 방법을 아래에 정리해 드릴게요.

문제의 핵심 원인

  1. 잘못된 이메일 주소로 인한 재시도 실패:

    • 말씀해주신 대로, 잘못된 이메일 주소가 반복적으로 실패하게 되면 이 문제가 발생할 수 있습니다.
  2. DLT (Dead Letter Topic) 관련 설정 문제:

    • DeadLetterPublishingRecovererFactory 메시지를 확인해보면, DLT로 넘어가야 하는 메시지가 제대로 처리되지 않는 것 같습니다. 설정을 잘 확인해볼 필요가 있습니다.
  3. 수동 AckMode 문제:

    • 로그에서 java.lang.IllegalStateException: No Acknowledgment available as an argument 에러가 보이는데, 이는 수동 ACK 설정이 필요함을 나타냅니다.

해결 방안

  1. 재시도 및 DLT 설정 확인:

    • @RetryableTopic 어노테이션의 설정을 정확하게 검토해 주세요. 특히 재시도 횟수(attempts)와 DLT 관련 설정(dltTopicSuffix)이 의도한 대로 설정되어 있는지 확인해야 합니다.
  2. ACK Mode 설정:

    • 수동 확인 모드를 설정하면, Listener가 메시지를 수동으로 확인해야 처리되도록 설정 가능하며, 관련 설정을 AckMode.MANUAL로 변경하고, Acknowledgment 객체를 주입 받아 수동으로 ack 처리하는 방식으로 구현할 수 있습니다.
  3. 로그 분석 통한 원인 파악:

    • DLT로 메시지를 정상적으로 보내는지, 해당 메시지가 어떻게 처리되고 있는지 더욱 자세히 로그를 남기도록 설정하여 문제의 근본적인 원인을 확인할 수 있습니다.

유사한 질문 및 해결 방안 링크

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

gnstjrdlsla님의 프로필 이미지
gnstjrdlsla

작성한 질문수

질문하기