Backoff 전략과 Spring Retry로 안정적인 재시도 구현하기
시스템을 개발하다 보면 외부 API 호출, 분산락 획득, 네트워크 요청과 같이 일시적인 실패가 발생할 수 있는 작업을 마주하게 됩니다. 이런 실패 상황에서 가장 많이 쓰이는 전략이 바로 Backoff입니다.
이번 포스팅에서는 Backoff 전략의 개념과 종류, 그리고 이를 손쉽게 구현할 수 있는 Spring Retry 라이브러리 활용법까지 정리해봅니다.
Backoff란?
Backoff는 실패한 작업을 즉시 다시 시도하지 않고 일정 시간 대기 후 재시도하는 전략입니다.
지속적인 충돌과 서버 과부하를 방지하기 위해 사용되며, 대표적으로 API 재시도, 락 획득 충돌 해결 등에 활용됩니다.
Backoff의 종류
1. Fixed Backoff (고정 대기)
• 항상 고정된 시간만큼 대기 후 재시도
• 구현이 간단하지만, 동시에 많은 요청이 실패한 경우 충돌이 계속될 수 있음
500ms → 500ms → 500ms …
2. Exponential Backoff (지수적 증가)
• 실패할수록 대기 시간을 2배, 4배로 증가시킴
• 서버 부하를 점진적으로 완화하는 데 효과적
500ms → 1s → 2s → 4s …
3. Exponential Backoff + Jitter (무작위 오차)
• 지수적 증가에 **무작위 지연(jitter)**를 추가하여
동시 재시도 요청이 몰리는 현상 방지
1s ± random → 2s ± random → …
Backoff가 필요한 상황
상황이유
🔐 락 획득 경쟁 | 동시에 여러 요청이 락을 시도할 때 충돌 완화 |
☁️ 외부 API 실패 | 서버 부하, 일시적 장애 시 안정적인 재시도 |
📡 네트워크 지연 | 간헐적 오류일 수 있으므로 효율적 대응 |
Spring Retry로 Backoff 적용하기
Spring에서는 spring-retry 라이브러리를 통해 재시도 + Backoff 전략을 쉽게 구현할 수 있습니다.
의존성 추가
// build.gradle
implementation 'org.springframework.retry:spring-retry'
implementation 'org.springframework.boot:spring-boot-starter-aop'
설정 활성화
@Configuration
@EnableRetry
public class RetryConfig {
}
재시도 메서드에 @Retryable 적용
@Service
public class MyService {
@Retryable(
value = { SomeTemporaryException.class },
maxAttempts = 4,
backoff = @Backoff(delay = 1000, multiplier = 2)
)
public void retryableMethod() {
// 실패 가능성 있는 로직
}
@Recover
public void recover(SomeTemporaryException e) {
System.out.println("모든 재시도 실패 - 복구 로직 실행");
}
}
주요 옵션 정리
옵션설명
maxAttempts | 최대 재시도 횟수 (기본 3, 포함 최초 1회) |
value | 재시도 대상 예외 클래스 |
@Backoff | 딜레이 설정 (기본값: 1000ms) |
multiplier | 딜레이 증가 배수 (지수적 Backoff 구성 가능) |
random = true | 무작위 지연 추가 (Jitter 효과) |
@Recover | 모든 재시도 실패 시 호출되는 복구 메서드 |
예시: Redisson 락 획득 재시도
@Retryable(value = { LockExistException.class }, maxAttempts = 5, backoff = @Backoff(delay = 300, multiplier = 2.0))
public void assignTaskWithLock() {
// 락 획득 후 처리 로직
}
주의할 점
• @Retryable은 프록시 기반 AOP이므로, 자기 자신 클래스 내부에서 호출하면 적용되지 않음
• @Transactional과 함께 사용할 때는 트랜잭션 경계와 순서를 신경 써야 함
• 기본적으로 동기 재시도 방식이며, 비동기 작업에는 추가 구성이 필요함
마무리
Backoff는 단순히 “잠깐 기다렸다 다시 시도하는 것”처럼 보이지만, 시스템의 안정성과 성능 최적화를 위해 매우 중요한 전략입니다.
Spring Retry를 활용하면 재시도 로직을 훨씬 더 쉽고 안정적으로 구성할 수 있으니, 꼭 한 번 적용해보시길 추천드립니다.
다음 글에서는 락 대기 큐나 Redis 기반 재시도 구조에 대해서도 이어서 정리해보겠습니다!