Skip to content

[Common][Feat] 비동기 작업(Async) 모니터링 구축 및 전략 수립#205

Merged
sunwon12 merged 2 commits intodevfrom
feat/async-monitoring
Feb 1, 2026
Merged

[Common][Feat] 비동기 작업(Async) 모니터링 구축 및 전략 수립#205
sunwon12 merged 2 commits intodevfrom
feat/async-monitoring

Conversation

@sunwon12
Copy link
Contributor

@sunwon12 sunwon12 commented Feb 1, 2026

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)

  • Percentiles: p95, p99 지표를 통해 꼬리 지연(Tail Latency) 모니터링.

🔗 관련 문서

@sunwon12 sunwon12 requested a review from AlphaBs February 1, 2026 09:30
@sunwon12 sunwon12 self-assigned this Feb 1, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @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 가상 스레드 환경에서 비동기 작업의 성능을 정밀하게 측정하기 위한 새로운 모니터링 시스템을 도입합니다. @Async 메서드와 CompletableFuture를 반환하는 비동기 작업의 실제 완료 시점까지 실행 시간을 추적하는 Pure Custom AOP 전략을 사용합니다. 또한, 사용자의 독서 일지, 챌린지, 퀴즈 기록을 분석하여 개인화된 통찰력(Mindset 및 Action Plan)을 제공하는 AI 기반의 새로운 기능이 추가되었습니다.

Highlights

  • 비동기 작업 모니터링 시스템 개선: @Async 메서드 모니터링을 위해 Pure Custom AOP를 적용하여 개별 비동기 작업의 정확한 실행 시간(Latency)을 추적하도록 개선했습니다. 특히 CompletableFuture를 반환하는 비동기 작업의 경우 실제 완료 시점까지 타이머를 유지합니다.
  • 불필요한 모니터링 요소 제거: 짧은 작업의 부정확성으로 인해 LongTaskTimer를 제거하고, ExecutorServiceMetrics 래핑 코드를 삭제하여 Latency 중심의 모니터링 전략으로 전환했습니다.
  • AI 기반 맞춤형 인사이트 기능 추가: 사용자의 독서 일지, 챌린지, 퀴즈 기록을 분석하여 개인화된 마인드셋과 액션 플랜을 제공하는 새로운 AI(Gemini) 기반 인사이트 생성 기능을 도입했습니다.
  • 모니터링 전략 및 Grafana 가이드 문서화: 새로운 비동기 모니터링 전략의 트레이드오프 분석, 동작 원리 및 한계점을 상세히 기술한 문서와 Grafana 대시보드 설정 가이드를 추가했습니다.

🧠 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 Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이 PR은 AOP를 활용하여 비동기 작업 모니터링 시스템을 구축하는 중요한 개선 사항을 도입했습니다. 특히 CompletableFuture를 처리하는 방식이 인상적입니다. 또한, 사용자 데이터를 기반으로 AI를 활용하는 '맞춤형 인사이트' 기능이 새롭게 추가된 점도 흥미롭습니다.

전반적으로 코드는 잘 구조화되어 있으며, 상세한 문서도 포함되어 있습니다. 리뷰 과정에서 몇 가지 개선점을 발견했습니다. 레포지토리 쿼리의 잠재적인 성능 문제, 하드코딩된 값 리팩토링, 그리고 새로 추가된 AI 기능의 비동기 전환을 통한 성능 향상 가능성 등입니다. 전반적으로 훌륭한 기여라고 생각합니다.

Comment on lines +24 to +25
@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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

메서드 이름에 Top5가 포함되어 있지만, @Query 어노테이션을 사용하면 Spring Data JPA가 메서드 이름의 키워드(Top5)를 해석하여 LIMIT 절을 자동으로 추가하지 않습니다. 이로 인해 의도와 다르게 해당 멤버의 모든 독서 일지를 조회하게 되어 성능 저하를 유발할 수 있습니다.

결과를 5개로 제한하려면 Pageable 파라미터를 추가하고, 서비스 레이어에서 PageRequest.of(0, 5)를 전달하는 방식으로 수정하는 것을 권장합니다.

Suggested change
@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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

getPersonalizedInsight 메서드는 외부 AI 서비스(Gemini)를 호출하는 I/O-bound 작업이므로, 동기 방식으로 실행하면 웹 서버 스레드를 오랫동안 블로킹할 수 있습니다. 이로 인해 전체 애플리케이션의 응답성이 저하될 수 있습니다.

이 PR에서 구현하신 @Async 모니터링의 훌륭한 사용 사례가 될 수 있으므로, 이 메서드를 비동기로 전환하는 것을 강력히 권장합니다.

  1. 메서드에 @Async 어노테이션을 추가합니다.
  2. 반환 타입을 CompletableFuture<InsightResponse>로 변경합니다.
  3. geminiSdkClient.generateContentAsync(request)를 호출하여 비동기적으로 AI 응답을 받습니다.

이렇게 변경하면 컨트롤러도 CompletableFuture를 처리하도록 수정해야 하지만, 애플리케이션의 확장성과 성능에 큰 도움이 될 것입니다.




## 2. 응답 시간 분포 (점 그래프)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

문서의 섹션 번호가 ## 2.부터 시작하여 일관성이 부족해 보입니다. ## 1.부터 시작하도록 번호를 조정하는 것을 제안합니다.

Comment on lines +117 to +119
if (description != null && description.length() > 150) {
description = description.substring(0, 150) + "...";
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

코드 내에 150이라는 매직 넘버가 사용되었습니다. 이 값의 의도를 명확하게 하고 향후 변경을 용이하게 하기 위해, 클래스 레벨에 private static final int MAX_DESCRIPTION_LENGTH = 150;과 같은 상수로 선언하여 사용하는 것을 권장합니다.

Suggested change
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) + "...";
}

@github-actions
Copy link

github-actions bot commented Feb 1, 2026

Test Results

 99 files   99 suites   22s ⏱️
477 tests 477 ✅ 0 💤 0 ❌
487 runs  487 ✅ 0 💤 0 ❌

Results for commit 3a93189.

@github-actions
Copy link

github-actions bot commented Feb 1, 2026

🌻 테스트 커버리지 리포트

Overall Project 51.93% -1.02% 🍏
Files changed 7.2%

File Coverage
V2ChallengeService.java 94.85% -1.17%
ReadingChallengeRepository.java 60.61% 🍏
ReadingDiaryRepository.java 58.33% 🍏
KnowledgeFusionController.java 42.86% -57.14%
ReadingDiaryService.java 8.07% -4.21%
KnowledgeFusionService.java 6.25% -93.75%

@sunwon12 sunwon12 merged commit c52be9b into dev Feb 1, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant