Feat/414 : Ai피드백 Resilence4j를 활용한 CB,Retry 적용#415
Conversation
|
Warning Rate limit exceeded@HeeMang-Lee has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 22 minutes and 26 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (3)
Walkthroughresilience4j 의존성과 구성(properties)이 추가되고 AiResilience 컴포넌트가 새로 도입됨. OpenAi/Claude 클라이언트들이 생성자로 AiResilience를 주입받아 동기/스트림 호출을 각각 executeSync/executeStream로 래핑하도록 호출 흐름이 변경됨. Changes
Sequence Diagram(s)sequenceDiagram
participant Service as 호출자 (서비스 레이어)
participant Client as OpenAi/Claude Client
participant Res as AiResilience
participant CB as CircuitBreaker
participant Retry as Retry
participant LLM as Underlying ChatClient
Service->>Client: call(prompt)
Client->>Res: executeSync("openai|claude", supplier)
Res->>Retry: decorate supplier with Retry
Res->>CB: decorate with CircuitBreaker
Res->>LLM: invoke decorated supplier
LLM-->>Res: response
Res-->>Client: result
Client-->>Service: return
sequenceDiagram
participant Service as 호출자
participant Client as OpenAi/Claude Client
participant Res as AiResilience
participant LLM as Underlying ChatClient (Flux)
participant RetryOp as RetryOperator
participant CBOp as CircuitBreakerOperator
Service->>Client: stream(prompt)
Client->>Res: executeStream("openai|claude", supplier)
Res->>LLM: supplier.get() -> Flux
Res->>RetryOp: apply RetryOperator
Res->>CBOp: apply CircuitBreakerOperator
LLM-->>Service: Flux events (onNext/onError)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
cs25-service/build.gradle (1)
36-41: Resilience4j 의존성 추가는 적절합니다. Starter 중복 여부와 Micrometer 바인더 포함 여부만 확인 부탁드립니다.
resilience4j-spring-boot3가 다수 모듈을 transitive로 끌어오므로, 개별 모듈(circuitbreaker,retry,reactor) 선언이 불필요할 수 있습니다. 실제 중복/중복제거 가능 여부 확인을 권장합니다.- Actuator + Micrometer(프로메테우스 레지스트리) 사용 중이므로 Resilience4j 메트릭 노출이 자동으로 되는지 확인해 주세요. 필요 시
resilience4j-micrometer의존성이 추가로 요구될 수 있습니다.검증 팁:
- Gradle 의존성 트리 확인:
./gradlew :cs25-service:dependencies --configuration runtimeClasspath | rg resilience4j -n -C2- 메트릭 노출 확인: 애플리케이션 구동 후
/actuator/metrics에서resilience4j.circuitbreaker.calls등 지표가 보이는지 점검cs25-service/src/main/java/com/example/cs25service/domain/ai/resilience/AiResilience.java (2)
21-32: 동기 데코레이터 순서 주석의 의미를 더 명확히 해주세요.현재 구현은 Retry로 감싼 Supplier를 다시 CircuitBreaker로 감싸는 형태입니다(결과적으로 CB가 바깥, Retry가 안쪽). 주석의 "Retry → CircuitBreaker"가 의도(= CB가 재시도들의 외피가 아닌, 재시도 단위 내에서만 동작해야 한다/말아야 한다)를 혼동 없이 전달하도록 보완하면 유지보수에 도움이 됩니다.
예: "Retry로 supplier를 먼저 감싸고, 그 뒤에 CircuitBreaker로 최종 래핑(= CB가 Retry 전체를 외부에서 감시)" 같은 식으로 설명을 추가.
37-44: Flux.defer로 supplier 조립 시 발생 가능한 예외까지 보호하세요.지금은
supplier.get()을 즉시 호출한 뒤 오퍼레이터를 적용합니다. 만약 조립 시점(assembly)에서 예외가 던져지면 Retry/CB에 포착되지 않을 수 있습니다.Flux.defer(supplier)를 사용해 구독 시점(subscription)까지 지연하면, Resilience4j 오퍼레이터가 모든 예외를 안정적으로 처리합니다.아래와 같이 수정 제안드립니다.
- return supplier.get() - .transformDeferred(RetryOperator.of(retry)) - .transformDeferred(CircuitBreakerOperator.of(cb)); + return Flux.defer(supplier) + .transformDeferred(RetryOperator.of(retry)) + .transformDeferred(CircuitBreakerOperator.of(cb));cs25-service/src/main/resources/application.properties (1)
76-87: CircuitBreaker 기본값 재검토 제안: Open 상태 30분은 과도할 수 있습니다.
waitDurationInOpenState=30m은 사용자 대화형 시나리오에서 복구 지연이 큽니다. 1~5분 수준부터 시작해 트래픽/장애 패턴에 맞춰 조정하는 것을 권장합니다.permittedNumberOfCallsInHalfOpenState=1역시 복구 판단에 노이즈가 있을 수 있으므로 3~10 범위에서 검토를 권장합니다.원하시면 워크로드 가정(요청량/에러율) 기반으로 권장 파라미터 계산식/시뮬레이션 스크립트를 제공하겠습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
cs25-service/build.gradle(1 hunks)cs25-service/src/main/java/com/example/cs25service/domain/ai/client/ClaudeChatClient.java(3 hunks)cs25-service/src/main/java/com/example/cs25service/domain/ai/client/OpenAiChatClient.java(3 hunks)cs25-service/src/main/java/com/example/cs25service/domain/ai/resilience/AiResilience.java(1 hunks)cs25-service/src/main/resources/application.properties(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
cs25-service/src/main/java/com/example/cs25service/domain/ai/resilience/AiResilience.java (2)
cs25-service/src/main/java/com/example/cs25service/domain/ai/client/ClaudeChatClient.java (1)
Component(11-52)cs25-service/src/main/java/com/example/cs25service/domain/ai/client/OpenAiChatClient.java (1)
Component(11-54)
🔇 Additional comments (4)
cs25-service/src/main/java/com/example/cs25service/domain/ai/client/OpenAiChatClient.java (2)
17-22: Resilience 주입 및 필드 추가 구조 좋습니다.생성자 주입과 불변 필드로 컴포넌트 설계가 명확합니다.
26-33: 동기 호출 경로에 Resilience 적용 LGTM.
.call().content().trim()을executeSync로 감싼 흐름이 자연스럽고, 구성과 이름 매핑도openai인스턴스와 일치합니다.cs25-service/src/main/java/com/example/cs25service/domain/ai/client/ClaudeChatClient.java (2)
17-21: Resilience 주입 및 필드 추가 구조 좋습니다.OpenAI 클라이언트와 대칭적인 설계로 일관성이 좋습니다.
25-31: 동기 호출 경로에 Resilience 적용 LGTM.
claude인스턴스명과 구성의 매핑이 명확합니다.
cs25-service/src/main/java/com/example/cs25service/domain/ai/client/ClaudeChatClient.java
Outdated
Show resolved
Hide resolved
cs25-service/src/main/java/com/example/cs25service/domain/ai/client/OpenAiChatClient.java
Outdated
Show resolved
Hide resolved
| resilience4j.retry.configs.default.maxAttempts=2 | ||
| resilience4j.retry.configs.default.waitDuration=200ms | ||
| resilience4j.retry.configs.default.enableExponentialBackoff=true | ||
| resilience4j.retry.configs.default.exponentialBackoffMultiplier=2 | ||
| resilience4j.retry.instances.openai.baseConfig=default | ||
| resilience4j.retry.instances.claude.baseConfig=default |
There was a problem hiding this comment.
🛠️ Refactor suggestion
4xx 클라이언트 오류는 Retry/CB 집계에서 제외하는 것을 강력 권장합니다.
현재 스트림 경로에서 오류가 AiException으로 래핑되어 올라오면 예외 타입 기반 필터링이 어려워집니다(아래 클라이언트 파일 코멘트의 리팩터를 먼저 적용 권장). 그 후 아래와 같이 4xx를 무시하도록 설정하면 불필요한 재시도/CB 오픈을 줄일 수 있습니다. 429(레이트리밋)는 유지해 재시도 대상으로 남기는 편이 좋습니다.
아래 설정 추가를 제안드립니다(예: default config에 4xx 무시):
resilience4j.retry.configs.default.maxAttempts=2
resilience4j.retry.configs.default.waitDuration=200ms
resilience4j.retry.configs.default.enableExponentialBackoff=true
resilience4j.retry.configs.default.exponentialBackoffMultiplier=2
+resilience4j.retry.configs.default.ignoreExceptions[0]=org.springframework.web.reactive.function.client.WebClientResponseException$BadRequest
+resilience4j.retry.configs.default.ignoreExceptions[1]=org.springframework.web.reactive.function.client.WebClientResponseException$Unauthorized
+resilience4j.retry.configs.default.ignoreExceptions[2]=org.springframework.web.reactive.function.client.WebClientResponseException$Forbidden
+resilience4j.retry.configs.default.ignoreExceptions[3]=org.springframework.web.reactive.function.client.WebClientResponseException$NotFound
resilience4j.retry.instances.openai.baseConfig=default
resilience4j.retry.instances.claude.baseConfig=defaultCircuitBreaker 쪽도 동일하게 4xx를 무시하도록 설정하면 더 안정적입니다.
resilience4j.circuitbreaker.configs.default.minimumNumberOfCalls=6
resilience4j.circuitbreaker.configs.default.waitDurationInOpenState=30m
resilience4j.circuitbreaker.configs.default.permittedNumberOfCallsInHalfOpenState=1
resilience4j.circuitbreaker.configs.default.automaticTransitionFromOpenToHalfOpenEnabled=true
+resilience4j.circuitbreaker.configs.default.ignoreExceptions[0]=org.springframework.web.reactive.function.client.WebClientResponseException$BadRequest
+resilience4j.circuitbreaker.configs.default.ignoreExceptions[1]=org.springframework.web.reactive.function.client.WebClientResponseException$Unauthorized
+resilience4j.circuitbreaker.configs.default.ignoreExceptions[2]=org.springframework.web.reactive.function.client.WebClientResponseException$Forbidden
+resilience4j.circuitbreaker.configs.default.ignoreExceptions[3]=org.springframework.web.reactive.function.client.WebClientResponseException$NotFound참고: 이 설정이 효과를 내기 위해서는 아래 클라이언트 코드에서 onErrorResume로 즉시 AiException을 던지던 부분을 Resilience4j 적용 “이후”로 이동해야 합니다(원인 유지).
🤖 Prompt for AI Agents
In cs25-service/src/main/resources/application.properties around lines 89 to 94,
the Retry (and CircuitBreaker) configuration currently retries on all errors;
add configuration to exclude 4xx client errors from retry and CB evaluation
(keep 429 as retryable) by updating the default retry/circuit breaker configs to
ignore HTTP 4xx status codes (or exceptions that wrap 4xx) and add a specific
rule to treat 429 as retryable; also ensure the client code change (move the
onErrorResume that wraps/throws AiException) is applied so that the original
HTTP status-based exception is visible to Resilience4j (i.e., stop wrapping into
AiException before resilience filters run), allowing the new ignore-4xx rules to
take effect.
🔎 작업 내용
Summary by CodeRabbit