[ Refactor ] - AI 리포트 배치 대상 소득분석 조회 N+1 → 벌크 조회 개선 - #152
Merged
Conversation
added 8 commits
August 14, 2026 10:16
- SettlementMapper 인터페이스에 IN절 기반 벌크 조회 메서드 추가 (findByUserIdInAndDepositDateRange) - XML에 <foreach>로 다건 userId 바인딩 구현 - InMemorySettlementMapper 테스트 페이크에 동일 동작 구현 기존 findByUserIdAndDepositDateRange(단건)은 그대로 유지한다. (타 서비스에서 사용)
- JobMapper 인터페이스에 IN절 기반 벌크 조회 메서드 추가 (findByUserIdIn) - XML에 <foreach>로 다건 userId 바인딩 구현 - InMemoryJobMapper 테스트 페이크에 동일 동작 구현 기존 findByUserId(단건)는 그대로 유지한다. (타 서비스에서 사용)
- WorkLogMapper 인터페이스에 IN절 기반 벌크 조회 메서드 추가 (findByUserIdInAndDateRange) - XML에 <foreach>로 다건 userId 바인딩 구현 - InMemoryWorkLogMapper 테스트 페이크에 동일 동작 구현 기존 findByUserIdAndDateRange(단건)는 그대로 유지한다 (타 서비스에서 사용)
- WorkLogPlatformIncomeMapper 인터페이스에 IN절 기반 벌크 조회 메서드 추가 (findConfirmedByUserIdInAndDateRange) - XML에 <foreach>로 다건 userId 바인딩 구현 (WORK_LOG 조인 조건에 적용) - InMemoryWorkLogPlatformIncomeMapper 테스트 페이크에 동일 동작 구현 이 엔티티에는 userId 필드가 없어 벌크 결과를 사용자별로 그룹핑하려면 같은 기간에 벌크 조회한 WorkLog(logId → userId)를 호출부에서 함께 이용 - IncomeAnalysisService 벌크 메서드 구현 시 처리 예정. 기존 findConfirmedByUserIdAndDateRange(단건)는 그대로 유지한다.
- getMonthlyIncomeAnalysisBulk 추가 (쿼리 6개로 고정, N+1 해결) - aggregateMonth를 DB조회부/순수집계부(aggregate)로 분리 - 단건/벌크가 buildSummary()로 조립 로직 공유 - calculateVolatility 순수 함수화 - findWorkLogsInMonth 중복 호출 제거 - WorkLogPlatformIncome(userId 없음)은 WorkLog logId→userId 매핑으로 그룹핑 - StubSettlementMapper에 신규 메서드 구현 추가
- getMonthlyIncomeAnalysisBulk(List<Long> userIds, YearMonth yearMonth)를 default 메서드로 추가 - 기본 구현은 단건 메서드를 사용자마다 반복 호출하는 폴백이라 N+1을 그대로 가진다 - LocalIncomeAnalysisQueryClient(work-service)는 이 메서드를 오버라이드해서 IncomeAnalysisService.getMonthlyIncomeAnalysisBulk로 위임, 실제 벌크 조회 수행 다른 구현체(diagnosis-service/ai-service)가 이 메서드를 오버라이드하지 않으면 폴백(N+1)으로 계속 동작한다 - 실제 성능 이점을 받으려면 각 소비 주체가 명시적으로 오버라이드해야 한다.
- userIds 빈 리스트 → 빈 Map - 요청한 사용자 전원이 결과에 포함되는지 - 데이터 없는 사용자는 0/빈 값으로 채워지고 null이 아닌지 (단건 경로의 null=조회실패 의미와 다름을 검증) - 두 사용자의 SETTLEMENT/WORK_LOG/WorkLogPlatformIncome이 서로 섞이지 않는지 (WorkLogPlatformIncome은 userId가 없어 logId 매핑을 거치므로 별도 검증) - 벌크 결과가 단건 조회(getMonthlyIncomeAnalysis)와 동일한 값을 내는지
getMonthlyIncomeAnalysis, getMonthlyIncomeAnalysisBulk 성능 비교 테스트 구현 - 벌크 경로는 사용자 수와 무관하게 쿼리 6개 이하로 고정되는지, 루프 경로는 사용자 수 이상 쿼리가 나가는지 검증 - work-service build.gradle에 테스트 전용 DB 연동 의존성 추가 (HikariCP, mysql-connector-java, spring-jdbc/spring-test) 실측 결과: - 사용자 50명: 루프 917ms/쿼리 300회 → 벌크 61ms/쿼리 6회 - 사용자 1000명: 루프 7843ms/쿼리 6000회 → 벌크 192ms/쿼리 6회 (약 40배)
hanjyoon01
requested review from
KSBoscoKim,
Kimd0ng,
SOOsuhyuncho,
bigwaveBigwave and
shinh09
August 14, 2026 02:17
bigwaveBigwave
approved these changes
Aug 14, 2026
shinh09
approved these changes
Aug 14, 2026
MonthlyAiReportOrchestrationService.runBatch가 사용자마다 개별 호출하던 incomeAnalysisQueryClient.getMonthlyIncomeAnalysis를 벌크 메서드 getMonthlyIncomeAnalysisBulk 한 번 호출로 교체한다. - runBatch: 사용자 루프 밖에서 벌크 조회 1회 수행, 실패 시 try/catch로 감싸 빈 Map + 로그로 처리 (사용자 단위 격리가 없는 대신 배치 전체가 막히지 않도록) - processUserReport: 내부에서 직접 조회하던 코드를 지우고 income을 파라미터로 받도록 시그니처 변경 account-service의 monthlyExpenseQueryClient는 이번 변경에 포함되지 않아 배치 쿼리 수는 절반만 개선된다 - 완전 해결은 account-service 벌크화 이후.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
관련 이슈
작업 사항
SettlementMapper/JobMapper/WorkLogMapper/WorkLogPlatformIncomeMapper에 벌크 조회 메서드 추가, 각InMemory*Mapper페이크도 함께 구현IncomeAnalysisService.aggregateMonth를 DB 조회부/순수 집계부로 분리,getMonthlyIncomeAnalysisBulk추가 - 단건 경로와buildSummary()로 조립 로직 공유buildEarnedDepositComparisons/buildFatigueSummaries가findWorkLogsInMonth를 각각 중복 호출하던 부분 제거IncomeAnalysisQueryClient(common)에default메서드로 벌크 조회 계약 추가 - 다른 도메인(diagnosis-service/ai-service)의 기존 구현체·테스트는 변경 없이 그대로 컴파일되도록 함SettlementMapper가 실제 DB에서 언더스코어 컬럼(actual_amount,job_id등)을 매핑하지 못하던 버그 발견 및 수정 (resultMap명시)RUN_INCOME_ANALYSIS_BULK_PERF_TEST=true)실제 비교 결과 (사용자 1000명)

참고 사항
SettlementMapper버그(중요):SELECT *+resultType만 쓰고 명시적resultMap이 없어서,mapUnderscoreToCamelCase설정이 없는 이 프로젝트에서는actual_amount/job_id/period_start등 언더스코어 컬럼이 전부null로 매핑되고 있었습니다.InMemorySettlementMapper기반 유닛 테스트는 실제 DB를 거치지 않아 이 문제를 못 잡았고, 이번에 처음 실제 MySQL로 돌려본 성능 테스트에서 NPE로 드러났습니다. 이 PR에서resultMap을 추가해 고쳤지만, 운영 환경에서 실제로 소득분석 결과에 영향이 있었는지는 별도 확인이 필요합니다. SETTLEMENT를 함께 쓰는 diagnosis-service 쪽도 확인이 필요합니다.추가 참고 사항‼️ -> 구현 완료
ai-service 연동은 이번 PR 범위 밖입니다.
MonthlyAiReportOrchestrationService(ai-service)가 여전히 사용자 루프 안에서 단건 메서드를 호출하는 한, 실제 AI 리포트 배치의 쿼리 수는 줄어들지 않습니다. 벌크 메서드는 이번에 준비만 해뒀고, 배치가 그걸 쓰도록 바꾸는 작업은 별도 이슈로 진행 필요합니다. 참고로 필요한 변경은 대략 이렇습니다:runBatch()에서 사용자 루프 밖으로incomeAnalysisQueryClient.getMonthlyIncomeAnalysisBulk(userIds, targetYearMonth)호출을 빼내서, 전체 사용자분을 미리Map<Long, MonthlyIncomeAnalysisSummary>로 한 번에 받아둡니다.processUserReport(userId, targetYearMonth)가 내부에서 직접getMonthlyIncomeAnalysis(userId, ...)를 호출하던 부분을 지우고, 1번에서 받은 Map에서 꺼낸 값을 파라미터로 받도록 시그니처를 바꿉니다 (processUserReport(userId, targetYearMonth, income)).try/catch로 격리돼 있었는데, 벌크 호출을 루프 밖으로 빼면 그 벌크 호출 자체의 실패는 더 이상 사용자 단위로 격리되지 않습니다. 벌크 호출을 별도로try/catch해서 실패 시 빈 Map으로 진행하고 로그를 남기는 처리가 필요합니다.monthlyExpenseQueryClient(account-service, 소비집계)는 이 PR에 포함 안 됐으므로 위 작업을 해도 배치 쿼리 수는 절반만 줄어듭니다. 완전히 해결하려면 account-service 쪽도 같은 방식으로 벌크화하고 배치가 그것도 같이 쓰도록 바꿔야 합니다.work-service의
getMonthlyIncomeAnalysisBulk는 사용자 6000명이든 몇 명이든 쿼리 6개로 고정되니, 위 작업만 하면 배치 쪽 쿼리 수도 동일하게 고정됩니다.