Feign 장애 대응 구조 및 예외 흐름 문제 해결 - Genie-Uss/genieus GitHub Wiki

🧩 문제 상황 (Problem)

MSA 환경에서 주문 서비스는 재고, 쿠폰, 프로모션 등 외부 서비스와의 FeignClient 기반 통신을 통해 주문 처리를 진행합니다. 이 과정에서 다음과 같은 문제에 직면했습니다:

  • 외부 서비스 장애(503, 타임아웃 등) 시, 빠른 격리와 복구가 필요했으나 기본 구조로는 장애 전파 및 시스템 전체 오류 발생
  • 이를 해결하기 위해 Resilience4j의 서킷브레이커 + 리트라이 조합을 도입했으나,
    • fallbackMethod가 호출된 이후에도 예외가 재시도 대상으로 분류되어 리트라이가 반복
    • FeignClientException(4xx)과 같은 비즈니스 예외에도 무의미한 재시도 발생
  • Fallback 내부에서 예외를 명확하게 던졌음에도, Retry가 이를 감지하지 못하고 예외 흐름이 꼬이는 현상 발생

🔎 원인 분석

  • @Retry는 기본적으로 메서드 내 모든 예외를 재시도 대상으로 처리
  • fallbackMethod에서 던진 예외가 retry.ignoreExceptions에 등록되어 있지 않으면 → 다시 Retry가 동작
  • 결과적으로 서킷브레이커와 리트라이가 fallback을 공유하면서 예외 흐름이 꼬임
  • FeignClientException (4xx)은 비즈니스 실패로 간주되어야 하는데, 무조건 재시도 되는 구조였음

🎯 선택한 해결 방안 (Decision)

✅ Resilience4j 설정 개선

  • 서킷브레이커와 리트라이의 예외 처리를 분리 관리
    • retryExceptions: RetryableException, FeignServerException, CustomServiceUnavailableException
    • ignoreExceptions: FeignClientException, CustomNotFoundException
  • Fallback은 오직 서킷브레이커에서만 적용, @Retry는 fallback 없이 실패 시 그대로 서킷브레이커로 예외 전달

✅ 예외 흐름 명확화

  • FallbackMethod에서는 RuntimeException 대신 **의미 있는 커스텀 예외(ProductServiceFailureException)**를 명확하게 던짐
  • 예외 타입에 따라 적절한 log level (info, warn, error)로 로깅하여 운영 중 원인 추적 용이
  • FeignClientException에 대해서는 fallback 내부에서도 그대로 throw하여 비즈니스 실패로 처리

🧾 적용 코드 요약

@Retry(name = "productServiceClient")  // 재시도 최대 3회
@CircuitBreaker(name = "productServiceClient", fallbackMethod = "useStockFallback")
public List<ProductClientResponse> useStock(StockRequest request) {
  return productFeignClient.useStock(request);
}

resilience4j:
  retry:
    configs:
      default:
        retry-exceptions:
          - feign.RetryableException
          - feign.FeignException.FeignServerException
        ignore-exceptions:
          - feign.FeignException.FeignClientException
  circuitbreaker:
    configs:
      default:
        record-exceptions:
          - feign.RetryableException
          - feign.FeignException.FeignServerException
        ignore-exceptions:
          - feign.FeignException.FeignClientException


적용 효과 (Outcome)

  • *비즈니스 예외(4xx)**는 재시도 없이 즉시 실패 처리 → API 낭비 방지
  • *서버 장애(5xx, 타임아웃)**는 최대 3회까지 재시도 후 서킷브레이커 fallback 호출 → 장애 전파 방지
  • Fallback 내부의 명확한 커스텀 예외 정의로 전체 흐름이 한눈에 파악 가능해지고, 시스템 안정성 향상
  • 무한 루프 또는 미묘한 예외 누락 등의 사일런트 실패 방지

🔭 향후 개선 방향 (Next Steps)

  • 🔍 Fallback 트리거 시 알림 발송 로직 연동 → Slack, Sentry, PagerDuty 등과 연계 예정
  • 📈 서킷브레이커 상태, 재시도 통계 등 메트릭을 Prometheus로 수집 → Grafana 시각화
  • 🧪 Retry 성능/효율성 튜닝을 위한 실 트래픽 기반 테스트 수행 예정

💬 요약

“Resilience4j의 Retry와 CircuitBreaker를 동시에 사용할 때 예외 흐름 충돌 문제가 발생했지만, 예외 분리 설정과 fallback 구조 분리, 커스텀 예외 정의를 통해 안정적이고 예측 가능한 장애 대응 흐름을 확보했습니다.”