[Common][Feat] 비동기 작업(Async) 모니터링 구축 및 전략 수립#205
Conversation
Summary of ChangesHello @sunwon12, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 이 PR은 Java 21 가상 스레드 환경에서 비동기 작업의 성능을 정밀하게 측정하기 위한 새로운 모니터링 시스템을 도입합니다. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
이 PR은 AOP를 활용하여 비동기 작업 모니터링 시스템을 구축하는 중요한 개선 사항을 도입했습니다. 특히 CompletableFuture를 처리하는 방식이 인상적입니다. 또한, 사용자 데이터를 기반으로 AI를 활용하는 '맞춤형 인사이트' 기능이 새롭게 추가된 점도 흥미롭습니다.
전반적으로 코드는 잘 구조화되어 있으며, 상세한 문서도 포함되어 있습니다. 리뷰 과정에서 몇 가지 개선점을 발견했습니다. 레포지토리 쿼리의 잠재적인 성능 문제, 하드코딩된 값 리팩토링, 그리고 새로 추가된 AI 기능의 비동기 전환을 통한 성능 향상 가능성 등입니다. 전반적으로 훌륭한 기여라고 생각합니다.
| @Query("SELECT rd FROM ReadingDiary rd JOIN FETCH rd.book WHERE rd.member.id = :memberId ORDER BY rd.createdDate DESC") | ||
| List<ReadingDiary> findTop5ByMemberIdOrderByCreatedDateDescWithBook(Long memberId); |
There was a problem hiding this comment.
메서드 이름에 Top5가 포함되어 있지만, @Query 어노테이션을 사용하면 Spring Data JPA가 메서드 이름의 키워드(Top5)를 해석하여 LIMIT 절을 자동으로 추가하지 않습니다. 이로 인해 의도와 다르게 해당 멤버의 모든 독서 일지를 조회하게 되어 성능 저하를 유발할 수 있습니다.
결과를 5개로 제한하려면 Pageable 파라미터를 추가하고, 서비스 레이어에서 PageRequest.of(0, 5)를 전달하는 방식으로 수정하는 것을 권장합니다.
| @Query("SELECT rd FROM ReadingDiary rd JOIN FETCH rd.book WHERE rd.member.id = :memberId ORDER BY rd.createdDate DESC") | |
| List<ReadingDiary> findTop5ByMemberIdOrderByCreatedDateDescWithBook(Long memberId); | |
| @Query("SELECT rd FROM ReadingDiary rd JOIN FETCH rd.book WHERE rd.member.id = :memberId ORDER BY rd.createdDate DESC") | |
| List<ReadingDiary> findTop5ByMemberIdOrderByCreatedDateDescWithBook(Long memberId, org.springframework.data.domain.Pageable pageable); |
| private final QuizService quizService; | ||
| private final GeminiSdkClient geminiSdkClient; | ||
|
|
||
| public InsightResponse getPersonalizedInsight(Long memberId) { |
There was a problem hiding this comment.
getPersonalizedInsight 메서드는 외부 AI 서비스(Gemini)를 호출하는 I/O-bound 작업이므로, 동기 방식으로 실행하면 웹 서버 스레드를 오랫동안 블로킹할 수 있습니다. 이로 인해 전체 애플리케이션의 응답성이 저하될 수 있습니다.
이 PR에서 구현하신 @Async 모니터링의 훌륭한 사용 사례가 될 수 있으므로, 이 메서드를 비동기로 전환하는 것을 강력히 권장합니다.
- 메서드에
@Async어노테이션을 추가합니다. - 반환 타입을
CompletableFuture<InsightResponse>로 변경합니다. geminiSdkClient.generateContentAsync(request)를 호출하여 비동기적으로 AI 응답을 받습니다.
이렇게 변경하면 컨트롤러도 CompletableFuture를 처리하도록 수정해야 하지만, 애플리케이션의 확장성과 성능에 큰 도움이 될 것입니다.
|
|
||
|
|
||
|
|
||
| ## 2. 응답 시간 분포 (점 그래프) |
| if (description != null && description.length() > 150) { | ||
| description = description.substring(0, 150) + "..."; | ||
| } |
There was a problem hiding this comment.
코드 내에 150이라는 매직 넘버가 사용되었습니다. 이 값의 의도를 명확하게 하고 향후 변경을 용이하게 하기 위해, 클래스 레벨에 private static final int MAX_DESCRIPTION_LENGTH = 150;과 같은 상수로 선언하여 사용하는 것을 권장합니다.
| if (description != null && description.length() > 150) { | |
| description = description.substring(0, 150) + "..."; | |
| } | |
| if (description != null && description.length() > MAX_DESCRIPTION_LENGTH) { | |
| description = description.substring(0, MAX_DESCRIPTION_LENGTH) + "..."; | |
| } |
Test Results 99 files 99 suites 22s ⏱️ Results for commit 3a93189. |
🌻 테스트 커버리지 리포트
|
PR: 비동기 작업(Async) 모니터링 구축 및 전략 수립
개요
Java 21 가상 스레드 환경에 맞는 비동기 작업(
@Async) 모니터링 시스템을 구축했습니다.기존의 단순 Executor 감싸기 방식이 가진 한계(메소드 별로 추적 불가능)를 극복하고, 개별 비동기 작업의 **정확한 실행 시간(Latency)**을 추적할 수 있도록 Pure Custom AOP 전략을 적용했습니다.
기존 이슈였던 "짧은 작업의 누락" 문제와 "AI 비동기 응답 대기 시간 추적" 문제를 모두 해결했습니다.
변경 사항
1. Pure Custom AOP 적용 (
AsyncMetricsAspect)TaskExecutor를 Micrometer로 감싸서(Wrapping) 풀 전체의 부하만 확인 가능했음.@Async가 붙은 메서드마다 AOP를 적용하여 기능별로 분리된 정밀 지표 수집.async.execution(Tag:class,method,exception)2. CompletableFuture 정밀 추적 구현
CompletableFuture를 리턴하는 비동기 메서드는 호출 직후(0.n초) 리턴되어, 실제 AI 응답 대기 시간(1분 등)이 누락되는 문제 발생.CompletableFuture인 경우,whenComplete콜백을 등록하여 **"실제 작업이 완료된 시점"**까지 타이머를 유지하도록 개선. 스레드 블로킹 없이 정확한 소요 시간 측정 가능.3. 불필요한 모니터링 제거
LongTaskTimer(Active Threads) 제거: 짧은 작업(0.3s)이 많아 샘플링 한계로 인해 데이터가 부정확하여 제거. Latency(실행 시간) 집중 전략으로 선회.AsyncConfig정리: 불필요해진ExecutorServiceMetrics래핑 코드 삭제. Spring Boot 기본 가상 스레드 설정 활용.4. 문서화
docs/monitor/ASYNC_MONITORING_STRATEGY.md: 채택한 모니터링 전략과 트레이드오프 분석, 동작 원리를 상세 기술.docs/monitor/GRAFANA_GUIDE.md: Grafana 대시보드 구성을 위한 PromQL 및 시각화(점 그래프) 가이드.모니터링 전략 (Grafana)
🔗 관련 문서