Posts라는 게시글 정보를 위한 엔티티와 Comments라는 댓글 엔티티가 1대다로 맵핑되어 있다.
맵핑 정보는 아래 코드와 같다. Lazy로 로딩하기 때문에 posts 정보를 가져와도 comments 정보까지 가져오진 않는다.
이 상황에서 댓글 정보를 가져올 때 posts.getCommentsList()를 통해 댓글을 가져오는 게 빠를까
아니면 CommentsRepository에서 쿼리를 통해 posts 에 작성된 댓글을 가져오는 게 빠를까.
궁금증을 해결하기 위해 직접 실행해봤다. 실험해볼 핵심 코드는 아래 두 줄이다.
윗줄의 코드는 posts의 멤버 변수를 통해 댓글 목록을 가져오는 것이고, 아래 코드는 직접 쿼리를 날려서 가져오는 것이다.
언제 어느 쿼리가 날라가는지 보기 위해 아래와 같이 print 문을 삽입했다.
프로젝트를 실행한 후 동일한 게시글을 읽어서 어떻게 쿼리가 나오는 지 확인해봤다.
아래 결과는 멤버변수를 통해 실행한 결과이다.
=============게시글 읽기 컨트롤러 시작=====================
Hibernate:
select
posts0_.posts_id as posts_id1_4_0_,
posts0_.create_date as create_d2_4_0_,
posts0_.created_date as created_3_4_0_,
posts0_.modified_date as modified4_4_0_,
posts0_.content as content5_4_0_,
posts0_.picture as picture6_4_0_,
posts0_.singer as singer7_4_0_,
posts0_.song_name as song_nam8_4_0_,
posts0_.users_id as users_id9_4_0_
from
posts posts0_
where
posts0_.posts_id=?
Hibernate:
select
users0_.users_id as users_id1_5_0_,
users0_.email as email2_5_0_,
users0_.name as name3_5_0_,
users0_.password as password4_5_0_,
users0_.picture as picture5_5_0_
from
users users0_
where
users0_.users_id=?
Hibernate:
select
likes0_.likes_id as likes_id1_3_,
likes0_.posts_id as posts_id2_3_,
likes0_.users_id as users_id3_3_
from
likes likes0_
where
likes0_.posts_id=?
=========댓글 가져오기 시작========
=========댓글 가져오기 끝========
==============댓글 실제 사용=============
Hibernate:
select
commentsli0_.posts_id as posts_id6_0_0_,
commentsli0_.comments_id as comments1_0_0_,
commentsli0_.comments_id as comments1_0_1_,
commentsli0_.create_date as create_d2_0_1_,
commentsli0_.created_date as created_3_0_1_,
commentsli0_.modified_date as modified4_0_1_,
commentsli0_.content as content5_0_1_,
commentsli0_.posts_id as posts_id6_0_1_,
commentsli0_.user_id as user_id7_0_1_
from
comments commentsli0_
where
commentsli0_.posts_id=?
Hibernate:
select
users0_.users_id as users_id1_5_0_,
users0_.email as email2_5_0_,
users0_.name as name3_5_0_,
users0_.password as password4_5_0_,
users0_.picture as picture5_5_0_
from
users users0_
where
users0_.users_id=?
Hibernate:
select
users0_.users_id as users_id1_5_0_,
users0_.email as email2_5_0_,
users0_.name as name3_5_0_,
users0_.password as password4_5_0_,
users0_.picture as picture5_5_0_
from
users users0_
where
users0_.users_id=?
==============댓글 사용 끝=============
=============게시글 읽기 컨트롤러 끝=====================
getCommentsList()를 실행했을 때에는 아무 쿼리도 실행되지 않았다. LazyLoading이기 때문에 당연하다.
그 후 댓글을 dto로 전환하는 코드를 실행할 때 실제로 사용되므로 그 때 댓글 정보를 가져오는 쿼리문과 작성한 사용자 정보를 위한 쿼리문이 실행된다.
=============게시글 읽기 컨트롤러 시작=====================
Hibernate:
select
posts0_.posts_id as posts_id1_4_0_,
posts0_.create_date as create_d2_4_0_,
posts0_.created_date as created_3_4_0_,
posts0_.modified_date as modified4_4_0_,
posts0_.content as content5_4_0_,
posts0_.picture as picture6_4_0_,
posts0_.singer as singer7_4_0_,
posts0_.song_name as song_nam8_4_0_,
posts0_.users_id as users_id9_4_0_
from
posts posts0_
where
posts0_.posts_id=?
Hibernate:
select
users0_.users_id as users_id1_5_0_,
users0_.email as email2_5_0_,
users0_.name as name3_5_0_,
users0_.password as password4_5_0_,
users0_.picture as picture5_5_0_
from
users users0_
where
users0_.users_id=?
Hibernate:
select
likes0_.likes_id as likes_id1_3_,
likes0_.posts_id as posts_id2_3_,
likes0_.users_id as users_id3_3_
from
likes likes0_
where
likes0_.posts_id=?
=========댓글 가져오기 시작========
Hibernate:
select
comments0_.comments_id as comments1_0_,
comments0_.create_date as create_d2_0_,
comments0_.created_date as created_3_0_,
comments0_.modified_date as modified4_0_,
comments0_.content as content5_0_,
comments0_.posts_id as posts_id6_0_,
comments0_.user_id as user_id7_0_
from
comments comments0_
where
comments0_.posts_id=?
=========댓글 가져오기 끝========
==============댓글 실제 사용=============
Hibernate:
select
users0_.users_id as users_id1_5_0_,
users0_.email as email2_5_0_,
users0_.name as name3_5_0_,
users0_.password as password4_5_0_,
users0_.picture as picture5_5_0_
from
users users0_
where
users0_.users_id=?
Hibernate:
select
users0_.users_id as users_id1_5_0_,
users0_.email as email2_5_0_,
users0_.name as name3_5_0_,
users0_.password as password4_5_0_,
users0_.picture as picture5_5_0_
from
users users0_
where
users0_.users_id=?
==============댓글 사용 끝=============
=============게시글 읽기 컨트롤러 끝=====================
다음은 쿼리를 직접 실행한 결과이다. 위의 결과와 다르다. commentService에서 댓글 목록을 가져올 때 직접 쿼리를 날리고 댓글을 전환할 때 작성자 정보를 따로 쿼리를 통해 가져온다.
궁금해서 실험해봤는데 쿼리 횟수가 동일하게 나왔다.
보고 넘어갈랬는데, 게시글 하나 읽는데 쿼리가 너무 많이 실행되는 것 같아 최적화를 하려 한다.
기존 코드는 data jpa에서 제공하는 findBy~ 를 활용했었다. 그렇다보니 Lazy Loading으로 인해, 각 엔티티에 연결된 다른 엔티티의 정보를 가져올 때마다 select 쿼리가 날아갔다.
예를 들어 게시글에 작성한 댓글 목록을 select 쿼리를 통해 가져왔다 해도, 각 댓글들의 작성자에 대한 정보는 Lazy Loading으로 인해 다시 select 쿼리를 날려 각 user 정보를 가져와야 한다. 그렇다보니 댓글을 100명이 작성했다면 댓글목록 가져오기(1번) + 100명 각각에 대한 정보(100번)으로 총 101번의 쿼리가 날아갈 것이다.
혼자 실행하는 프로젝트라 사용자가 많지 않아 현재는 괜찮지만, 만약 몇 천명이 사용하는 서비스를 이대로 제공하면 성능 이슈가 분명히 발생할 것이다.
다행히도 이런 상황에 대처하기 위해 fetch join이란 것이 있다. 댓글과 작성자 정보를 각각 가져오는 게 아니라, 댓글 정보를 가져옴과 동시에 작성자에 대한 정보까지 하나의 쿼리로 가져오는 것이다.
과정은 지루하니 생략하고 결과를 보여주자면 아래와 같다.
위에서 6개의 select 쿼리가 나간 것에 비해, 단 두 번으로 모든 정보를 가져온다.
첫 번째 쿼리는 게시글 정보 + 게시글 작성자 + 게시글 좋아요 목록을 가져오고
두 번째 쿼리는 댓글 목록 + 댓글 작성자를 가져오는 쿼리이다.
만약 댓글을 100명이 달았다면 3 + 1 + 100 번의 쿼리를 날려야 할 것을 2번으로 줄였으니 비약적인 성능 상승이 이뤄졌다고 할 수 있다.
=============게시글 읽기 컨트롤러 시작=================
Hibernate:
select
posts0_.posts_id as posts_id1_4_0_,
users1_.users_id as users_id1_5_1_,
likeslist2_.likes_id as likes_id1_3_2_,
posts0_.create_date as create_d2_4_0_,
posts0_.created_date as created_3_4_0_,
posts0_.modified_date as modified4_4_0_,
posts0_.content as content5_4_0_,
posts0_.picture as picture6_4_0_,
posts0_.singer as singer7_4_0_,
posts0_.song_name as song_nam8_4_0_,
posts0_.users_id as users_id9_4_0_,
users1_.email as email2_5_1_,
users1_.name as name3_5_1_,
users1_.password as password4_5_1_,
users1_.picture as picture5_5_1_,
likeslist2_.posts_id as posts_id2_3_2_,
likeslist2_.users_id as users_id3_3_2_,
likeslist2_.posts_id as posts_id2_3_0__,
likeslist2_.likes_id as likes_id1_3_0__
from
posts posts0_
inner join
users users1_
on posts0_.users_id=users1_.users_id
left outer join
likes likeslist2_
on posts0_.posts_id=likeslist2_.posts_id
where
posts0_.posts_id=?
Hibernate:
select
comments0_.comments_id as comments1_0_0_,
users1_.users_id as users_id1_5_1_,
comments0_.create_date as create_d2_0_0_,
comments0_.created_date as created_3_0_0_,
comments0_.modified_date as modified4_0_0_,
comments0_.content as content5_0_0_,
comments0_.posts_id as posts_id6_0_0_,
comments0_.user_id as user_id7_0_0_,
users1_.email as email2_5_1_,
users1_.name as name3_5_1_,
users1_.password as password4_5_1_,
users1_.picture as picture5_5_1_
from
comments comments0_
inner join
users users1_
on comments0_.user_id=users1_.users_id
where
comments0_.posts_id=?
=============게시글 읽기 컨트롤러 끝=================
그래도 과정을 살짝 기록하자면 아래 두 코드이다. 위의 코드는 PostsRepository의 코드이고 아래는 CommentsRepository의 코드이다. 둘 다 기존에는 spring data jpa 인터페이스가 제공하는 findBy~ 메서드를 사용했었다. 아래와 같이 직접 fetch join 쿼리를 작성해 실행한 후 성능이 좋아졌음을 확인했다.
원본 코드 : https://github.com/chosh95/Songstagram
chosh95/Songstagram
Spring Boot로 만들어보는 음악 SNS. Contribute to chosh95/Songstagram development by creating an account on GitHub.
github.com
'프로젝트 > Songstagram' 카테고리의 다른 글
[Songstagram] List - > Set으로 Entity Type 변경 (0) | 2020.10.12 |
---|---|
[Songstagram] 갑자기 회원 수정이 안 되는 상황 해결하기 (0) | 2020.09.21 |
[Songstagram] 분노의 ip 차단 기능 구현 (0) | 2020.09.12 |
[Songstagram] 피드백 정리 (0) | 2020.08.25 |
[Songstagram] AWS Elastic Beanstalk에 HTTPS 적용하기 (0) | 2020.08.24 |