Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor/#83 problem 조회 쿼리 개선 및 코드 리팩토링 #95

Merged
merged 12 commits into from
Mar 23, 2025

Conversation

sejoon00
Copy link
Contributor

💡 Issue

📄 Description

image
  • 현재 문항 조회시 같이 조회가 필요한 연관된 엔티티는 위와 같습니다.
  • 문항 <-> 새끼문항 <-> 개념태그 의 일대다 연관관계들이 중첩되어 있어 모두 fetch join을 할 시 중복 데어터가 메모리에 중복 적재되는 문제가 발생합니다.

image 917

  • 예를 들어 위와 같은 문항 데이터를 조회한다고 하면 아래와 같은 결과가 조회됩니다.

Frame 2

  • 위와 같이 카타시안 곱 형태로 문항 데이터가 중복되어 메모리로 내려오기때문에 조회해야하는 column이 18개인 저희 문항은 중복데이터의 크기가 너무 크게 됩니다.
  • 위와 같이 10번만 반복되어도 18개의 컬럼이 x 20번씩 360개의 컬럼이 중복 생성되게 되는 문제가 있습니다.
  • 하지만 저희는 하나에 문항에 개념태그가 5개~10개씩 달리기 때문에 더 많은 중복이 발생할 것이라 예상됩니다.

해결 방법

@Query("SELECT DISTINCT p FROM Problem p " +
            "LEFT JOIN FETCH p.childProblems c " +
            "WHERE p.id = :id")
Optional<Problem> findByIdWithFetchJoin(@Param("id") Long id);
  • 따라서 일대다 관계는 새끼문항까지만 fetch join 한 후 연관된 다른 엔티티들은 batch size를 통해 in 절로 bulk 조회하게 설정하였습니다.
    • batch size 설정 : 하나의 트랜잭션에서 특정 테이블에 생기는 여러번의 조회 쿼리를 한번에 조회해줌
    • @batchsize(size = N)는 "한 번에 최대 N개의 엔티티를 조회하라는 뜻
# application-datasource.yml
spring:
  jpa:
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQLDialect
        default_batch_fetch_size: 100
  • @batchsize(size = N)으로 연관관계별로 설정해줄 수 있지만 yml로 global하게 100으로 설정하였습니다.
image
  • 위와 같이 개념 태그를 조인해올 때 새끼문항 3개에 대해 3개의 쿼리가 나가는 걸 하나의 in절로 보내는 것을 볼 수 있습니다.

  • 일대다 연관관계가 중첩된다고 항상 fetch join을 2번하지않고 batch size을 하는것은 좋은 방향은 아닌 것 같습니다.

  • 저희 상황에서는 problem에 데이터가 많고 중복이 많이 될 수 있어 메모리 부하를 낮출 필요가 있다고 판단했지만 메인 select할 엔티티의column 개수가 많지 않다면 fetch join 여러번해도 괜찮다는 생각이 듭니다.

💬 To Reviewers

  • hibernate 6부터 일대다 연관관계의 list를 LAZY 로딩으로 조회해도 메인쿼리 + N개의 추가쿼리가 나가는 것이 아니라 N개 조회 쿼리를 in절로 최적화해서 한번만 쏘는 것을 발견했습니다.
    • N+1 문제보다 이제 1+1 문제가 된 것 같습니다.
    • 처음에는 default_batch_fetch_size 설정 때문에 된 것인줄 알았으나 설정을 지워도 in 절로 최적화해서 나가더군요.
    • hibernate 6 공식 문서에서는 해당 내용을 찾진 못했습니다.
  • 추가 참고, hibernate 6 공식문서를 읽다가 6버전부터는 JPQL에서 DISTINCT를 붙이지않아도 자동으로 DISTINCT처리된다는 것을 알게되었습니다.

🔗 Reference

@sejoon00 sejoon00 self-assigned this Mar 15, 2025
@sejoon00 sejoon00 added the Type: Refactor [이슈 목적] 프로덕션 코드 리팩토링 label Mar 15, 2025
@sejoon00 sejoon00 requested review from seokbeom00 March 15, 2025 13:22
@seokbeom00
Copy link
Member

좋은 코드 잘봤습니다! 조회 쿼리 관련 고민을 많이 한게 느껴지네요. 문항세트 쪽도 연관관계가 하나만 늘어나고, 동일한 구조인데 많이 참고하겠습니다!

Comment on lines +22 to +25
@Query("SELECT DISTINCT p FROM Problem p " +
"LEFT JOIN FETCH p.childProblems c " +
"WHERE p.id = :id")
Optional<Problem> findByIdWithFetchJoin(@Param("id") Long id);
Copy link
Member

Choose a reason for hiding this comment

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

그럼 hibernate6부터는 해당 distinct를 제거해도 내부적으로 추가되는 걸까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

넵 맞습니다

@sejoon00 sejoon00 merged commit 0883a17 into develop Mar 23, 2025
1 check passed
@sejoon00 sejoon00 deleted the refactor/#83 branch March 23, 2025 14:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Refactor [이슈 목적] 프로덕션 코드 리팩토링
Projects
None yet
Development

Successfully merging this pull request may close these issues.

♻️ fetch join
2 participants