You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
refactor: DB Repository 계층 쿼리 성능 개선 (5건 - 쿼리 구조 개선 + 인덱스 추가) #845
로컬 환경에서 Repository 계층 전체를 대상으로 쿼리 플랜(EXPLAIN ANALYZE) 조사를 진행해, 비효율이 확인된 5개 쿼리에 대해 구조 개선 및 인덱스 추가를 검증했습니다. 로컬 MySQL(합성 데이터)에서 검증만 완료된 상태이고, 실제 코드/DB에는 아직 반영되지 않았습니다.
AS-IS
게시판 목록 조회(PostRepository): category 필터를 애플리케이션 레이어(Java 스트림)에서 처리 — DB에서 board 전체를 가져온 뒤 대부분 버림. (board_code, created_at) 복합 인덱스 없음
채팅 메시지 페이징(ChatMessageRepository.findByRoomIdWithPaging): LEFT JOIN FETCH cm.chatAttachments + Pageable 조합 때문에 Hibernate가 SQL LIMIT을 포기하고 방의 전체 메시지를 매 요청마다 로드
성적 검수 대기 목록(GpaScoreFilterRepositoryImpl/LanguageTestScoreFilterRepositoryImpl): verify_status/created_at 인덱스가 없어 매번 테이블 전체 스캔
대학 검색 / 개인 추천(UnivApplyInfoFilterRepositoryImpl/UnivApplyInfoRepository): languageRequirements(1:N) fetch join으로 행이 폭증(검색 쿼리는 .distinct() 누락으로 결과 List에 중복 엔티티가 들어가는 정합성 버그도 있음)
관리자 제재 유저 목록(SiteUserFilterRepositoryImpl.searchRestrictedUsers): 상관 서브쿼리(MAX(report.id))가 결과 페이지 20건마다 report 테이블 전체(6만 건)를 재스캔
어떤 부분을 리팩터링하려 하나요?
로컬 환경에서 Repository 계층 전체를 대상으로 쿼리 플랜(
EXPLAIN ANALYZE) 조사를 진행해, 비효율이 확인된 5개 쿼리에 대해 구조 개선 및 인덱스 추가를 검증했습니다. 로컬 MySQL(합성 데이터)에서 검증만 완료된 상태이고, 실제 코드/DB에는 아직 반영되지 않았습니다.AS-IS
PostRepository): category 필터를 애플리케이션 레이어(Java 스트림)에서 처리 — DB에서 board 전체를 가져온 뒤 대부분 버림.(board_code, created_at)복합 인덱스 없음ChatMessageRepository.findByRoomIdWithPaging):LEFT JOIN FETCH cm.chatAttachments+Pageable조합 때문에 Hibernate가 SQLLIMIT을 포기하고 방의 전체 메시지를 매 요청마다 로드GpaScoreFilterRepositoryImpl/LanguageTestScoreFilterRepositoryImpl):verify_status/created_at인덱스가 없어 매번 테이블 전체 스캔UnivApplyInfoFilterRepositoryImpl/UnivApplyInfoRepository):languageRequirements(1:N) fetch join으로 행이 폭증(검색 쿼리는.distinct()누락으로 결과 List에 중복 엔티티가 들어가는 정합성 버그도 있음)SiteUserFilterRepositoryImpl.searchRestrictedUsers): 상관 서브쿼리(MAX(report.id))가 결과 페이지 20건마다report테이블 전체(6만 건)를 재스캔TO-BE
WHERE로 이동 +(board_code, category, created_at)인덱스ChatMessage.chatAttachments에@BatchSize) +(chat_room_id, created_at)인덱스(verify_status, created_at)인덱스 (gpa_score, language_test_score 동일 적용)languageRequirementsfetch join 제거 +@BatchSize로 지연 로딩 전환(중복 반환 버그도 함께 해결)IN절) 3단계로 재작성 +(user_status, created_at),(reported_id)인덱스작업 상세 내용
PostRepository에 category 조건 포함 쿼리 추가,PostQueryService의 인메모리 필터링(getPostListByPostCategory) 제거ChatMessageRepository.findByRoomIdWithPaging에서LEFT JOIN FETCH cm.chatAttachments제거,ChatMessage.chatAttachments에@BatchSize적용SiteUserFilterRepositoryImpl.searchRestrictedUsers를 배치조회 방식으로 재작성UnivApplyInfoRepository,UnivApplyInfoFilterRepositoryImpl에서languageRequirementsfetch join 제거,UnivApplyInfo.languageRequirements에@BatchSize적용V60__...)으로 인덱스 8종 추가:post,post_image,post_like,chat_message,gpa_score,language_test_score,site_user,reportIGNORE INDEX) 적용 검토 — 선택도가 낮아 인덱스 사용 시 오히려 느려지는 케이스 확인됨참고할만한 자료(선택)
로컬 MySQL 8.0(합성 데이터: post 8만 건, chat_message 14.9만 건, site_user 1.5만 건, report 6만 건 등) 기준
EXPLAIN ANALYZE개선 효과:상세 조사/검증 과정(적용 SQL, EXPLAIN ANALYZE 로그, 반복측정 결과)은 Notion "쿼리 플랜 결과" DB에 기록되어 있습니다.
🤖 Generated with Claude Code