Skip to content

Conversation

@anxi01
Copy link
Member

@anxi01 anxi01 commented Jan 8, 2026

🚀 Why - 해결하려는 문제가 무엇인가요?

스크린샷 2026-01-08 오후 11 12 37

출발지 수가 6개 이상 + 중간지점 수 2개 이상일 경우, 1개의 중간지점의 경로 조회가 대부분 실패합니다.
이 경우를 대처하기 위해 작업을 진행했습니다.

✅ What - 무엇이 변경됐나요?

  • 기존 Platform Thread에서 Virtual Thread로 변경
  • get + try-catch에서 join으로 변경
  • 경로 못가져올 경우, 1번 재시도하는 로직 추가

🛠️ How - 어떻게 해결했나요?

  • 재시도는 첫시도 포함 2번이고 재시도 간격은 0.1초 (100ms)입니다. 보수적으로 잡았으며 100ms로 했을 때 중간지점 3개 + 출발지 8개에서 경로를 다 가져올 수 있었습니다. 추후 경과를 보고 값 변경 진행 필요합니다.
  • Platform Thread는 sleep 시 block 하여 OS 자원을 점유하지만 Virtual Thread는 sleep 시 block이 아닌 unmount를 통해 다른 Virtual Thread를 실행할 수 있습니다. 따라서 Virtual Thread로 변경했습니다. (참고)

🖼️ Attachment

💬 기타 코멘트

  • 스테이징 서버에서 충분한 테스트 진행 후 릴리즈 예정입니다.

Summary by CodeRabbit

  • 새로운 기능

    • 경로 조회에 재시도 메커니즘 추가로 실패 시 자동 재시도 지원
  • 성능 개선

    • 가상 스레드 기반 실행 환경 도입으로 경로 처리 효율성 향상
  • 개선 사항

    • 경로 응답 유효성 검증 로직 추가로 더 안정적인 결과 선별
  • 수정

    • 이벤트 캐시 검사 흐름이 변경되어 관련 예외 처리 방식이 조정됨

✏️ Tip: You can customize this high-level summary in your review settings.

@anxi01 anxi01 self-assigned this Jan 8, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 8, 2026

Walkthrough

이벤트 캐시 존재 검증 메서드를 제거하고, RouteAssembler의 라우트 조회를 가상 스레드 기반 공유 Executor로 전환해 재시도 로직(fetchWithRetry)과 유효성 검사(isValid)를 도입했습니다. 새로운 ThreadConfig에서 routeExecutor 빈을 등록합니다.

Changes

Cohort / File(s) 변경 내용
EventValidator 메서드 제거
src/main/java/com/meetup/server/event/implement/EventValidator.java
공개 메서드 validateEventCacheExist(Cache.ValueWrapper eventCache) 삭제, 관련 null 검증 및 예외 경로 제거
RouteAssembler 리팩토링
src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java
인스턴스 필드 routeExecutor 추가(생성자 파라미터화), per-call 쓰레드풀 제거, fetchWithRetry(startPoint, subway) 재시도 로직 추가, isValid(RouteResponse) 유효성 검사 추가, CompletableFuture.join 기반 집계로 비동기 흐름 변경, null 처리 및 로그 경로 추가
스레드 설정 추가
src/main/java/com/meetup/server/global/config/ThreadConfig.java
Project Loom 가상 스레드 기반 ExecutorService Bean (routeExecutor) 추가 및 스프링 빈 등록

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

⚡️ perf, ✨ feat, ♻️ refactor

Suggested reviewers

  • syjdjr
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 Virtual Thread 전환 및 경로 조회 재시도 로직 추가라는 주요 변경사항을 명확하게 요약하고 있으며, 변경 내용과 일치한다.
Description check ✅ Passed PR 설명이 템플릿 구조를 따르며 문제, 변경사항, 해결 방법을 모두 포함하고 있으나 일부 섹션이 미흡하다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b06920c and ce19596.

📒 Files selected for processing (1)
  • src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: anxi01
Repo: Team-MOISAM/moisam-server PR: 160
File: src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java:25-34
Timestamp: 2025-09-28T08:27:38.024Z
Learning: MOISAM 프로젝트의 외부 API(Odsay Transit, Kakao Mobility)는 동시 호출 제한이 있어서 3개 이상의 스레드로 병렬 요청 시 일부 경로 조회가 실패함. 스테이징 테스트 결과 2개 스레드가 안정적으로 동작하는 최적값으로 확인됨.
Learnt from: anxi01
Repo: Team-MOISAM/moisam-server PR: 160
File: src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java:30-35
Timestamp: 2025-09-25T13:46:49.071Z
Learning: MOISAM 프로젝트에서 RouteProcessor + RouteAssembler의 중첩 Virtual Thread 사용은 EventValidator의 최대 출발지 8개 제한과 중간지점 알고리즘 특성으로 최대 24-32개 동시 API 호출로 제한되며, RateLimiter 인프라와 캐싱 전략으로 적절히 관리됨.
📚 Learning: 2025-10-11T14:28:08.219Z
Learnt from: syjdjr
Repo: Team-MOISAM/moisam-server PR: 162
File: src/main/java/com/meetup/server/event/application/EventService.java:68-87
Timestamp: 2025-10-11T14:28:08.219Z
Learning: In the moisam-server codebase, the EventService.getMeetingPointRoutes method uses a ReentrantLock via EventLockManager that should block and wait (using lock.lock()) rather than using tryLock with timeout. The 409 error handling for concurrent access applies to StartPointService but not to this particular lock in EventService.

Applied to files:

  • src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java
📚 Learning: 2025-09-28T08:19:04.426Z
Learnt from: anxi01
Repo: Team-MOISAM/moisam-server PR: 160
File: src/main/java/com/meetup/server/event/implement/route/RouteProcessor.java:32-55
Timestamp: 2025-09-28T08:19:04.426Z
Learning: RouteAssembler.assemble() 메서드는 예외가 발생해도 CompletableFuture가 exceptionally 완료되지 않고, 내부에서 예외를 catch하여 null로 변환하여 정상 완료됨. 따라서 CompletableFuture.allOf()를 사용해도 개별 assemble 실패가 전체 처리를 중단시키지 않음.

Applied to files:

  • src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java
📚 Learning: 2025-09-25T13:46:49.071Z
Learnt from: anxi01
Repo: Team-MOISAM/moisam-server PR: 160
File: src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java:30-35
Timestamp: 2025-09-25T13:46:49.071Z
Learning: MOISAM 프로젝트에서 RouteProcessor + RouteAssembler의 중첩 Virtual Thread 사용은 EventValidator의 최대 출발지 8개 제한과 중간지점 알고리즘 특성으로 최대 24-32개 동시 API 호출로 제한되며, RateLimiter 인프라와 캐싱 전략으로 적절히 관리됨.

Applied to files:

  • src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java
📚 Learning: 2025-09-28T08:19:04.426Z
Learnt from: anxi01
Repo: Team-MOISAM/moisam-server PR: 160
File: src/main/java/com/meetup/server/event/implement/route/RouteProcessor.java:32-55
Timestamp: 2025-09-28T08:19:04.426Z
Learning: RouteFetcher.fetch() 메서드는 개별 API 호출(fetchTransitRoute, fetchDrivingRoute)에서 예외를 catch하여 null로 변환하므로, 전체 메서드가 정상적으로 RouteResponse를 반환함. 따라서 상위의 CompletableFuture 체인에서 exceptionally 완료되지 않고 정상 완료됨.

Applied to files:

  • src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java
📚 Learning: 2025-12-25T11:50:19.070Z
Learnt from: anxi01
Repo: Team-MOISAM/moisam-server PR: 170
File: src/main/java/com/meetup/server/admin/application/AdminService.java:70-76
Timestamp: 2025-12-25T11:50:19.070Z
Learning: In the MOISAM project, the server and database operate in the Asia/Seoul timezone, and the service is domestic. Therefore, using LocalDate.now() or LocalDateTime.now() without an explicit ZoneId is acceptable for this codebase. Apply this guideline to Java files under src/main/java, but remain vigilant: for time-sensitive data or if future maintenance requires cross-timezone handling, prefer using ZoneId or ZonedDateTime with an explicitZone.

Applied to files:

  • src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java
🧬 Code graph analysis (1)
src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java (1)
src/main/java/com/meetup/server/event/exception/EventException.java (1)
  • EventException (6-11)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (4)
src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java (4)

33-54: LGTM!

allOf + join 패턴을 사용한 결과 수집과 병렬로 주차장 조회를 실행하는 구조가 적절합니다. null 필터링으로 실패한 경로를 제외하고 thenCombine으로 결과를 조합하는 방식이 깔끔합니다.


82-86: LGTM!

isTransit 플래그에 따라 해당 경로 정보의 존재 여부를 검증하는 로직이 명확합니다.


56-80: 재시도 로직 및 예외 처리 적절

InterruptedException 발생 시 인터럽트 플래그를 복원하고 예외를 던지는 처리가 올바릅니다. Virtual Thread 환경에서 Thread.sleep()은 unmount되어 효율적으로 동작합니다.

모든 재시도 실패 후 Line 79에서 반환하는 routeRouteResponse 객체이며(null이 아님), 항상 출발지 정보(startName, startLongitude, startLatitude)를 포함합니다. 다만 내부의 transitRoute 또는 drivingRoute 필드는 null일 수 있으며, isValid() 메서드가 이를 명시적으로 검증하므로 설계가 의도된 것으로 보입니다.

최종 상태: 
- RouteFetcher.fetch()는 API 호출 실패 시 경로를 null로 반환하되, 항상 valid한 RouteResponse 객체 반환
- RouteAssembler.fetchWithRetry()는 재시도 소진 후에도 startPoint 정보를 포함한 RouteResponse 반환
- isValid()가 경로 필드의 null 여부를 확인하는 방식이 적절함

26-31: Virtual Thread 기반 Executor로 인한 동시 API 호출 증가 주의 필요

newThreadPerTaskExecutor()를 통해 생성되는 Virtual Thread Executor는 동시성 제한이 없습니다. 과거 학습에서 언급된 대로 외부 API(Odsay Transit, Kakao Mobility)는 3개 이상의 스레드로 병렬 요청 시 일부 경로 조회가 실패하므로, 최대 8개 출발지 × 병렬 실행의 중첩으로 인한 동시 API 호출 증가가 우려됩니다.

현재 구조에서 RedisRateLimiter는 일일 API 호출 한도만 관리하며, 실행 시점의 동시성 제어는 이루어지지 않습니다. Virtual Thread 기반 Executor 도입 시 추가적인 동시성 제어 메커니즘(Semaphore 등)의 도입을 검토하거나, API 호출을 제한하는 구체적인 전략이 필요합니다.

⛔ Skipped due to learnings
Learnt from: anxi01
Repo: Team-MOISAM/moisam-server PR: 160
File: src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java:30-35
Timestamp: 2025-09-25T13:46:49.071Z
Learning: MOISAM 프로젝트에서 RouteProcessor + RouteAssembler의 중첩 Virtual Thread 사용은 EventValidator의 최대 출발지 8개 제한과 중간지점 알고리즘 특성으로 최대 24-32개 동시 API 호출로 제한되며, RateLimiter 인프라와 캐싱 전략으로 적절히 관리됨.
Learnt from: anxi01
Repo: Team-MOISAM/moisam-server PR: 160
File: src/main/java/com/meetup/server/event/implement/route/RouteProcessor.java:32-38
Timestamp: 2025-09-25T13:43:29.955Z
Learning: RouteProcessor.buildRouteGroups() 메서드에서 중간지점역 리스트 수 × 출발지 수만큼 동시 API 호출이 발생하여 외부 API 레이트 리밋에 도달할 수 있는 중첩 병렬화 문제가 있음.
Learnt from: anxi01
Repo: Team-MOISAM/moisam-server PR: 160
File: src/main/java/com/meetup/server/event/implement/route/RouteProcessor.java:32-55
Timestamp: 2025-09-28T08:19:04.426Z
Learning: RouteAssembler.assemble() 메서드는 예외가 발생해도 CompletableFuture가 exceptionally 완료되지 않고, 내부에서 예외를 catch하여 null로 변환하여 정상 완료됨. 따라서 CompletableFuture.allOf()를 사용해도 개별 assemble 실패가 전체 처리를 중단시키지 않음.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java (1)

56-73: 마지막 시도 실패 시 로그 추가 고려

현재 구현에서는 재시도 전에만 로그가 남고, 모든 시도가 실패하여 null을 반환할 때는 로그가 없습니다. 디버깅을 위해 최종 실패 시에도 로그를 남기는 것을 권장합니다.

♻️ Proposed fix
     private RouteResponse fetchWithRetry(StartPoint startPoint, Subway subway) {
         for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
             RouteResponse route = routeFetcher.fetch(startPoint, subway);
             if (isValid(route)) return route;

             if (attempt < MAX_ATTEMPTS - 1) {
                 log.warn("[RouteAssembler] Route fetch failed for {}. Retrying... (Attempt {}/{})",
                         startPoint.getName(), attempt + 1, MAX_ATTEMPTS);
                 try {
                     Thread.sleep(RETRY_DELAY_MS);
                 } catch (InterruptedException e) {
                     Thread.currentThread().interrupt();
                     throw new EventException(EventErrorType.ROUTE_FETCH_FAILED);
                 }
             }
         }
+        log.error("[RouteAssembler] All {} attempts failed for startPoint: {}, subway: {}",
+                MAX_ATTEMPTS, startPoint.getName(), subway.getName());
         return null;
     }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 54f1200 and b06920c.

📒 Files selected for processing (3)
  • src/main/java/com/meetup/server/event/implement/EventValidator.java
  • src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java
  • src/main/java/com/meetup/server/global/config/ThreadConfig.java
💤 Files with no reviewable changes (1)
  • src/main/java/com/meetup/server/event/implement/EventValidator.java
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: anxi01
Repo: Team-MOISAM/moisam-server PR: 160
File: src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java:25-34
Timestamp: 2025-09-28T08:27:38.024Z
Learning: MOISAM 프로젝트의 외부 API(Odsay Transit, Kakao Mobility)는 동시 호출 제한이 있어서 3개 이상의 스레드로 병렬 요청 시 일부 경로 조회가 실패함. 스테이징 테스트 결과 2개 스레드가 안정적으로 동작하는 최적값으로 확인됨.
Learnt from: anxi01
Repo: Team-MOISAM/moisam-server PR: 160
File: src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java:30-35
Timestamp: 2025-09-25T13:46:49.071Z
Learning: MOISAM 프로젝트에서 RouteProcessor + RouteAssembler의 중첩 Virtual Thread 사용은 EventValidator의 최대 출발지 8개 제한과 중간지점 알고리즘 특성으로 최대 24-32개 동시 API 호출로 제한되며, RateLimiter 인프라와 캐싱 전략으로 적절히 관리됨.
Learnt from: anxi01
Repo: Team-MOISAM/moisam-server PR: 160
File: src/main/java/com/meetup/server/event/implement/route/RouteProcessor.java:32-38
Timestamp: 2025-09-25T13:43:29.955Z
Learning: RouteProcessor.buildRouteGroups() 메서드에서 중간지점역 리스트 수 × 출발지 수만큼 동시 API 호출이 발생하여 외부 API 레이트 리밋에 도달할 수 있는 중첩 병렬화 문제가 있음.
📚 Learning: 2025-10-11T14:28:08.219Z
Learnt from: syjdjr
Repo: Team-MOISAM/moisam-server PR: 162
File: src/main/java/com/meetup/server/event/application/EventService.java:68-87
Timestamp: 2025-10-11T14:28:08.219Z
Learning: In the moisam-server codebase, the EventService.getMeetingPointRoutes method uses a ReentrantLock via EventLockManager that should block and wait (using lock.lock()) rather than using tryLock with timeout. The 409 error handling for concurrent access applies to StartPointService but not to this particular lock in EventService.

Applied to files:

  • src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java
📚 Learning: 2025-09-28T08:19:04.426Z
Learnt from: anxi01
Repo: Team-MOISAM/moisam-server PR: 160
File: src/main/java/com/meetup/server/event/implement/route/RouteProcessor.java:32-55
Timestamp: 2025-09-28T08:19:04.426Z
Learning: RouteAssembler.assemble() 메서드는 예외가 발생해도 CompletableFuture가 exceptionally 완료되지 않고, 내부에서 예외를 catch하여 null로 변환하여 정상 완료됨. 따라서 CompletableFuture.allOf()를 사용해도 개별 assemble 실패가 전체 처리를 중단시키지 않음.

Applied to files:

  • src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java
📚 Learning: 2025-09-25T13:46:49.071Z
Learnt from: anxi01
Repo: Team-MOISAM/moisam-server PR: 160
File: src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java:30-35
Timestamp: 2025-09-25T13:46:49.071Z
Learning: MOISAM 프로젝트에서 RouteProcessor + RouteAssembler의 중첩 Virtual Thread 사용은 EventValidator의 최대 출발지 8개 제한과 중간지점 알고리즘 특성으로 최대 24-32개 동시 API 호출로 제한되며, RateLimiter 인프라와 캐싱 전략으로 적절히 관리됨.

Applied to files:

  • src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java
📚 Learning: 2025-09-28T08:19:04.426Z
Learnt from: anxi01
Repo: Team-MOISAM/moisam-server PR: 160
File: src/main/java/com/meetup/server/event/implement/route/RouteProcessor.java:32-55
Timestamp: 2025-09-28T08:19:04.426Z
Learning: RouteFetcher.fetch() 메서드는 개별 API 호출(fetchTransitRoute, fetchDrivingRoute)에서 예외를 catch하여 null로 변환하므로, 전체 메서드가 정상적으로 RouteResponse를 반환함. 따라서 상위의 CompletableFuture 체인에서 exceptionally 완료되지 않고 정상 완료됨.

Applied to files:

  • src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java
📚 Learning: 2025-12-25T11:50:19.070Z
Learnt from: anxi01
Repo: Team-MOISAM/moisam-server PR: 170
File: src/main/java/com/meetup/server/admin/application/AdminService.java:70-76
Timestamp: 2025-12-25T11:50:19.070Z
Learning: In the MOISAM project, the server and database operate in the Asia/Seoul timezone, and the service is domestic. Therefore, using LocalDate.now() or LocalDateTime.now() without an explicit ZoneId is acceptable for this codebase. Apply this guideline to Java files under src/main/java, but remain vigilant: for time-sensitive data or if future maintenance requires cross-timezone handling, prefer using ZoneId or ZonedDateTime with an explicitZone.

Applied to files:

  • src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java
  • src/main/java/com/meetup/server/global/config/ThreadConfig.java
🧬 Code graph analysis (1)
src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java (1)
src/main/java/com/meetup/server/event/exception/EventException.java (1)
  • EventException (6-11)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (4)
src/main/java/com/meetup/server/event/implement/route/RouteAssembler.java (3)

26-31: LGTM!

재시도 상수와 executor 주입이 적절하게 설정되어 있습니다. Virtual Thread는 Thread.sleep() 시 unmount되어 다른 Virtual Thread가 실행될 수 있으므로, 재시도 로직에 적합한 선택입니다.


75-79: LGTM!

isTransit 플래그와 해당 route 존재 여부를 함께 검증하는 로직이 적절합니다. Learning에 따르면 RouteFetcher.fetch()가 예외를 null로 변환하므로 이 유효성 검사가 필수적입니다.


47-53: null 반환은 호출자에서 적절히 처리됨

RouteProcessor.buildRouteGroups()에서 assemble() 호출 결과에 대해 .filter(Objects::nonNull)으로 명시적으로 null을 필터링하고 있습니다(Line 47). 따라서 routes.isEmpty() 케이스의 null 반환은 호출자에서 적절히 처리되고 있습니다.

src/main/java/com/meetup/server/global/config/ThreadConfig.java (1)

13-17: ExecutorService 라이프사이클은 Spring 6.1+에서 자동으로 관리됩니다

현재 프로젝트는 Spring Boot 3.4.4를 사용하고 있으며, 이는 Spring Framework 6.1 이상에 해당합니다. Spring 6.1+에서는 AutoCloseable을 구현하는 빈들을 자동으로 종료하므로, Executors.newThreadPerTaskExecutor()로 생성된 ExecutorService는 별도의 shutdown 처리 없이 자동으로 관리됩니다. Java 21에서 제공되는 Virtual Thread 기반 ExecutorService도 동일하게 작동합니다. 추가 라이프사이클 관리는 필요하지 않습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants