Home 연관 관계를 고려한 N+1 문제 해결하기
Post
Cancel

연관 관계를 고려한 N+1 문제 해결하기

제 퍼디 프로젝트 중, N+1 문제가 발생하여 연관관계를 다시 수립하고 N+1 문제를 개선했던 경험을 공유하고자 합니다.

erd

글 이해를 돕기 위해 간략하게 설명하면,

  • Question 테이블은 User 테이블과 다대일(@ManyToOne) 관계를 맺습니다. 이는 하나의 사용자가 여러 질문을 게시할 수 있음을 의미합니다.
  • 또한, Question 테이블은 여러 답변을 가질 수 있으므로 Answer 테이블과 일대다(@OneToMany) 관계에 있습니다.
  • Question 테이블은 이미지를 최대 3개까지 첨부할 수 있으므로, Image 테이블과 일대다(@OneToMany) 관계를 가집니다.
  • User 테이블은 해당 사용자가 전문가인 경우, Expert 테이블과 일대일(@OneToOne) 관계를 맺습니다.
  • 또한, 사용자는 한 마리의 반려동물만 소유할 수 있으므로, Pet 테이블과 일대일(@OneToOne) 관계를 가집니다.
  • 사용자는 프로필 사진을 한 장만 가질 수 있기 때문에 Image 테이블과 일대일(@OneToOne) 관계를 형성합니다.

정도가 됩니다.

AS-IS

퍼디 프로젝트의 질문글 상세 페이지는 아래와 같이 구성되어 있습니다.

erd

필요한 정보를 나열하면 질문글, 이미지, 질문글 작성자, 질문글 작성자의 반려동물 정보, 답변글, 답변 작성자, 답변 작성자의 전문가 유무가 됩니다.

그렇기 때문에 질문글을 상세 조회할 때 연관된 테이블에 대한 정보가 필요하기 때문에,

select 쿼리가 여러 개 발생하게 됩니다.

발생하는 쿼리에 대해 정리하면,

  1. 질문글 클릭시 조회수 증가 쿼리 발생
  2. 특정 질문글에 대한 조회 쿼리 발생
  3. 질문글에 대한 작성자 조회 쿼리 발생
  4. 질문글에 대한 작성자의 전문가 프로필 조회 쿼리 발생
  5. 작성자의 펫 정보 조회 쿼리 발생
  6. 질문글에 대한 이미지 조회 쿼리 발생
  7. 질문글에 대한 답변글 조회 쿼리 발생
  8. 답변글에 대한 작성자 조회 쿼리 발생

페치조인을 하고 컬렉션을 페치조인 시 예외가 발생하는 점을 고려했을 때 발생하는 쿼리가 7개 정도 되었습니다.

제가 위에서 성능을 개선할 수 있을 것이라고 생각한 부분은 작성자 조회 쿼리에 대한 부분입니다.

User 테이블은 펫, 이미지, 그리고 전문가 테이블과 일대일 관계에 있었기 때문에, 양방향 연관관계로 설정할 경우 외래키가 없는 쪽에서 조회 시 지연 로딩으로 설정했음에도 불구하고 프록시 객체의 한계로 인해 기능하지 않게 됩니다.

펫, 이미지 테이블 연관관계 개선하기

제가 펫, 이미지 테이블과 일대일 양방향 연관관계로 설정했던 이유는, 추후 한명의 유저가 두마리 이상의 펫을 관리하게 될 수 있는 확장성을 고려했기 때문입니다.

팀원들과의 회의 끝에 두 마리 이상의 반려동물을 관리하는 기능은 필요하지 않다고 결정하였습니다.

그래서 저는 일대일 양방향 관계에서 단방향 관계로 변경하고, 외래키를 유저 테이블에 주는 방법을 택했습니다.

그렇게 되면 펫 테이블 입장에서는 주인인 유저 테이블을 알 수 없기 때문에 펫 조회시 유저를 조회할 수 없게 되지만, 유저 테이블에 종속적인 관계가 된다고 생각했습니다.

그리고 지연 로딩을 기능하고자 개선했던 이유는 저희는 질의응답 서비스 뿐만 아니라 커뮤니티 서비스도 제공하고 있는데, 커뮤니티 서비스에서는 반려동물 정보를 사용하지 않기 때문에, 필요하지 않은 펫 정보를 함께 가져오는 것이 비효율적이라고 판단했기 때문입니다.

그래서 마찬가지 이유로 이미지 테이블도 양방향에서 단방향으로 변경하게 되었습니다.

전문가 테이블 연관관계 개선하기

전문가 테이블은 위 펫, 이미지와는 고려할 점이 다릅니다.

추가 정보의 개념으로 접근했기 때문에 전문가 테이블에서 유저 FK를 전문가 테이블의 PK로 사용하도록 설정했습니다.

그러나 이 방법이 유저를 조회할 때 전문가 정보를 조회하는 경우가 있었기 때문에 N+1 문제가 발생했던 것입니다.

이를 펫, 이미지처럼 단방향으로 바꾸는 방법도 고려해보았지만, 쉽게 선택할 수 없었던 이유는 아래와 같습니다.

  • 퍼디에는 등록된 전문가 목록 페이지가 존재하는데, 해당 페이지에서 필요한 정보는 전문가 프로필, 유저의 실명, 유저의 프로필 이미지입니다.
  • 전문가 테이블에 유저 실명을 추가하는 것은 중복된 정보를 나누어서 가지고 있는 것이 데이터 관리가 비효율적이라고 생각했습니다.

그래서 저는 이 문제를 페치 조인을 통해 해결하고자 했습니다.

유저를 조회할 때에는 즉시로딩을 통해 가져오는 방법도 고려했지만, 필요하지 않은 데이터에 대한 조회가 발생하는 것은 비효율적이라고 생각했습니다.

또한, 퍼디에는 질의응답 서비스 뿐만 아니라 커뮤니티 서비스도 제공하고 있습니다. 커뮤니티 서비스의 기능에서는 전문가에 대한 정보가 노출되지 않아 유저를 조회할 때 전문가 테이블을 가져올 필요가 없었기 때문입니다.

그래서 저는 유저를 조회하면서 전문가 정보를 조회할 필요가 있을 경우에만 페치 조인을 활용하여 조회 쿼리 수를 줄일 수 있었고,

결과적으로, 질문글 상세 조회에서 초기에 발생하는 쿼리수가 7개에서 3개로 줄일 수 있었습니다.

TO-BE

위 문제를 개선하면서 들었던 생각은 아래와 같았습니다.

연관관계를 설정할 때 우선 단방향 연관관계로 설정하고 필요할 경우에만 양방향을 고려해보기

프로젝트를 진행하며, 양방향 연관관계 설정의 습관성을 가지고 있었습니다.

이를 통해 외래키를 대상 테이블에 주는 것의 장점만을 생각했던 것이였습니다.

그러나 실제로는 단방향 연관관계를 설정하는 것이 더 적합한 상황을 많이 접하게 되었고, 이는 위 케이스처럼 유용하게 활용할 수 있었습니다.

따라서 앞으로는 연관관계를 설정할 때, 기본적으로 단방향 연관관계를 우선 고려하고, 필요에 따라 양방향 연관관계를 설정하는 것이 좋은 방법이 될 수 있음을 체감할 수 있었습니다.

현재 상황에 맞는 해결 방안을 선택하기

N+1 문제를 해결하는 방법은 다양합니다. 그 중에서도 배치 사이즈 조절,Entity Graph, 페치 조인 사용 등의 방법이 있습니다.

그러나 이러한 해결책이 모든 상황에 적합한 것이 아님을 깨달았습니다.

도메인의 로직을 한번 더 살펴보고 그에 맞는 해결책을 선택하는 것이 제일 좋은 해결책이 될 수 있다는 것이였습니다.

예를 들어, 일대일 관계의 경우에도 단방향으로 설정하고, 필요에 따라 페치 조인을 사용하여 불필요한 쿼리 발생을 최소하하는 방법을 선택할 수 있습니다.

This post is licensed under CC BY 4.0 by the author.

-

url 변수 동적으로 받을 수 있도록 개선하기