ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링 카프카 리트라이 정책 정하기
    글또 2023. 8. 8. 23:12

     

    0. 들어가며  

    안녕하세요.

    이번에는 스프링 카프카에서의 리트라이 정책을 설정하는 과정을 공유하려고 합니다.

    저희 시스템에서 프로듀서컨슈머의 리트라이 정책은 다음과 같이 설정했습니다.

     

    • 프로듀서: 서비스 요구에 따라 Kafka Reties 설정에 의존하거나, 이벤트 발행 실패 시 여러 번 재시도한 후 실패하면 개발자에게 알림을 보내고, 이벤트를 저장하여 유실을 방지한다.
    • 컨슈머: 이벤트 소비에 실패할 경우, 해당 이벤트를 DLT(Dead Letter Topic)에 저장하도록 한다.


    1. 프로듀서의 Retry
    2. 컨슈머의 Retry
    3. 마무리

     

    1. 프로듀서의 Retry

    언제 프로듀서가 메시지 발행을 실패할까요.

    • 리더가 없어 리더 선출 중일 때: 리더 선출이 완료되지 않은 상태에서는 메시지 발행이 실패할 수 있습니다.
    • ACK 설정 값에 따른 실패: 특정 ACK 설정 값에 따라, 브로커의 응답이 없거나 지연될 때 실패할 수 있습니다.
    • 전송 타임아웃: 메시지가 설정된 시간 내에 브로커로 전달되지 않을 때 발생합니다.
    • 브로커의 메시지 크기 초과: 브로커에서 허용하는 메시지 크기를 초과하거나 프로듀서 자체에서 요청 크기가 초과될 경우 실패합니다.
    • 버퍼 대기 시간 초과: 프로듀서 버퍼가 가득 차고 메시지가 정해진 시간 안에 처리되지 않으면 실패합니다.

    이 외에도 다양한 브로커 장애나 네트워크 문제 등으로 인해 메시지 발행이 실패할 수 있습니다.

     

    기본 Retry 정책의 적용

    카프카 프로듀서는 기본적으로 retries 설정을 통해 메시지 발행 실패 시 자동으로 재시도를 합니다.

    최신 버전에서는 이 retries 설정의 기본값이 int의 최대값으로 설정되어 있어, 재시도 횟수가 무한대에 가까울 수 있습니다.

    하지만, delivery.timeout.ms 설정 시간 만큼 재시도하기때문에 이 설정을 수정해야합니다.

    이는 재시도 기간을 지나치게 길지 않도록 적절히 조정하는 것이 중요합니다.

    브로커의 장애만 개발자가 알 수 있다면 이 방법또한 메시지 발행을 지킬 수 있습니다.

     

    중복 발행 방지

    재시도 과정에서 브로커의 ACK 응답 지연 등으로 인해 동일한 이벤트가 중복 발행될 수 있습니다.

    이를 방지하기 위해 idempotence(멱등성) 설정을 사용하면, 동일한 메시지가 중복으로 발행되더라도 한 번만 처리되도록 보장할 수 있습니다.

     

    메시지 유실 방지

    retries 설정을 통해 자동 재시도를 하더라도, 계속 실패할 경우 프로듀서가 재시작되거나, 재시도 횟수가 초과되면 메시지는 유실될 수 있습니다. 이런 상황을 방지하기 위해서는:

    • 재시도가 계속 실패할 경우, 실패한 메시지를 DB나 파일 시스템과 같은 외부 스토리지에 저장하고,
    • 개발자에게 장애 알림을 보낼 수 있는 로직을 구현해야 합니다.

     

    이와 같은 요구사항은 카프카 인프라 레벨의 기본 설정만으로는 충족되지 않으며, 애플리케이션 레벨에서 별도의 메시지 유실 방지 및 알림 기능을 구현해야 합니다.

     

     

     

    Spring-retry 사용

     

     

    kafka lack 알림등을 마련해 놓지 않았고 어플리케이션에서 제어하고 싶다면 Spring retry를 생각할 수 있습니다. 

    retries 설정이 적용되면 동일한 메시지가 재시도되지만, 멱등성 프로듀서에서는 서로 다른 메시지로 처리됩니다. 이는 프로듀서가 PID와 시퀀스 번호로 중복 여부를 검증하기 때문입니다. 따라서 retries를 0으로 설정하거나, 중복을 감수해야 합니다.

     

    spring-retry 방식의 문제점은 콜백 함수로 비동기 처리가 가능해도 실제로는 추가 로직 없이 블로킹처럼 작동할 수 있어 성능에 영향을 줄 수 있다는 점입니다.

     

     

     

    2. 컨슈머의  Retry

    컨슈머는 메시지를 소비하다가 에러가 발생할 때 실패합니다.

    이러한 경우를 처리하기 위한 여러 방식이 있습니다.

     

     

     

    2-1. ErrorHandler 방식

     

    컨슈머는 기존에 retry를 사용했지만, 이제는 ErrorHandler를 활용하여 재시도 로직을 처리합니다.

    에러가 발생하면, backOff 설정으로 5초 간격으로 두 번 재시도하고, 실패 시 DeadLetterPublishingRecoverer를 통해 DLT(Dead Letter Topic)로 메시지가 전송됩니다.

     

    이 방식은 에러 발생 시 컨슈머가 블로킹되어 재시도 후 처리하게 되며, 이로 인해 성능 저하가 발생할 수 있습니다.

     

    2-2. 어노테이션 방식

     

    https://docs.spring.io/spring-kafka/api/org/springframework/kafka/retrytopic/RetryTopicConfigurer.html

     

     


    스프링에서 제공하는 어노테이션을 사용하면 비동기 방식으로 리트라이 로직을 구현할 수 있습니다.

    메시지를 받은 스레드가 소비 중 에러가 발생하면, 해당 메시지를 다음 리트라이 토픽으로 전달한 후 종료됩니다.

    이후 다른 스레드가 리트라이 토픽에서 메시지를 가져오는 방식으로, 이를 비동기 처리라고 설명합니다.

     

    내부적으로 Retryable도 같은 방식을 사용하지만, 여러 개의 리트라이 토픽을 사용하는 차이가 있습니다.

    예를 들어, 최대 4번 재시도 설정 시, retry1부터 retry4까지 메시지가 전달되며, 마지막 리트라이에서도 실패하면 DLT로 전송됩니다.

     

    리트라이 토픽은 자동으로 생성되거나, 미리 설정해두어야 합니다.

    또한, DLT에서도 에러가 발생할 수 있으며, 기본 설정은 DLT로 재전송됩니다.

    DLT에 전송된 메시지는 @DLT 어노테이션을 통해 소비할 수 있습니다.

     

     

     

    3. 마무리

    저희 서비스에서는 Kafka의 retries와 RetryableTopic 어노테이션 방식을 사용하고 있습니다.

     

    프로듀서는 Kafka retries 설정에 의존하도록 했습니다.

    Kafka retries는 브로커가 다운되지 않는 한 설정된 재시도 횟수만큼 delivery.timeout.ms 동안 재시도를 진행하게 됩니다.

    만약 브로커에 장애가 발생하면 개발자가 알림을 통해 이를 인지하고 브로커를 재시작하면 문제가 해결될 수 있습니다.

    메시지의 유실 여부는 메시지의 중요성에 따라 판단해야 하는데, 저희 서비스에서는 다른 서비스로 전달되는 메시지가 유실되어도 큰 문제가 없다고 판단하여 이 같은 선택을 하였습니다.

     

    컨슈머에서는 메시지 유실이 절대 발생해서는 안 되는 서비스였기 때문에 여러 번의 Retry topic을 거친 후 최종적으로 DLT에 도달하도록 설계했습니다.

    만약 메시지가 DLT에까지 도달하면 개발자에게 알림이 전송되고, RDB에 해당 메시지와 에러 스택 트레이스를 기록하여 원인을 분석하고 재발행할 수 있도록 시스템을 구성하였습니다.

     

     

    앞서 언급했듯이, 메시지의 특성에 대해 충분히 고려하는 것이 중요합니다.

    메시지가 유실되어도 괜찮은지, 중복 발행이 허용되는지 등을 평가하여 적절한 리트라이 정책과 처리 방법을 결정하는 것이 좋습니다.

     

    처음으로 정책을 정하는 작업을 해봤습니다.

    Kafka에 대해 계속 공부하고, 팀원들에게 지식을 전파할 수 있었던 좋은 기회였다고 생각합니다.

      

     

     

     

     

     

    Sample Code

     

     

    GitHub - ChoiGiSung/spring-kafka-retry

    Contribute to ChoiGiSung/spring-kafka-retry development by creating an account on GitHub.

    github.com

     

     

     


    ps. 

     

    Auto commit이 false로 설정되어 있어도, Kafka에서는 리스너가 자동으로 커밋을 처리할 수 있습니다.

    이는 스프링 카프카의 KafkaMessageListenerContainer가 기본적으로 커밋을 처리하기 때문입니다.

    리스너의 기본 구현인 KafkaMessageListenerContainer는 메시지 오프셋을 자동으로 커밋할 수 있으며, ack-mode가 RECORD로 설정되어 있으면 commitIfNecessary() 메서드가 레코드 단위로 오프셋을 커밋합니다.

    하지만 수동 커밋 모드를 사용하는 경우에는 AcknowledgingMessageListener를 상속받아 구현하고, Acknowledgment 객체의 acknowledge() 메서드를 명시적으로 호출해야 합니다.

    이렇게 하면 커밋을 명확하게 제어할 수 있으며, 이는 스프링 카프카의 베스트 프랙티스입니다.

     

    '글또' 카테고리의 다른 글

    Spring kafka에서 Multi Type Converter 여행기  (0) 2023.09.25
    인프콘 2023  (0) 2023.08.22
    글또 8기 회고  (0) 2023.07.15
    오픈소스 기여해보기  (0) 2023.06.18
    메시지 플랫폼 무엇이 좋을까  (0) 2023.05.29

    댓글

Designed by Tistory.