스프링 - RabbutMQ Retry 정책 설정하기(ft. 10만원의 교훈)
작성 일자 : 2025년 01월 24일
개요
질문
스프링부트와 RabbitMQ를 연동하고, 만약 Consumer 로직에서 Exception이 발생하면 어떤 일이 일어나는지 아는가?
정답
Retry 정책이 없다면, 스프링부트의 Consumer 로직은 1초에 수십 번이고 다시 해당 메세지를 Consume하고 다시 Exception이 발생시킨다.
고통스러운 경험
필자와 같이 Exception 로깅으로 CloudWatch의 PutLogEvents 비용에 10만원을 지불하고 싶지 않다면, Retry 정책을 미리미리 설정해놓자!
손을 벌벌 떨며 원인이 무엇인지 파악하고 -> RabbitMQ WebConsole에 접속해서 메세지를 Purge하고 -> Retry 정책을 설정해서 다시 배포해놓기까지 얼마나 가슴 졸이며 작업했는지 모르습니다.
또한, AWS에서는 Billing을 실시간으로 업데이트 해주지 않습니다. (저의 경우 약 10시간 간격)
- 첫 번째 Buget Notification 이메일: 1월 8일 20시 41분 (41.81$)
- 두 번째 Buget Notification 이메일: 1월 9일 06시 07분 (82.11$)
그래서 Retry 정책, 어떻게 설정하는가?
Code
@Configuration
public class RabbitMQConfig {
// Exchange, Queue, Binding 설정 생략
/*
* Retry Interceptor for Fixed Backoff
*/
@Bean
@Qualifier("FixedBackoffRetryInterceptor")
public RetryOperationsInterceptor fixedBackoffRetryInterceptor() {
return RetryInterceptorBuilder
.stateless()
.maxAttempts(3) // 총 3번 시도
.backOffOptions(1000, 1.0, 1000) // 고정된 1초 간격으로 재시도
.build();
}
/*
* Retry Interceptor for Exponential Backoff
*/
@Bean
@Qualifier("ExponentialBackoffRetryInterceptor")
public RetryOperationsInterceptor exponentialBackoffRetryInterceptor() {
return RetryInterceptorBuilder
.stateless()
.maxAttempts(5) // 총 5번 시도
.backOffOptions(2000, 2.0, 30000) // 재시도 간격을 2초에서 30초까지 지수적으로 증가
.build();
}
/*
* Container Factories
*/
@Bean
public SimpleRabbitListenerContainerFactory listenerContainerFactory(
ConnectionFactory connectionFactory,
@Qualifier("FixedBackoffRetryInterceptor") RetryOperationsInterceptor fixedBackoffRetryInterceptor) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(jackson2JsonMessageConverter());
factory.setConcurrentConsumers(2);
factory.setMaxConcurrentConsumers(5);
factory.setBatchListener(false);
factory.setPrefetchCount(1);
// Retry Interceptor 적용
factory.setAdviceChain(fixedBackoffRetryInterceptor);
// 로깅
factory.setErrorHandler(throwable -> {
log.error("Message Queue Error", throwable);
});
return factory;
}
}
Fixed Backoff 예시
Attempt 1 --> Wait 1s --> Attempt 2 --> Wait 1s --> Attempt 3
Exponential Backoff 예시
Attempt 1 --> Wait 2s --> Attempt 2 --> Wait 4s --> Attempt 3 --> Wait 8s --> Attempt 4 --> Wait 16s --> Attempt 5