Retry Pattern

Retry Pattern은 분산 시스템이나 마이크로서비스 아키텍처에서 일시적인 오류(Transient Failure)를 처리하기 위한 핵심 설계 패턴이다.
네트워크 불안정, 일시적인 서비스 중단 등 일시적인 실패 상황에서 시스템의 복원력(Resilience)을 강화하는 데 목적을 둔다.

이 패턴은 분산 시스템의 안정성을 높이는 필수 도구이지만, 남용할 경우 역효과를 낼 수 있으므로 신중한 정책 수립이 필요하다.

Retry Pattern의 핵심 개념

  1. 작동 원리

    • 실패한 작업 자동 재시도: API 호출, 데이터베이스 접근 등 실패 가능성이 있는 작업을 정의된 정책에 따라 재시도한다.
    • 일시적 오류 감지: 네트워크 타임아웃, HTTP 5xx 에러, 데이터베이스 연결 실패 등 일시적인 오류만 대상으로 한다.
  2. 주요 구성 요소

구성 요소설명예시
재시도 정책 (Retry Policy)어떤 오류를 재시도할지 결정HTTP 503(서비스 불가), SocketException
재시도 간격 (Retry Delay)재시도 사이의 대기 시간고정 간격, 지수 백오프(Exponential Backoff)
최대 재시도 횟수재시도 최대 허용 횟수기본 3회, 시스템 특성에 따라 조정
폴백 메커니즘 (Fallback)최종 실패 시 대체 동작캐시 데이터 반환, 오류 로깅

구현 전략과 사례

  1. 지수 백오프 전략

    • 점진적 대기 시간 증가: 첫 재시도는 1초, 두 번째는 2초, 세 번째는 4초와 같이 대기 시간을 지수적으로 증가시켜 과부하 방지.

    • 예시 코드 (의사 코드):

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      
      def retry_operation():
          max_retries = 3
          base_delay = 1  # 초 단위
          for attempt in range(max_retries):
              try:
                  return perform_operation()
              except TransientError:
                  if attempt == max_retries - 1:
                      raise
                  delay = base_delay * (2 ** attempt)
                  sleep(delay)
      
  2. 라이브러리 활용

    • Spring Retry (Java): @Retryable 어노테이션으로 간편한 재시도 로직 구현:

      1
      2
      3
      4
      
      @Retryable(maxAttempts=3, backoff=@Backoff(delay=1000))
      public void callExternalService() {
          // 외부 서비스 호출
      }
      
    • Polly (.NET): 복합적인 재시도 정책과 서킷 브레이커 패턴 통합 지원.

고려 사항과 주의점

  1. Idempotency(멱등성) 보장

    • 동일 요청의 안전성: 재시도로 인해 중복 처리되지 않도록 POST 요청보다는 GET/PUT/DELETE 등 멱등성 메서드 사용 권장.
    • 예시: 결제 시스템에서 중복 재시도 시 트랜잭션 ID 검증 필수.
  2. 부작용 관리

    • 시스템 과부하 방지: 무분별한 재시도는 서비스 장애 연쇄 반응(Cascading Failure) 유발 가능. 재시도 횟수와 간격을 엄격히 제한.
  3. 다른 패턴과의 조합

    • 서킷 브레이커 패턴: 지속적 실패 시 재시도를 중단하고 장애 전파 차단.
    • 헤징 패턴: 동시에 여러 서비스에 요청을 보내 가장 빠른 응답 선택.

적용 시나리오와 사례

  1. 적합한 사례

    • 클라우드 서비스 간 통신: AWS, Azure에서 발생하는 일시적인 API 오류.
    • 데이터베이스 장애 복구: DB 연결 일시 중단 후 자동 복구.
  2. 비적합한 사례

    • 영구적 오류: 잘못된 요청 파라미터(HTTP 400) 또는 권한 오류(HTTP 403)는 재시도해도 해결되지 않음.

구현 예시

HTTP 클라이언트에서의 재시도:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class HttpClientWithRetry:
    def __init__(self):
        self.retry_strategy = ExponentialBackoffRetry(
            max_attempts=3,
            initial_delay=1
        )
        
    async def make_request(self, url, method="GET", **kwargs):
        async def operation():
            async with aiohttp.ClientSession() as session:
                async with session.request(method, url, **kwargs) as response:
                    if response.status >= 500:
                        raise RetryableError(f"서버 오류: {response.status}")
                    return await response.json()
                    
        return await self.retry_strategy.execute(operation)

데이터베이스 작업에서의 재시도:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class DatabaseRetry:
    def __init__(self):
        self.retry_strategy = ConditionalRetry(
            should_retry_func=self.is_retryable_error,
            max_attempts=3
        )
    
    async def execute_query(self, query, params=None):
        async def operation():
            async with self.pool.acquire() as connection:
                return await connection.fetch(query, params)
                
        return await self.retry_strategy.execute(operation)
        
    def is_retryable_error(self, error):
        return isinstance(error, (
            asyncpg.DeadlockDetectedError,
            asyncpg.ConnectionDoesNotExistError
        ))

참고 및 출처