이 참에 N+1 문제를 제대로 정리해보자.
위 쿼리는 채팅방 목록을 조회하는 쿼리다.
이때, 여러 테이블을 뒤져서 정보를 조회한다.
프로필, 프로필사진, 채팅 메시지(최신 메시지를 보여줘야 하기에)
이렇게 세개의 테이블을 조인해야 한다.
그런데 조회하는 채팅방마다 쿼리가 나가는 문제가 발생했다.
느낌상 N+1 문제다.
n+1은 요청하나가 왔을 때, 각각의 요청마다 정보를 얻기 위해서 추가적인 request가 발생하는 문제를 말한다.
원래는 쿼리가 엔티티마다 발생하지 않고 하나만 쭉 나간다
(10개의 엔티티를 조회하더라도 하나만 나간다->물론 연관관계를 조회하는 쿼리는 별도로 나간다)
N+1은 왜 발생할까? 관련 내용들을 여기에 정리해두었다.
처음에 ChatRoom을 가져온다.
이 ChatRoom을 기반으로 채팅 내역과 프로필, 프로필 이미지를 가져온다.
쿼리를 봐보자. 현재 프로필은 Batch방식을 통해서 in절로 가져오고 있다.
프로필 사진과 채팅 메시지를 조회할 때 n+1 문제가 발생한다.
프로필을 조회할 때 프로필 이미지를 같이 조회하는 방식으로 변경한다.
fetch join은 되지만 건건이 쿼리가 나가는 문제는 여전하다...
채팅방의 현재 구조를 살펴보자.
1) 채팅방은 채팅 메시지를 연관관계로 갖고 있지 않다. 단방향이다.
몽고 디비로 마이그레이션을 하려면 연관관계가 있으면 안 될 것이라고 생각했다.
그래서 채팅 쪽도 fetch join으로 갖고오기가 어렵다.
2)현재 채팅방 목록 api에서는 모든 채팅방마다 최신 채팅 메시지 10개씩을 함께 내려주고 있다. 채팅창 목록에 가면 최신 채팅 메시지를 채팅방마다 보여줘야 해서, 그럴 바엔 채팅방마다 최신 메시지 10개씩을 내려주는 게 더 낫다고 생각했따. 그러면, 채팅방에 입장할 때 별도로 api를 호출하지 않아도 되기 때문이다(모든 정보를 이미 내려준 상태)
3)굳이 그럴 필요가 있을까? 어차피 채팅방은 메시지가 올 때마다 lastChatTime 필드를 업데이트 해줘야 한다. 그래야 메시지가 온 순서대로 채팅방 목록을 정렬해서 전달할 수 있다(+페이지네이션)
4)그렇다면, 구조를 채팅방은 lastChat과 lastChatTime만 갖고 있고, 채팅방에 들어가면 10개의 채팅 메시지를 호출하는 식으로 변경하자.
5)이 작업은 나중에 레디스를 활용해서 좀더 효율화해보자.
지금은 프로필 조회마다 쿼리가 발생한다.
대신에 in으로 한번에 가져오도록 변경하자.
구조를 이렇게 변경했다.
이제 채팅방과 파트너의 프로필을 조회하는 2개의 쿼리만 나가는 것을 볼 수 있다.
인덱스를 타지 못하고 있다.
나중에 최신 채팅순에 따라서 채팅방을 정렬할 것이기에 마지막 채팅 시간까지 같이 넣어서 인덱스를 걸어준다.
이제 인덱스를 잘 타고 있다.