Skip to content
woojin.jang edited this page May 13, 2026 · 4 revisions

Retry

  • Spring Retry는 메소드를 호출해서 예외가 발생 했을 때, 자동으로 지정한 메소드를 다시 호출하는 기능을 제공한다. 일시적인 네트워크 접속 장애가 발생했을 때, Failover으로 유용하다.
  • Failover : 시스템 대체 작동, 평소 사용하는 서버와 그 서버의 클론 서버를 가지고 있다가 사용 서버가 장애로 사용이 어렵게 되었을 경우 클론 서버로 그 일을 대신하게 해서 무정지 시스템을 구축하게 해주는 것을 의미한다.

Retry

@Retryable(
    // 1. 특정 예외 상황(예: 네트워크 타임아웃)에서만 재시도
    includes = {RemoteAccessException.class, ConnectException.class},
    // 2. 최대 재시도 횟수는 시스템 환경에 맞춰 신중히 결정 (보통 3~5회)
    maxRetries = 3,
    // 3. 지수 백오프 전략 사용
    delay = 1000,      // 초기 대기 1초
    multiplier = 2,    // 실패 시 대기 시간 2배 증가 (1s -> 2s -> 4s)
    maxDelay = 10000,  // 최대 대기 시간은 10초로 제한
    jitter = 100       // 100ms 범위 내 무작위성 추가
)
public void callExternalSystem() {
    // 외부 시스템 호출 로직
}

Retry(재시도) 전략

1. 멱등성(Idempotency) 확보

  • 재시도 로직을 적용할 메서드는 반드시 멱등성이 보장되어야 한다.
  • 외부 API 호출이나 DB 쓰기 작업에 재시도를 적용할 때는 유니크 키(Retry-Key) 등을 사용하여 여러 번 호출되어도 결과가 동일하도록 설계해야 한다.

2. 백오프(Back-off) 전략과 지터(Jitter)

  • 네트워크 일시적 장애 상황에서 즉시 재시도를 하는 것은 서비스에 더 큰 부하를 줄 수 있다.
  • Exponential Back-off : 실패할 때마다 대기 시간을 배수로 늘려 시스템이 회복할 시간을 벌어준다.
  • Jitter : 모든 서버가 정확히 똑같은 시간 후에 재시도하면 다시 충돌이 발생할 수 있다. 대기 시간에 약간의 무작위 변수를 더해 재시도 시점을 분산시키는 방법이다.

3. 재시도 대상 예외의 선별

  • 모든 예외에 대해 재시도하는 것은 자원 낭비이다.
  • 재시도 가능 : 네트워크 타임아웃, 503 Service Unavilable, 낙관적 락 충돌
  • 재시도 불필요 : 400 Bad Request, 401 Unauthorized, 비즈니스 로직 에러는 다시 시도해도 결과가 바뀌지 않는다.

동시성 제한(Concurrency Limit)

// 1. 단순 재시도: 모든 예외에 대해 최대 3회 시도
@Retryable
public void sendSimple() {
    this.jmsClient.destination("notifications").send(...);
}

// 2. 특정 예외 지정: 배달 예외 발생 시에만 최대 4회(총 5회) 재시도
@Retryable(MessageDeliveryException.class)
public void sendWithException() {
    this.jmsClient.destination("notifications").send(...);
}

// 3. 정석적인 지수 백오프 전략 (Best Practice)
@Retryable(
    includes = MessageDeliveryException.class,
    maxRetries = 4,      // 최대 4번 더 시도 (총 5번 실행)
    delay = 100,         // 초기 지연 100ms
    multiplier = 2,      // 실패할 때마다 대기 시간 2배 증가 (100 -> 200 -> 400...)
    maxDelay = 1000,     // 최대 대기 시간은 1초로 제한
    jitter = 10          // 대기 시간에 무작위성(10ms)을 추가해 병목 방지
)
public void sendNotification() {
    this.jmsClient.destination("notifications").send(...);
}

1. 빠른 실패(Fail-fast)와 시스템 보호

  • 임계치를 넘는 요청이 들어왔을 때, 무한정 대기시키는 것은 전체 시스템의 쓰레드 고갈로 이어질 수 있다.
  • 내부 자원이 감당할 수 있는 한계치를 @ConcurrencyLimit으로 설정하여 시스템이 마비되기 전에 요청을 차단하거나 대기열을 관리해야 한다. 이를 Bulkhead 패턴의 핵심이 된다.

2. 외부 리소스 고려(Rate Limiting)

  • 자사 시스템에서 호출하는 외부 API나 공유 자원은 무한한 개념이 아니다.
  • 외부 리소스의 스펙에 맞춰 @ConcurrencyLimit을 설정함으로써 외부 시스템에 과부하를 주지 않아야 한다.

3. 스레드 풀 vs 동시성 제한

  • 쓰레드 풀 : 작업을 비동기로 처리하기 위해 미리 쓰레드를 할당하는 방식이다.
  • 동시성 제한 : 호출하는 쪽의 쓰레드를 그대로 사용하되, 동시에 진입하는 수를 체크한다.

📖 Java

📖 Kotlin

📖 Coroutine

📖 Spring

📖 Spring Security

📖 Spring Batch

📖 Reactive Programming

📖 Database

📖 MySQL

📖 Redis

📖 JPA

📖 QueryDsl

📖 MSA

📖 Kafka

📖 Apache Flink

  • [Apache Flink - Apache Flink Architecture]
  • [Apache Flink - Stream Processing]
  • [Apache Flink - Data Stream API & Window]
  • [Apache Flink - State Management]

📖 HTTP

📖 AWS

📖 Docker

📖 Kubernetes

📖 CI/CD

📖 Nginx

📖 Monitoring🥈

  • [Monitoring - Log Concept]
  • [Monitoring - Log Level & Filter]
  • [Monitoring - Logback]
  • [Monitoring - Log Collection with ELK Stack]
  • [Monitoring - Log Monitoring with Kibana]
  • [Monitoring - Building a Monitoring System with Spring Boot Actuator]
  • [Monitoring - Server Monitoring with Prometheus and Grafana with Discord Alerts]

📖 Test

📖 Effective Java 3/E

📖 Kotlin Academy - Effective Kotlin

📖 Kotlin Academy - 핵심편

📖 스프링으로 시작하는 리액티브 프로그래밍

📖 가상 면접 사례로 배우는 대규모 시스템 설계 기초 1

📖 가상 면접 사례로 배우는 대규모 시스템 설계 기초 2

📖 Clean Code

📖 리팩토링 2판

📖 주니어 백엔드 개발자가 반드시 알아야 할 실무 지식

📖 GraphQL

Clone this wiki locally