feat: 경매장 거래 내역 기본 조회 시 인덱스 사용을 위한 쿼리 수정#88
Conversation
✅ 테스트 결과 for PRBuild: success 🧪 테스트 실행 with Gradle |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
This PR implements performance improvements for auction history search functionality through query optimization. The main changes include a deferred join (late row lookup) pattern for more efficient database queries, along with code style improvements by reorganizing import statements to follow Java conventions (standard library imports after third-party imports).
Changes:
- Implemented deferred join pattern in AuctionHistoryQueryDslRepository to optimize search queries by fetching IDs first, then joining for full data
- Added WebConfig to register custom Spring converters for SortField and SortDirection enums
- Enabled p6spy SQL logging in application.yml for development/debugging purposes
Reviewed changes
Copilot reviewed 109 out of 109 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/main/resources/application.yml | Enabled p6spy logging for SQL query monitoring |
| src/main/java/until/the/eternity/config/WebConfig.java | New configuration class that registers custom enum converters for request parameter binding |
| src/main/java/until/the/eternity/auctionhistory/infrastructure/persistence/AuctionHistoryQueryDslRepository.java | Refactored search method to use deferred join pattern - first query fetches IDs only, second query fetches full entities with joins |
| Multiple files (60+) | Reorganized imports to follow Java conventions: third-party imports first, then standard library imports |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| datasource: | ||
| p6spy: | ||
| enable-logging: false | ||
| enable-logging: true |
There was a problem hiding this comment.
Enabling p6spy logging in the default application.yml will cause SQL logging to be enabled in production environments. This can have significant performance implications and may expose sensitive data in logs. Consider using a profile-specific configuration (e.g., application-dev.yml) or keeping this disabled by default.
| // 4-2단계: ID로 상세 조회 (LEFT JOIN으로 옵션 포함) | ||
| List<AuctionHistory> content = | ||
| queryFactory | ||
| .selectFrom(ah) | ||
| .leftJoin(ah.auctionItemOptions, aio) | ||
| .fetchJoin() | ||
| .where(historyBuilder) | ||
| .where(ah.auctionBuyId.in(ids)) | ||
| .orderBy(orderSpecifiers.toArray(new OrderSpecifier[0])) | ||
| .distinct() // 중복 제거 | ||
| .offset(pageable.getOffset()) | ||
| .limit(pageable.getPageSize()) | ||
| .distinct() | ||
| .fetch(); |
There was a problem hiding this comment.
The deferred join pattern implementation may not preserve the original ordering when fetching the detailed results in step 4-2. After retrieving IDs in a specific order, the second query fetches by ID with the same orderBy clause, but the IN clause doesn't guarantee order preservation. The results might be returned in a different order than the IDs list. Consider either: 1) Ordering the results in-memory based on the original IDs list order, or 2) Using a positional parameter to preserve the order from the first query.
| List<OrderSpecifier<?>> orderSpecifiers = buildOrderSpecifiers(pageable, ah); | ||
|
|
||
| // 4단계: 모든 옵션과 함께 조회 (LEFT JOIN - 조건 없음!) | ||
| // 4단계: Deferred Join (Late Row Lookup) 패턴 적용 |
There was a problem hiding this comment.
The deferred join pattern optimization lacks documentation explaining why this approach is used and what performance benefits it provides. Consider adding a comment block before step 4 that explains: 1) The purpose of the two-step query (Late Row Lookup pattern), 2) The performance benefit of fetching IDs first with index-only scan, 3) Any trade-offs or limitations of this approach.
| // 4단계: Deferred Join (Late Row Lookup) 패턴 적용 | |
| /* | |
| * 4단계: Deferred Join (Late Row Lookup) 패턴 적용 | |
| * | |
| * 이 부분은 의도적으로 "2단계 쿼리"를 사용하는 Late Row Lookup 패턴입니다. | |
| * 1) 목적: | |
| * - 4-1단계에서 JOIN 없이 필터/정렬 조건만 적용하여 ID(기본 키) 리스트만 먼저 조회합니다. | |
| * - 4-2단계에서 조회된 ID들을 기준으로 실제 엔티티와 연관 관계(옵션)를 JOIN 하여 상세 데이터를 가져옵니다. | |
| * - 즉, 비용이 큰 JOIN과 넓은 row 조회를 나중으로 미루어, 먼저 "어떤 row를 볼지"만 가볍게 결정합니다. | |
| * | |
| * 2) 성능상의 이점: | |
| * - 4-1단계 쿼리는 auctionBuyId 기준 인덱스를 활용한 (사실상) index-only scan에 가깝게 동작할 수 있습니다. | |
| * - 이 단계에서는 조인/대량 컬럼 로딩 없이 ID만 조회하므로, 디스크 I/O와 네트워크 전송량이 줄어듭니다. | |
| * - 페이징(offset/limit)도 이 가벼운 쿼리에서 처리하여, 불필요한 조인 대상 row를 읽지 않게 됩니다. | |
| * - 4-2단계는 이미 좁혀진 ID 집합에 대해서만 JOIN을 수행하므로, 전체 쿼리 비용이 줄어드는 효과가 있습니다. | |
| * | |
| * 3) 트레이드오프 / 한계: | |
| * - 쿼리가 2번 실행되므로, 네트워크 round-trip이 한 번 늘어납니다. | |
| * - ID 리스트가 매우 커지는 경우(IN 절이 비대해지는 경우)에는 DB 또는 드라이버에 추가 부담을 줄 수 있습니다. | |
| * - 정렬/필터 조건은 4-1단계와 4-2단계에서 동일하게 유지되어야 하며, | |
| * DB 스냅샷 일관성(동시성 업데이트) 측면에서 2번의 쿼리 사이에 약간의 시차가 존재할 수 있습니다. | |
| * - 따라서, 이 패턴은 "JOIN이 많은/행이 많은 조회"에서 튜닝 목적의 선택적 최적화로 활용해야 합니다. | |
| */ |
📋 상세 설명
📊 체크리스트
📆 마감일