728x90

Posts라는 게시글 정보를 위한 엔티티와 Comments라는 댓글 엔티티가 1대다로 맵핑되어 있다.

맵핑 정보는 아래 코드와 같다. Lazy로 로딩하기 때문에 posts 정보를 가져와도 comments 정보까지 가져오진 않는다.

 

Posts의 멤버 변수 commentsList

 

Comments의 멤버 변수 posts

 

이 상황에서 댓글 정보를 가져올 때 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

 

728x90

+ Recent posts