좋은 질문이에요. 한 줄 정의부터 찍고, 왜/언제 필요한지 빠르게 정리할게요.
| 리스크 | Retry | CircuitBreaker | TimeLimiter | RateLimiter |
|---|---|---|---|---|
| 지연(느려터짐) | ✖️(오히려 더 지연될 수 있음) | △(연속 실패시 차단) | ✅(상한시간 넘기면 즉시 실패) | ✖️ |
| 무한대기/커넥션 행 | ✖️ | ✖️ | ✅ | ✖️ |
| 429/쿼터 초과 | △(재시도는 오히려 악화) | ✖️ | ✖️ | ✅(미리 속도 제한) |
| 급격한 폭주/스레드 고갈 | ✖️ | △(장애 구간 차단) | ✅(빨리 끊어줌) | ✅(진입량 제한) |
기본: Retry + CircuitBreaker (일시 오류·연속 장애 대비)
추가로 할 수 있는 것:
결론: Retry+CircuitBreaker만으로는 “느림/429/쿼터” 리스크가 남는다.
실무에서는 RateLimiter (필수에 가깝게), TimeLimiter (대부분 권장)를 함께 쓰는 것 같다.
resilience4j:
circuitbreaker:
instances:
openai:
slidingWindowType: COUNT_BASED
slidingWindowSize: 20
failureRateThreshold: 50
waitDurationInOpenState: 30s
permittedNumberOfCallsInHalfOpenState: 3
retry:
instances:
openai:
maxAttempts: 3
waitDuration: 500ms
retryExceptions:
- org.springframework.web.client.HttpServerErrorException
- org.springframework.web.client.ResourceAccessException
timelimiter:
instances:
openai:
timeoutDuration: 5s # 5초 넘으면 실패로 간주
ratelimiter:
instances:
openai:
limitForPeriod: 5 # 초당 5회
limitRefreshPeriod: 1s
timeoutDuration: 0 # 대기하지 않고 즉시 실패
애너테이션 적용 예시:
@CircuitBreaker(name = "openai", fallbackMethod = "fallback")
@Retry(name = "openai")
@RateLimiter(name = "openai") // 429/쿼터 예방
//@TimeLimiter(name = "openai") // 비동기(CompletableFuture)로 바꿀 수 있으면 활성
public AiCallResponseDto generateMenuDescription(...) { ... }
@TimeLimiter는 비동기 리턴 타입(CompletableFuture 등)일 때 사용이 깔끔하다. 동기라면 클라이언트 레벨(HTTP 타임아웃)도 함께 설정해야 한다.요약: 프로덕션 외부 API 호출이면 Retry + CircuitBreaker + RateLimiter (+ TimeLimiter) 조합이 표준에 가깝다. 특히 OpenAI는 429/쿼터가 현실적 이슈라 RateLimiter는 사실상 필수이다.