댓글 리스트 조회 작업 중 N+1 쿼리 문제를 겪고 해결한 방법

1. N+1 쿼리 문제란 ?

  • 쿼리 1번으로 N건을 가져왔는데, 관련 컬럼을 얻기 위해 쿼리를 N번 추가 수행하는 문제

문제 상황 예제

$books = query_rows("SELECT * FROM books");
foreach( $books as &$book ) {
    $book['author_name'] = query_one("SELECT name FROM authors WHERE id=?", $book['author_id']); 
}

-> book N건을 얻기 위한 쿼리 1회
-> author_name을 얻기 위한 쿼리 N회
-> 성능 측면에서 극히 불합리하지만, 로직을 이해하기 쉽다는 장점은 있음

해결 상황 예제

$books = query_rows("SELECT b.*, a.author_name
    FROM books b LEFT JOIN authors a ON b.author_id=a.id");

출처 - 제타위키

2. 문제 해결

필요한 데이터

주문 댓글 리스트 -> 주문 -> 주문 상품 리스트

  • 주문 댓글 (order_comment) : 댓글 정보
  • 주문 (order) : 주문 날짜
  • 주문 상품 리스트 (order_goods) : 주문 상태

N+1 쿼리 문제 상황

order_comments.each do |comment|
    contents = comment.contents
    order_date = comment.order.created_at
    # order_goods.first 실행 시 N+1 쿼리 문제 발생
    order_status = comment.order.order_goods.first.status
end
  • has_many 관계에 있는 order_goods 의 첫 번째 데이터를 조회할 때마다 select 쿼리가 실행 돼 N+1 쿼리 문제가 발생
  • find, find_by, first, last, pluck 등의 쿼리 메서드 사용 즉시 데이터베이스에 접근해 데이터를 조회
  • first 를 사용하지 않고 주문의 첫 번째 상품을 조회 해야 함

N+1 쿼리 문제 해결

  • rails 에선 model 간 관계를 지정할 수 있는데 order 와 order_goods 같은 경우는 아래와 같이 has_many 관계로 맺어짐
    class Order
      has_many :order_goods
    
  • 실제로 has_many 관계에 있다 해도 임의로 has_one 관계 설정을 통해 order 와 order_goods 가 1:1 의 관계로 사용 가능
    class Order
      # 실제 relation
      has_many :order_goods
      # 커스텀 relation, order_goods_get_first 의 실제 모델인 OrderGoods 명시 필요
      has_one :order_goods_get_one, class_name: 'OrderGoods'
    
  • has_one 관계로 데이터 조회
    order_comments.each do |comment|
      contents = comment.contents
      order_date = comment.order.created_at
      # N+1 쿼리 문제 해결
      order_status = comment.order.order_goods_get_one.status
    end
    

마무리..