N+1 Query Problem

ding·2025년 1월 6일

N+1 Query Problem

N+1 문제는 데이터베이스에서 연관 데이터(Join)를 조회할 때 비효율적인 쿼리 방식으로 인해 발생하는 성능 문제이다.

예시

from beanie import Document, Link

class Author(Document):
    name: str

class Book(Document):
    title: str
    author: Link[Author]

# N+1 문제 발생
books = await Book.find()
for book in books:
    author = await book.author.fetch()
    print(f"{book.title} by {author.name}")
  1. Book.find()에서 책을 조회하는 쿼리 1개 발생
  2. for book in books에서 각 책마다 author.fetch() 실행하여 3개(book의 개수)의 추가 쿼리 발생

영향

  • 성능 저하: 데이터가 많아질수록 쿼리 수가 급격히 증가
  • 데이터베이스 부하 증가: 다중 쿼리로 인해 DB에 부담 증가
  • 대기 시간 증가: 네트워크 요청 및 데이터베이스 응답 지연

해결 방안

  1. Eager Loading 사용
  • 한 번의 쿼리로 연관 데이터를 미리 가져온다.
# fetch_links=True를 사용하여 author를 한 번에 로딩
books = await Book.find(fetch_links=True)
for book in books:
    print(f"{book.title} by {book.author.name}")
    
# `SELECT * FROM books JOIN authors` 한 개의 쿼리만 발생
  • fetch_links=True를 사용하면 한 번의 쿼리로 연관된 데이터를 모두 로딩한다.
  • 장점: 쿼리 수를 최소화하고, 한 번의 쿼리로 모든 데이터를 가져온다.
  • 단점: 데이터가 너무 많을 경우 메모리 사용량이 증가할 수 있다.
  1. 데이터베이스 JOIN 활용(Aggregation)
  • MongoDB에서는 aggregate 사용 가능
# MongoDB Aggregation Pipeline
books_with_authors = await Book.find().aggregate([
    {"$lookup": {
        "from": "authors",
        "localField": "author",
        "foreignField": "_id",
        "as": "author_info"
    }}
]).to_list()
  • $lookup: MongoDB의 조인 기능을 사용하여 한 번의 쿼리로 데이터를 가져온다.
  • 장점: 효율적으로 연관 데이터를 조회할 수 있다.
  • 단점: 쿼리가 복잡해질 수 있고, MongoDB 인덱스 관리가 필요하다.
  1. Batch Loading
  • 여러 ID를 한 번에 가져오기($in 사용)
# 여러 저자를 한 번에 가져오기
author_ids = [book.author.id for book in books]
authors = await Author.find({"_id": {"$in": author_ids}}).to_list()
  • 장점: N+1 문제 해결, 쿼리 수 감소
  • 단점: 한 번에 불러오는 데이터가 너무 많을 경우 메모리 사용량 증가 가능

0개의 댓글