N+1 ๋ฌธ์ ์ ํด๊ฒฐ๋ฐฉ๋ฒ
N+1 ๋ฌธ์ ๋?
N+1 ๋ฌธ์ ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๊ด๋ จ๋ ์ฑ๋ฅ ๋ฌธ์ ๋ก, ์ฃผ๋ก ORMs(Object-Relational Mapping)์์ ๋ฐ์ํฉ๋๋ค.
์ด๋ ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ์ถฉ๋ถํ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์์๋ ๋ถ๊ตฌํ๊ณ , ์ถ๊ฐ์ ์ธ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด N๊ฐ์ ์ถ๊ฐ ์ฟผ๋ฆฌ๊ฐ ์คํ๋๋ ์ํฉ์ ๋งํฉ๋๋ค.
์ด๋ก ์ธํด ์ฑ๋ฅ์ด ์ ํ๋๊ณ , ํนํ ๋ฐ์ดํฐ๊ฐ ๋ง์์ง์๋ก ์ฌ๊ฐํ ๋ณ๋ชฉํ์์ ์ผ์ผํฌ ์ ์์ต๋๋ค.
N+1 ๋ฌธ์ ์ ์์
์ํฉ
๋ค์๊ณผ ๊ฐ์ ๋ ๊ฐ์ ํ ์ด๋ธ์ด ์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค.
- Author ํ
์ด๋ธ
id name 1 John Doe 2 Jane Smith - Book ํ
์ด๋ธ
id title author_id 1 Book A 1 2 Book B 1 3 Book C 2
์ฝ๋ ์์ (Python + SQLAlchemy)
from sqlalchemy.orm import joinedload
from models import Author, Book
# ์๋ชป๋ ๋ฐฉ์: N+1 ๋ฌธ์ ๋ฐ์
authors = session.query(Author).all()
for author in authors:
print(author.name, [book.title for book in author.books])
๋ฐ์ํ ๋ฌธ์
- session.query(Author).all()๋ก Author ํ ์ด๋ธ์ ๋ชจ๋ ๋ ์ฝ๋๋ฅผ ๊ฐ์ ธ์ต๋๋ค. (1๊ฐ์ ์ฟผ๋ฆฌ)
- ๊ฐ Author์ ์ฐ๊ฒฐ๋ Book ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด N๊ฐ์ ์ถ๊ฐ ์ฟผ๋ฆฌ๊ฐ ์คํ๋ฉ๋๋ค.
์:
SELECT * FROM Author; -- 1๊ฐ์ ์ฟผ๋ฆฌ
SELECT * FROM Book WHERE author_id = 1; -- N๊ฐ์ ์ฟผ๋ฆฌ ์ค ์ฒซ ๋ฒ์งธ
SELECT * FROM Book WHERE author_id = 2; -- N๊ฐ์ ์ฟผ๋ฆฌ ์ค ๋ ๋ฒ์งธ
...
์ฆ, ์ด 1 + N๊ฐ์ ์ฟผ๋ฆฌ๊ฐ ์คํ๋ฉ๋๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ
1. Eager Loading (๋ฏธ๋ฆฌ ๋ก๋ฉ)
ORM์์ ๊ด๊ณ๋ฅผ ๋ฏธ๋ฆฌ ๋ก๋ฉํ์ฌ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.
authors = session.query(Author).options(joinedload(Author.books)).all()
for author in authors:
print(author.name, [book.title for book in author.books])
์คํ๋๋ SQL:
SELECT * FROM Author LEFT OUTER JOIN Book ON Author.id = Book.author_id;
2. Raw SQL ํ์ฉ
ํ์ํ ๋ฐ์ดํฐ๋ฅผ ์ง์ SQL๋ก ๊ฐ์ ธ์ต๋๋ค.
query = """
SELECT Author.name, Book.title
FROM Author
LEFT JOIN Book ON Author.id = Book.author_id;
"""
result = session.execute(query).fetchall()
for row in result:
print(row.name, row.title)
3. Batch Loading (Batch Query)
๋ฐ์ดํฐ๋ฅผ ๊ทธ๋ฃนํํ์ฌ ๊ฐ์ ธ์ต๋๋ค.
authors = session.query(Author).all()
author_ids = [author.id for author in authors]
books = session.query(Book).filter(Book.author_id.in_(author_ids)).all()
# Mapping
books_by_author = {}
for book in books:
if book.author_id not in books_by_author:
books_by_author[book.author_id] = []
books_by_author[book.author_id].append(book)
for author in authors:
print(author.name, books_by_author.get(author.id, []))
N+1 ๋ฌธ์ ๋ฅผ ์๋ฐฉํ๋ ๋ฐฉ๋ฒ
- ORM์ Lazy Loading ๊ธฐ๋ณธ ์ค์ ํ์ธ
- ๊ด๊ณ ํ๋์ ๊ธฐ๋ณธ ๋ก๋ฉ ์ ๋ต์ Lazy๋ก ์ค์ ํ๋ฉด N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ๊ธฐ ์ฝ์ต๋๋ค.
- Eager Loading ํ์ฉ
- joinedload, selectinload์ ๊ฐ์ ๋ฏธ๋ฆฌ ๋ก๋ฉ ์ต์ ์ ์ฌ์ฉํฉ๋๋ค.
- ์ฟผ๋ฆฌ ์ต์ ํ, fetch join์ฌ์ฉ
- ํ์ํ์ง ์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค์ง ์๋๋ก ์ฟผ๋ฆฌ๋ฅผ ์ต์ํํฉ๋๋ค.
- ์ง์ ์ ์ธ join์ ์ฌ์ฉํ๋๋ก ํ๋ ์์ํฌ์์ ์ง์ํ๋ ์ต์ ์ ์ฌ์ฉํ์ฌ ์ฟผ๋ฆฌ๋ฅผ ์ต์ํ ํฉ๋๋ค. JPA์์๋ @Query annotation์ ์ฌ์ฉํด์ fetch joinํ๋๋ก ํฉ๋๋ค.
- ํ๋กํ์ผ๋ง ๋๊ตฌ ์ฌ์ฉ
- SQLAlchemy์ echo=True ์ต์ ์ด๋ Django์ django-silk๋ฅผ ์ฌ์ฉํด ์คํ๋๋ SQL ์ฟผ๋ฆฌ๋ฅผ ํ์ธํฉ๋๋ค.
- ๋ฐ์ดํฐ ์ ๊ทผ ํจํด ๋ถ์
- N+1 ๋ฌธ์ ๋ ๋ฐ์ดํฐ์ ์ ๊ทผํ๋ ๋ฐฉ์์์ ๋น๋กฏ๋๋ฏ๋ก, ํจํด์ ๋ถ์ํ๊ณ ์ ์ ํ ์กฐ์ ํฉ๋๋ค.