본문 바로가기
프로젝트/협업 프로젝트(2023.12.18-2024.01.25)

[Key Word 개발기] 친구 검색 쿼리 개선기

by dal_been 2024. 3. 11.
728x90

최근 인덱스에 대해서 공부하다가 프로젝트를 생각해보니 검색 쿼리 속도 개선을 하면 좋을 거같아서 테스트하는 도중에 좋은 개선사항이 있어 적어본다.


쿼리 수정기

 

해당 프로젝트에서 친구관련 리스트를 가져올때 총 3가지의 경우가 존재한다.

 

1. 내가 친구한테 친구를 요청한 경우

2. 친구가 나에게 요청한 경우

3. 이미 친구인 경우

 

1,3번의 경우 밑에 코드를 이용한다.

  @Query("SELECT f.friend.memberId FROM Friend f JOIN f.friend friend JOIN f.memberRequest memberRequest "
      + "WHERE memberRequest.memberId = :memberRequestId AND f.status = :friendStatus ORDER BY f.friend.name")
  Page<Long> findFriendListByMemberId(@Param("memberRequestId") Long memberRequestId,
      @Param("friendStatus") FriendStatus friendStatus,
      Pageable pageable);
Hibernate: 
    select
        friend0_.member_id as col_0_0_ 
    from
        friend friend0_ 
    inner join
        member member1_ 
            on friend0_.member_id=member1_.member_id 
    inner join
        member member2_ 
            on friend0_.member_req_id=member2_.member_id 
    where
        member2_.member_id=? 
        and friend0_.status=? 
    order by
        member1_.name limit ?

 

현재 이코드와 쿼리는 불필요한 join을 발생시키고 있다.

그 이유는 첫번째

  • where member2.member_id는 결국 friend.member_req_id이다. 그렇다면 friend.member_req_id로 바꾸면 된다
  • 만약 그렇게 된다면 member2와의 join은 불필요하다

 

  @Query("SELECT f.friend.memberId FROM Friend f JOIN f.friend friend "
      + "WHERE f.memberRequest.memberId = :memberRequestId AND f.status = :friendStatus ORDER BY f.friend.name")
  Page<Long> findFriendListByMemberId(@Param("memberRequestId") Long memberRequestId,
      @Param("friendStatus") FriendStatus friendStatus,
      Pageable pageable);

 그래서 f.memberRequest에 대한 join을 없애고 where 절에 id값만 비교할 수 있게 수정하였다.

그랬더니 쿼리가 군더더기 없이 원하는데로 join을 한번만 진행하였다.

    select
        friend0_.member_id as col_0_0_ 
    from
        friend friend0_ 
    inner join
        member member1_ 
            on friend0_.member_id=member1_.member_id 
    where
        friend0_.member_req_id=? 
        and friend0_.status=? 
    order by
        member1_.name limit ?

 

 

 

2번의 경우

  @Query("SELECT f.memberRequest.memberId FROM Friend f JOIN f.friend friend JOIN f.memberRequest memberRequest "
      + "WHERE friend.memberId = :friendMemberId AND f.status = :friendStatus ORDER BY f.memberRequest.name")
  Page<Long> findFriendListByFriendId(@Param("friendMemberId") Long friendMemberId,
      @Param("friendStatus") FriendStatus friendStatus,
      Pageable pageable);
    select
        friend0_.member_req_id as col_0_0_ 
    from
        friend friend0_ 
    inner join
        member member1_ 
            on friend0_.member_id=member1_.member_id 
    inner join
        member member2_ 
            on friend0_.member_req_id=member2_.member_id 
    where
        member1_.member_id=? 
        and friend0_.status=? 
    order by
        member2_.name limit ?

 

결국 이 쿼리도  불필요한 join을 발생시키고 있다.

  • where member1.member_id는 friend.member_id와 동일하다
  • 따라서 member1과의 join은 불필요하다.
  @Query("SELECT f.memberRequest.memberId FROM Friend f JOIN f.memberRequest memberRequest "
      + "WHERE f.friend.memberId = :friendMemberId AND f.status = :friendStatus ORDER BY f.memberRequest.name")
  Page<Long> findFriendListByFriendId(@Param("friendMemberId") Long friendMemberId,
      @Param("friendStatus") FriendStatus friendStatus,
      Pageable pageable);

 

f.friend에 대한 join을 없애고 where절에는 f.friend.memberId로 값을 비교하게 수정하였다.

그랬더니 이번에도 join한번만 진행하게 수정되었다.

Hibernate: 
    select
        friend0_.member_req_id as col_0_0_ 
    from
        friend friend0_ 
    inner join
        member member1_ 
            on friend0_.member_req_id=member1_.member_id 
    where
        friend0_.member_id=? 
        and friend0_.status=? 
    order by
        member1_.name limit ?

 

 

 

쿼리 개선기

 

해당 쿼리 속도를 측정하기 위해 약 20만개의 데이터를 삽입하여 진행하였다.

 

 

일단 앞서 쿼리를 수정하기전 쿼리 속도는??

SELECT friend.member_id FROM Friend f JOIN member friend on f.member_id = friend.member_id
    JOIN member memberRequest on f.member_id = memberRequest.member_id
      WHERE f.member_req_id = 6 AND f.status = 'FRIEND_ACCEPTED' ORDER BY friend.name limit 180000 ,10;

 

첫째줄에 type이 All이다. 즉 아무 인덱스도 사용하고 있지 않다는 말이다.

여기서 앞서 쿼리를 수정한 걸로 쿼리 속도를 측정하면??

 

SELECT friend.member_id
FROM Friend f
         JOIN member friend ON f.member_id = friend.member_id
WHERE f.member_req_id = 6 AND f.status = 'FRIEND_ACCEPTED'
ORDER BY friend.name DESC limit 180000 ,10;

 

여기도 첫번째 type이 All이다. 아무 인덱스도 사용하고 있지않다.

다만 join두번이었던 것을 한번으로 줄였더니 약 35% 쿼리 속도가 개선되었다.

 

 

여기서 인덱스를 생성해서 쿼리 속도를 개선해보겠다.

 

첫번째로 Friend테이블과 관련된 칼럼 member_id, member_req_id, status 복합 인덱스를 생성해겠다

create index idx_test4 on friend (member_id,member_req_id, status);
SELECT friend.member_id
FROM Friend f
         JOIN member friend ON f.member_id = friend.member_id
WHERE f.member_req_id = 6 AND f.status = 'FRIEND_ACCEPTED'
ORDER BY friend.name DESC limit 180000 ,10;

 

약 230ms정도 나온다. 8%정도 개선된 것인데 획기적이지는 않다.

이유를 생각해보면 where절에서는 index를 사용하지만 orderBy절에서  filesort방식으로 임시 테이블을 사용해 정렬 작업을 수행하고 있다.

그럼 Order By에 있는 name절에 인덱스를 생성해볼까?

 

create index idx_test2  on member (name  DESC );

 

idx_test2를 사용한다는 것을 key칼럼과 Extra칼럼을 보면 알 수 있다.

그랬더니 230ms -> 197ms 15%개선되었다.

 

 

음 그럼 member에서 member_id랑 name Desc 복합 인덱스 만들어보는거 어때??

create index idx_test6  on member (member_id,name  DESC );

230ms로 생각보다 개선이 안되었다.

 

 

그래서 보니 rows를 보면 모든 데이터를 끌고온다.  idx_test6인덱스가 더 성능이 안좋았다.

 

 


결과적으로 join 불필요한거 줄이고, Friend 인덱스와 Member order 인덱스를 생성하였더니

388ms에서 197ms로 약 50%개선되었다.

 

아 테스트까지 완료했으니 이제 프로젝트 코드 수정해봐야징