Skip to content

[REFACTOR] 지도 API 쿼리 최적화#129

Merged
david-parkk merged 6 commits into
developfrom
KAN-59-refactor/query
Aug 11, 2025
Merged

[REFACTOR] 지도 API 쿼리 최적화#129
david-parkk merged 6 commits into
developfrom
KAN-59-refactor/query

Conversation

@You-Hyuk
Copy link
Copy Markdown
Contributor

@You-Hyuk You-Hyuk commented Aug 1, 2025

✏️ 작업 개요

⛳ 작업 분류

  • 유저 검증 중복 제거
  • N+1 문제 해결
  • Page -> List 타입 변경

🔨 작업 상세 내용

  1. 토큰 검증 시 유저 DB 조회를 진행하고 있는데, 이를 API 호출 시점에서 한 번 더 DB 조회 및 검증을 진행하고 있어 제거하였습니다.
  2. 지도 API의 대부분이 N+1 문제가 발생하고 있었는데, 이를 해결하였습니다. 다만 지도 상세 조회 및 지도 메뉴 상세 조회 API의 경우 메뉴태그, 메뉴판, 메뉴 이미지의 경우 연관관계상 추가적인 쿼리가 발생합니다.
        List<MenuTag> menuTags = menuTagRepository.findMenuTagsByMenuId(menu.getId());
        List<MenuImg> menuImgs = menuImgRepository.findAllByMenuId(menu.getId());
        List<MenuFolder> menuFolders = menuFolderRepository.findMenuFoldersByMenuId(menu.getId());
  1. Page 타입의 경우 추가적인 Count 쿼리가 발생하는데, 현재 Response 상에서 Page가 제공하는 총 개수, 페이지 등과 같은 메타 정보를 사용하지 않아 List 타입으로 변경하였습니다.

💡 생각해볼 문제

  • X

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Aug 1, 2025

Test Results

42 tests   42 ✅  1m 1s ⏱️
10 suites   0 💤
10 files     0 ❌

Results for commit 3f849cf.

♻️ This comment has been updated with latest results.

Copy link
Copy Markdown
Member

@david-parkk david-parkk left a comment

Choose a reason for hiding this comment

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

코멘트 확인 부탁드립니다~

Comment on lines -274 to -288
@Query(
value = "SELECT m.* FROM menu m " +
"JOIN store s ON m.store_id = s.id " +
"JOIN map ON s.map_id = map.id " +
"WHERE m.user_id = :userId " +
"AND LOWER(m.title) LIKE CONCAT('%', LOWER(:title), '%') " +
"ORDER BY ST_Distance_Sphere(map.location, :userLocation) ASC",
countQuery = "SELECT COUNT(*) FROM menu m " +
"JOIN store s ON m.store_id = s.id " +
"JOIN map ON s.map_id = map.id " +
"WHERE m.user_id = :userId " +
"AND LOWER(m.title) LIKE CONCAT('%', LOWER(:title), '%')",
nativeQuery = true
)
Page<Menu> findByUserIdTitleContainingOrderByDistance(@Param("userId") Long userId,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

네이티브 쿼리를 사용하면 N+1 쿼리를 해결할 수 있지 않나요?
해당 부분이 N+1 문제가 발생하는 지점이 맞을까요?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

말씀해주신대로 네이티브 쿼리를 사용해도 추가적인 쿼리(N+1문제)가 발생하지 않는 것을 확인했습니다.

궁금한 점이 있는데, 위 쿼리에서 Menu의 컬럼만을 조회하고자 하였는데 Store와 Map의 컬럼까지 모두 가져오는 것이 어째서 그런 것인지 잘 모르겠어서 간략하게 알려주실 수 있을까요?? JOIN의 결과 자체를 반환해주는 건가요??

제가 생각한 바로는 위 쿼리를 통해 Menu의 데이터를 조회하고, Store와 Map의 경우 LAZY 설정이 되어있어 get과 같은 호출로 인해 추가적인 쿼리가 발생해야 된다고 생각했는데 하나의 쿼리로 모든 엔티티의 컬럼이 모두 조회되는 이유를 잘 모르겠습니다.

추가적으로 JPQL을 사용하는 경우 SQL 함수를 사용하는 것에 제약이 있어 네이티브 쿼리를 사용하는게 맞는 것 같아 수정하겠습니다

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

저도 해당 부분에 대해서 추상적으로만 이해하고 있어서 관련해서 찾아본 내용을 공유해보겠습니다(틀린 내용이 있을 수 있어요~~)

결론적으로 위 쿼리에 Menu컬럼 만을 조회해도 Store, Map 엔티티를 모두 조회할 수 있는 내용은 nativeQuery사용하게 되면 해당 쿼리를 직접 JDBC API를 호출하기 때문에 연관관계 로딩 전략의 영향에서 벗어나게 됩니다.

그러면 위 쿼리를 패치 조인으로 바꾸게 되면 N+1을 방지할 수 있을까요?

방지할 수 있습니다! 저도 패치 조인에서 오인하고 있던 부분이 2개의 이상의 패치조인을 사용하게 되면 전부 적용되지 않고 일부분은 Lazy 전략을 사용하게된다고 이해하고 있었습니다.

그런데 이말이 틀리면서 맞는 오해의 소지가 있습니다. 정정하면 둘 이상의 컬렉션을 패지 할 수 있다라는 표현이 정확합니다.

public class Map extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToMany(mappedBy = "map")
    private List<Store> stores;

    @OneToMany(mappedBy = "user")
    private List<User> users;
}

만약에 Map 엔티티에 Users라는 컬랙션 일다대 연관관계가 추가되었고, Map엔티티와 Store,User를 모두 조회하고 싶어서 Fetch Join을 사용할 경우에는 문제가 됩니다.(조인 결과 컬럼Set DB관점에서 생각해보면 이유를 예상해볼 수 있습니다)

다시 돌아와서 findByUserIdTitleContainingOrderByDistance 를 보겠습니다. menu-store-map 순으로 조인하고 있고, menu-store는 n:1이고 stroe-map은 n:1 이기 때문에 둘이상의 컬렉션을 패치하는 상황이 아니기 때문에 Fetch Join을 사용하든 NativeQuery를 사용하던 문제가 되지 않습니다

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

미리 설정해둔 연관관계 전략 자체가 무시되는군요... 직접 쿼리마다 실행 결과도 확인하고 레퍼런스를 찾아봐도 잘 안나와서 질문 드렸는데 상세한 설명 정말 감사드립니다 👍

위에 제가 코멘트 단 것과 같이 SQL 내장 함수를 사용하기 때문에 네이티브 쿼리를 작성하는 방식으로 수정했습니다!

@david-parkk david-parkk merged commit 20d12fe into develop Aug 11, 2025
6 of 8 checks passed
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.

[KAN-59] 쿼리 성능 최적화

2 participants