๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ ๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ์ฑ๋ฅ ์ต์ ํ๋ฅผ ์ํ SQLAlchemy์ ์ง์ฐ ๋ฐ ์ฆ์ ๋ก๋ฉ ์ ๋ต์ ์ฌ์ธต ๋ถ์ํฉ๋๋ค. ๊ฐ ์ ๊ทผ ๋ฐฉ์์ ํจ๊ณผ์ ์ผ๋ก ์ฌ์ฉํ๋ ์๊ธฐ์ ๋ฐฉ๋ฒ์ ์์๋ณด์ธ์.
SQLAlchemy ์ฟผ๋ฆฌ ์ต์ ํ: ์ง์ฐ ๋ก๋ฉ vs. ์ฆ์ ๋ก๋ฉ ๋ง์คํฐํ๊ธฐ
SQLAlchemy๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ํธ์์ฉ์ ๋จ์ํํ๋ ๊ฐ๋ ฅํ Python SQL ํดํท์ด์ ๊ฐ์ฒด ๊ด๊ณํ ๋งคํผ(ORM)์ ๋๋ค. ํจ์จ์ ์ธ SQLAlchemy ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ฑํ๋ ๋ฐ ์์ด ํต์ฌ์ ๋ก๋ฉ ์ ๋ต์ ํจ๊ณผ์ ์ผ๋ก ์ดํดํ๊ณ ํ์ฉํ๋ ๊ฒ์ ๋๋ค. ์ด ๊ธ์์๋ ๋ ๊ฐ์ง ๊ธฐ๋ณธ ๊ธฐ์ ์ธ ์ง์ฐ ๋ก๋ฉ(lazy loading)๊ณผ ์ฆ์ ๋ก๋ฉ(eager loading)์ ์์ธํ ์ดํด๋ณด๊ณ , ๊ฐ๊ฐ์ ์ฅ์ , ๋จ์ ๋ฐ ์ค์ ์ ์ฉ ๋ฐฉ๋ฒ์ ํ๊ตฌํฉ๋๋ค.
N+1 ๋ฌธ์ ์ดํดํ๊ธฐ
์ง์ฐ ๋ก๋ฉ๊ณผ ์ฆ์ ๋ก๋ฉ์ ๋ํด ์์ธํ ์์๋ณด๊ธฐ ์ ์, ORM ๊ธฐ๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ์์ ํํ ๋ฐ์ํ๋ ์ฑ๋ฅ ๋ณ๋ชฉ ํ์์ธ N+1 ๋ฌธ์ ๋ฅผ ์ดํดํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ ์ ๋ชฉ๋ก์ ๊ฒ์ํ ๋ค์, ๊ฐ ์ ์์ ๋ํด ๊ด๋ จ ๋์๋ฅผ ๊ฐ์ ธ์์ผ ํ๋ค๊ณ ์์ํด ๋ณด์ธ์. ๋จ์ํ ์ ๊ทผ ๋ฐฉ์์ ๋ค์๊ณผ ๊ฐ์ ์ ์์ต๋๋ค:
- ๋ชจ๋ ์ ์๋ฅผ ๊ฒ์ํ๊ธฐ ์ํ ํ๋์ ์ฟผ๋ฆฌ ์คํ (1๊ฐ ์ฟผ๋ฆฌ).
- ์ ์ ๋ชฉ๋ก์ ๋ฐ๋ณตํ๊ณ ๊ฐ ์ ์์ ๋ํด ๋ณ๋์ ์ฟผ๋ฆฌ๋ฅผ ์คํํ์ฌ ํด๋น ๋์๋ฅผ ๊ฒ์ (N๊ฐ์ ์ฟผ๋ฆฌ, ์ฌ๊ธฐ์ N์ ์ ์์ ์).
์ด๋ก ์ธํด ์ด N+1๊ฐ์ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํฉ๋๋ค. ์ ์์ ์(N)๊ฐ ์ฆ๊ฐํจ์ ๋ฐ๋ผ ์ฟผ๋ฆฌ ์๋ ์ ํ์ ์ผ๋ก ์ฆ๊ฐํ์ฌ ์ฑ๋ฅ์ ์ฌ๊ฐํ ์ํฅ์ ๋ฏธ์นฉ๋๋ค. N+1 ๋ฌธ์ ๋ ๋๊ท๋ชจ ๋ฐ์ดํฐ์ ๋๋ ๋ณต์กํ ๊ด๊ณ๋ฅผ ๋ค๋ฃฐ ๋ ํนํ ๋ฌธ์ ๊ฐ ๋ฉ๋๋ค.
์ง์ฐ ๋ก๋ฉ: ์จ๋๋งจ๋ ๋ฐ์ดํฐ ๊ฒ์
์ง์ฐ ๋ก๋ฉ(deferred loading)์ด๋ผ๊ณ ๋ ํ๋ ์ง์ฐ ๋ก๋ฉ์ SQLAlchemy์ ๊ธฐ๋ณธ ๋์์ ๋๋ค. ์ง์ฐ ๋ก๋ฉ์ ์ฌ์ฉํ๋ฉด ๊ด๋ จ ๋ฐ์ดํฐ๋ ๋ช ์์ ์ผ๋ก ์ ๊ทผ๋ ๋๊น์ง ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๊ฐ์ ธ์ค์ง ์์ต๋๋ค. ์ ์์ ๋์ ์์ ์์, ์ ์ ๊ฐ์ฒด๋ฅผ ๊ฒ์ํ ๋ `books` ์์ฑ(์ ์์ ๋์ ์ฌ์ด์ ๊ด๊ณ๊ฐ ์ ์๋์ด ์๋ค๊ณ ๊ฐ์ )์ ์ฆ์ ์ฑ์์ง์ง ์์ต๋๋ค. ๋์ SQLAlchemy๋ `author.books` ์์ฑ์ ์ ๊ทผํ ๋๋ง ๋์๋ฅผ ๊ฐ์ ธ์ค๋ "์ง์ฐ ๋ก๋"๋ฅผ ์์ฑํฉ๋๋ค.
์์:
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Author(Base):
__tablename__ = 'authors'
id = Column(Integer, primary_key=True)
name = Column(String)
books = relationship("Book", back_populates="author")
class Book(Base):
__tablename__ = 'books'
id = Column(Integer, primary_key=True)
title = Column(String)
author_id = Column(Integer, ForeignKey('authors.id'))
author = relationship("Author", back_populates="books")
engine = create_engine('sqlite:///:memory:') # Replace with your database URL
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
# Create some authors and books
author1 = Author(name='Jane Austen')
author2 = Author(name='Charles Dickens')
book1 = Book(title='Pride and Prejudice', author=author1)
book2 = Book(title='Sense and Sensibility', author=author1)
book3 = Book(title='Oliver Twist', author=author2)
session.add_all([author1, author2, book1, book2, book3])
session.commit()
# Lazy loading in action
authors = session.query(Author).all()
for author in authors:
print(f"Author: {author.name}")
print(f"Books: {author.books}") # This triggers a separate query for each author
for book in author.books:
print(f" - {book.title}")
์ด ์์ ์์ ๋ฃจํ ๋ด์์ `author.books`์ ์ ๊ทผํ๋ฉด ๊ฐ ์ ์๋ง๋ค ๋ณ๋์ ์ฟผ๋ฆฌ๊ฐ ํธ๋ฆฌ๊ฑฐ๋์ด N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค.
์ง์ฐ ๋ก๋ฉ์ ์ฅ์ :
- ์ด๊ธฐ ๋ก๋ฉ ์๊ฐ ๋จ์ถ: ๋ช ์์ ์ผ๋ก ํ์ํ ๋ฐ์ดํฐ๋ง ์ด๊ธฐ์ ๋ก๋๋์ด ์ด๊ธฐ ์ฟผ๋ฆฌ์ ๋ํ ์๋ต ์๊ฐ์ด ๋นจ๋ผ์ง๋๋ค.
- ๋ฎ์ ๋ฉ๋ชจ๋ฆฌ ์๋น: ๋ถํ์ํ ๋ฐ์ดํฐ๊ฐ ๋ฉ๋ชจ๋ฆฌ์ ๋ก๋๋์ง ์์ ๋๊ท๋ชจ ๋ฐ์ดํฐ์ ์ ๋ค๋ฃฐ ๋ ์ ์ฉํ ์ ์์ต๋๋ค.
- ๋๋ฌธ ์ ๊ทผ์ ์ ํฉ: ๊ด๋ จ ๋ฐ์ดํฐ์ ๊ฑฐ์ ์ ๊ทผํ์ง ์๋ ๊ฒฝ์ฐ, ์ง์ฐ ๋ก๋ฉ์ ๋ถํ์ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๋ณต์ ํผํฉ๋๋ค.
์ง์ฐ ๋ก๋ฉ์ ๋จ์ :
- N+1 ๋ฌธ์ : N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ ๊ฐ๋ฅ์ฑ์ ํนํ ์ปฌ๋ ์ ์ ๋ฐ๋ณตํ๊ณ ๊ฐ ํญ๋ชฉ์ ๋ํด ๊ด๋ จ ๋ฐ์ดํฐ์ ์ ๊ทผํ ๋ ์ฑ๋ฅ์ ์ฌ๊ฐํ๊ฒ ์ ํ์ํฌ ์ ์์ต๋๋ค.
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๋ณต ์ฆ๊ฐ: ์ฌ๋ฌ ์ฟผ๋ฆฌ๋ ํนํ ๋ถ์ฐ ์์คํ ์ด๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๋ฒ๊ฐ ๋ฉ๋ฆฌ ๋จ์ด์ ธ ์๋ ๊ฒฝ์ฐ ๋๊ธฐ ์๊ฐ์ ์ฆ๊ฐ์ํฌ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ํธ์ฃผ์์ ์ ๋ฝ์ ์ ํ๋ฆฌ์ผ์ด์ ์๋ฒ์ ์ ๊ทผํ๊ณ ๋ฏธ๊ตญ์ ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํ๋ ์ํฉ์ ์์ํด ๋ณด์ธ์.
- ์์์น ๋ชปํ ์ฟผ๋ฆฌ ๋ฐ์ ๊ฐ๋ฅ์ฑ: ์ง์ฐ ๋ก๋ฉ์ด ์ธ์ ์ถ๊ฐ ์ฟผ๋ฆฌ๋ฅผ ํธ๋ฆฌ๊ฑฐํ ์ง ์์ธกํ๊ธฐ ์ด๋ ค์ ์ฑ๋ฅ ๋๋ฒ๊น ์ด ๋ ์ด๋ ค์์ง ์ ์์ต๋๋ค.
์ฆ์ ๋ก๋ฉ: ์ ์ ์ ๋ฐ์ดํฐ ๊ฒ์
์ฆ์ ๋ก๋ฉ์ ์ง์ฐ ๋ก๋ฉ๊ณผ ๋ฌ๋ฆฌ ์ด๊ธฐ ์ฟผ๋ฆฌ์ ํจ๊ป ๊ด๋ จ ๋ฐ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ ๊ฐ์ ธ์ต๋๋ค. ์ด๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๋ณต ํ์๋ฅผ ์ค์ฌ N+1 ๋ฌธ์ ๋ฅผ ํด๊ฒฐํฉ๋๋ค. SQLAlchemy๋ `joinedload`, `subqueryload`, `selectinload` ์ต์ ์ ์ฃผ๋ก ์ฌ์ฉํ์ฌ ์ฆ์ ๋ก๋ฉ์ ๊ตฌํํ๋ ์ฌ๋ฌ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค.
1. ์กฐ์ธ ๋ก๋ฉ (Joined Loading): ๊ณ ์ ์ ์ธ ์ ๊ทผ ๋ฐฉ์
์กฐ์ธ ๋ก๋ฉ์ SQL JOIN์ ์ฌ์ฉํ์ฌ ๋จ์ผ ์ฟผ๋ฆฌ์์ ๊ด๋ จ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ํฉ๋๋ค. ์ด๋ ์ผ๋ฐ์ ์ผ๋ก ์ผ๋์ผ ๋๋ ์ผ๋๋ค ๊ด๊ณ์ ๋น๊ต์ ์ ์ ์์ ๊ด๋ จ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃฐ ๋ ๊ฐ์ฅ ํจ์จ์ ์ธ ์ ๊ทผ ๋ฐฉ์์ ๋๋ค.
์์:
from sqlalchemy.orm import joinedload
authors = session.query(Author).options(joinedload(Author.books)).all()
for author in authors:
print(f"Author: {author.name}")
for book in author.books:
print(f" - {book.title}")
์ด ์์ ์์ `joinedload(Author.books)`๋ SQLAlchemy์๊ฒ ์ ์ ์์ฒด์ ๋์ผํ ์ฟผ๋ฆฌ์์ ์ ์์ ๋์๋ฅผ ๊ฐ์ ธ์ค๋๋ก ์ง์ํ์ฌ N+1 ๋ฌธ์ ๋ฅผ ํผํฉ๋๋ค. ์์ฑ๋ SQL์๋ `authors` ๋ฐ `books` ํ ์ด๋ธ ๊ฐ์ JOIN์ด ํฌํจ๋ฉ๋๋ค.
2. ์๋ธ์ฟผ๋ฆฌ ๋ก๋ฉ (Subquery Loading): ๊ฐ๋ ฅํ ๋์
์๋ธ์ฟผ๋ฆฌ ๋ก๋ฉ์ ๋ณ๋์ ์๋ธ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ๊ด๋ จ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ํฉ๋๋ค. ์ด ์ ๊ทผ ๋ฐฉ์์ ๋จ์ผ JOIN ์ฟผ๋ฆฌ๊ฐ ๋นํจ์จ์ ์ผ ์ ์๋ ๋๋์ ๊ด๋ จ ๋ฐ์ดํฐ ๋๋ ๋ณต์กํ ๊ด๊ณ๋ฅผ ๋ค๋ฃฐ ๋ ์ ์ฉํ ์ ์์ต๋๋ค. ํ๋์ ํฐ JOIN ๋์ , SQLAlchemy๋ ์ด๊ธฐ ์ฟผ๋ฆฌ๋ฅผ ์คํํ ๋ค์ ๋ณ๋์ ์ฟผ๋ฆฌ(์๋ธ์ฟผ๋ฆฌ)๋ฅผ ์คํํ์ฌ ๊ด๋ จ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ํฉ๋๋ค. ๊ฒฐ๊ณผ๋ ๋ฉ๋ชจ๋ฆฌ์์ ๊ฒฐํฉ๋ฉ๋๋ค.
์์:
from sqlalchemy.orm import subqueryload
authors = session.query(Author).options(subqueryload(Author.books)).all()
for author in authors:
print(f"Author: {author.name}")
for book in author.books:
print(f" - {book.title}")
์๋ธ์ฟผ๋ฆฌ ๋ก๋ฉ์ ์นดํ ์์ ๊ณฑ๊ณผ ๊ฐ์ JOIN์ ํ๊ณ๋ฅผ ํผํ์ง๋ง, ์ ์ ์์ ๊ด๋ จ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ง ๋จ์ํ ๊ด๊ณ์ ๊ฒฝ์ฐ ์กฐ์ธ ๋ก๋ฉ๋ณด๋ค ๋ ํจ์จ์ ์ผ ์ ์์ต๋๋ค. ์ฌ๋ฌ ์์ค์ ๊ด๊ณ๋ฅผ ๋ก๋ํด์ผ ํ ๋ ํนํ ์ ์ฉํ๋ฉฐ, ๊ณผ๋ํ JOIN์ ๋ฐฉ์งํฉ๋๋ค.
3. ์ ๋ ํธ์ธ ๋ก๋ฉ (Selectin Loading): ํ๋์ ์ธ ์๋ฃจ์
SQLAlchemy 1.4์ ๋์ ๋ ์ ๋ ํธ์ธ ๋ก๋ฉ์ ์ผ๋๋ค ๊ด๊ณ์ ๋ํ ์๋ธ์ฟผ๋ฆฌ ๋ก๋ฉ์ ๋ ํจ์จ์ ์ธ ๋์์ ๋๋ค. ์ด๋ SELECT...IN ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ์ฌ ๋ถ๋ชจ ๊ฐ์ฒด์ ๊ธฐ๋ณธ ํค๋ฅผ ์ฌ์ฉํ์ฌ ๋จ์ผ ์ฟผ๋ฆฌ์์ ๊ด๋ จ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ต๋๋ค. ์ด ๋ฐฉ์์ ํนํ ๋ง์ ์์ ๋ถ๋ชจ ๊ฐ์ฒด๋ฅผ ๋ค๋ฃฐ ๋ ์๋ธ์ฟผ๋ฆฌ ๋ก๋ฉ์ ์ ์ฌ์ ์ธ ์ฑ๋ฅ ๋ฌธ์ ๋ฅผ ํผํฉ๋๋ค.
์์:
from sqlalchemy.orm import selectinload
authors = session.query(Author).options(selectinload(Author.books)).all()
for author in authors:
print(f"Author: {author.name}")
for book in author.books:
print(f" - {book.title}")
์ ๋ ํธ์ธ ๋ก๋ฉ์ ํจ์จ์ฑ๊ณผ ๋จ์์ฑ ๋๋ฌธ์ ์ผ๋๋ค ๊ด๊ณ์์ ์ ํธ๋๋ ์ฆ์ ๋ก๋ฉ ์ ๋ต์ ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ์๋ธ์ฟผ๋ฆฌ ๋ก๋ฉ๋ณด๋ค ๋น ๋ฅด๋ฉฐ, ๋งค์ฐ ํฐ JOIN์ ์ ์ฌ์ ์ธ ๋ฌธ์ ๋ฅผ ํผํฉ๋๋ค.
์ฆ์ ๋ก๋ฉ์ ์ฅ์ :
- N+1 ๋ฌธ์ ์ ๊ฑฐ: ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๋ณต ํ์๋ฅผ ์ค์ฌ ์ฑ๋ฅ์ ํฌ๊ฒ ํฅ์์ํต๋๋ค.
- ํฅ์๋ ์ฑ๋ฅ: ํนํ ๊ด๋ จ ๋ฐ์ดํฐ๊ฐ ์์ฃผ ์ ๊ทผ๋ ๋, ๋ฏธ๋ฆฌ ๊ด๋ จ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒ์ด ์ง์ฐ ๋ก๋ฉ๋ณด๋ค ๋ ํจ์จ์ ์ผ ์ ์์ต๋๋ค.
- ์์ธก ๊ฐ๋ฅํ ์ฟผ๋ฆฌ ์คํ: ์ฟผ๋ฆฌ ์ฑ๋ฅ์ ์ดํดํ๊ณ ์ต์ ํํ๊ธฐ ๋ ์ฝ๊ฒ ๋ง๋ญ๋๋ค.
์ฆ์ ๋ก๋ฉ์ ๋จ์ :
- ์ด๊ธฐ ๋ก๋ฉ ์๊ฐ ์ฆ๊ฐ: ๋ชจ๋ ๊ด๋ จ ๋ฐ์ดํฐ๋ฅผ ํ๊บผ๋ฒ์ ๋ก๋ํ๋ฉด ์ด๊ธฐ ๋ก๋ฉ ์๊ฐ์ด ๋์ด๋ ์ ์์ผ๋ฉฐ, ํนํ ์ผ๋ถ ๋ฐ์ดํฐ๊ฐ ์ค์ ๋ก ํ์ํ์ง ์์ ๊ฒฝ์ฐ ๋์ฑ ๊ทธ๋ ์ต๋๋ค.
- ๋ ๋์ ๋ฉ๋ชจ๋ฆฌ ์๋น: ๋ถํ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ฉ๋ชจ๋ฆฌ์ ๋ก๋ํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ์๋น๊ฐ ์ฆ๊ฐํ์ฌ ์ ์ฌ์ ์ผ๋ก ์ฑ๋ฅ์ ์ํฅ์ ๋ฏธ์น ์ ์์ต๋๋ค.
- ๊ณผ๋ํ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ ๊ฐ๋ฅ์ฑ: ๊ด๋ จ ๋ฐ์ดํฐ์ ์ผ๋ถ๋ง ํ์ํ ๊ฒฝ์ฐ, ์ฆ์ ๋ก๋ฉ์ ๊ณผ๋ํ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ๋ฅผ ์ด๋ํ์ฌ ๋ฆฌ์์ค๋ฅผ ๋ญ๋นํ ์ ์์ต๋๋ค.
์ฌ๋ฐ๋ฅธ ๋ก๋ฉ ์ ๋ต ์ ํ
์ง์ฐ ๋ก๋ฉ๊ณผ ์ฆ์ ๋ก๋ฉ ์ฌ์ด์ ์ ํ์ ํน์ ์ ํ๋ฆฌ์ผ์ด์ ์๊ตฌ ์ฌํญ ๋ฐ ๋ฐ์ดํฐ ์ ๊ทผ ํจํด์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋๋ค. ๋ค์์ ์์ฌ ๊ฒฐ์ ๊ฐ์ด๋์ ๋๋ค:
์ง์ฐ ๋ก๋ฉ์ ์ฌ์ฉํด์ผ ํ ๋:
- ๊ด๋ จ ๋ฐ์ดํฐ์ ๊ฑฐ์ ์ ๊ทผํ์ง ์์ ๋. ๊ด๋ จ ๋ฐ์ดํฐ๊ฐ ํ์ํ ๊ฒฝ์ฐ๊ฐ ์ ๋ค๋ฉด ์ง์ฐ ๋ก๋ฉ์ด ๋ ํจ์จ์ ์ผ ์ ์์ต๋๋ค.
- ์ด๊ธฐ ๋ก๋ฉ ์๊ฐ์ด ์ค์ํ ๋. ์ด๊ธฐ ๋ก๋ฉ ์๊ฐ์ ์ต์ํํด์ผ ํ๋ค๋ฉด, ์ง์ฐ ๋ก๋ฉ์ ๊ด๋ จ ๋ฐ์ดํฐ ๋ก๋ฉ์ ํ์ํ ๋๊น์ง ์ฐ๊ธฐํ๋ ์ข์ ์ ํ์ด ๋ ์ ์์ต๋๋ค.
- ๋ฉ๋ชจ๋ฆฌ ์๋น๊ฐ ์ฃผ์ ๊ด์ฌ์ฌ์ผ ๋. ๋๊ท๋ชจ ๋ฐ์ดํฐ์ ์ ๋ค๋ฃจ๊ณ ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ ํ์ ์ด๋ผ๋ฉด ์ง์ฐ ๋ก๋ฉ์ด ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ์ค์ด๋ ๋ฐ ๋์์ด ๋ ์ ์์ต๋๋ค.
์ฆ์ ๋ก๋ฉ์ ์ฌ์ฉํด์ผ ํ ๋:
- ๊ด๋ จ ๋ฐ์ดํฐ์ ์์ฃผ ์ ๊ทผํ ๋. ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ๊ด๋ จ ๋ฐ์ดํฐ๊ฐ ํ์ํ๋ค๋ ๊ฒ์ ์๋ค๋ฉด, ์ฆ์ ๋ก๋ฉ์ N+1 ๋ฌธ์ ๋ฅผ ์ ๊ฑฐํ๊ณ ์ ๋ฐ์ ์ธ ์ฑ๋ฅ์ ํฅ์์ํฌ ์ ์์ต๋๋ค.
- ์ฑ๋ฅ์ด ์ค์ํ ๋. ์ฑ๋ฅ์ด ์ต์ฐ์ ์ด๋ผ๋ฉด, ์ฆ์ ๋ก๋ฉ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๋ณต ํ์๋ฅผ ํฌ๊ฒ ์ค์ผ ์ ์์ต๋๋ค.
- N+1 ๋ฌธ์ ๋ฅผ ๊ฒช๊ณ ์์ ๋. ๋ง์ ์์ ์ ์ฌํ ์ฟผ๋ฆฌ๊ฐ ์คํ๋๋ ๊ฒ์ ๋ฐ๊ฒฌํ๋ค๋ฉด, ์ฆ์ ๋ก๋ฉ์ ์ฌ์ฉํ์ฌ ํด๋น ์ฟผ๋ฆฌ๋ค์ ๋จ์ผํ๊ณ ๋ ํจ์จ์ ์ธ ์ฟผ๋ฆฌ๋ก ํตํฉํ ์ ์์ต๋๋ค.
ํน์ ์ฆ์ ๋ก๋ฉ ์ ๋ต ๊ถ์ฅ ์ฌํญ:
- ์กฐ์ธ ๋ก๋ฉ: ๊ด๋ จ ๋ฐ์ดํฐ ์์ด ์ ์ ์ผ๋์ผ ๋๋ ์ผ๋๋ค ๊ด๊ณ์ ์ฌ์ฉํ์ธ์. ์ฃผ์ ๋ฐ์ดํฐ๊ฐ ์ผ๋ฐ์ ์ผ๋ก ํ์ํ ์ฌ์ฉ์ ๊ณ์ ์ ์ฐ๊ฒฐ๋ ์ฃผ์์ ์ด์์ ์ ๋๋ค.
- ์๋ธ์ฟผ๋ฆฌ ๋ก๋ฉ: ๋ณต์กํ ๊ด๊ณ ๋๋ JOIN์ด ๋นํจ์จ์ ์ผ ์ ์๋ ๋๋์ ๊ด๋ จ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃฐ ๋ ์ฌ์ฉํ์ธ์. ๊ฐ ๊ฒ์๋ฌผ์ ์๋น์์ ๋๊ธ์ด ์์ ์ ์๋ ๋ธ๋ก๊ทธ ๊ฒ์๋ฌผ์ ๋๊ธ์ ๋ก๋ํ๋ ๋ฐ ์ข์ต๋๋ค.
- ์ ๋ ํธ์ธ ๋ก๋ฉ: ํนํ ๋ง์ ์์ ๋ถ๋ชจ ๊ฐ์ฒด๋ฅผ ๋ค๋ฃฐ ๋ ์ผ๋๋ค ๊ด๊ณ์ ์ฌ์ฉํ์ธ์. ์ด๋ ์ผ๋๋ค ๊ด๊ณ์ ์ฆ์ ๋ก๋ฉ์ ๋ํ ์ต์ ์ ๊ธฐ๋ณธ ์ ํ์ธ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค.
์ค์ฉ์ ์ธ ์์ ๋ฐ ๋ชจ๋ฒ ์ฌ๋ก
์ค์ ์๋๋ฆฌ์ค๋ฅผ ๊ณ ๋ คํด ๋ด ์๋ค: ์ฌ์ฉ์๋ค์ด ์๋ก๋ฅผ ํ๋ก์ฐํ ์ ์๋ ์์ ๋ฏธ๋์ด ํ๋ซํผ์ ๋๋ค. ๊ฐ ์ฌ์ฉ์๋ ํ๋ก์ ๋ชฉ๋ก๊ณผ ํ๋ก์ ๋ชฉ๋ก(ํ๋ก์ฐํ๋ ์ฌ์ฉ์)์ ๊ฐ์ง๊ณ ์์ต๋๋ค. ์ฌ์ฉ์ ํ๋กํ๊ณผ ํจ๊ป ํ๋ก์ ์ ๋ฐ ํ๋ก์ ์๋ฅผ ํ์ํ๋ ค๊ณ ํฉ๋๋ค.
๋จ์ํ (์ง์ฐ ๋ก๋ฉ) ์ ๊ทผ ๋ฐฉ์:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String)
followers = relationship("User", secondary='followers_association', primaryjoin='User.id==followers_association.c.followee_id', secondaryjoin='User.id==followers_association.c.follower_id', backref='following')
followers_association = Table('followers_association', Base.metadata, Column('follower_id', Integer, ForeignKey('users.id')), Column('followee_id', Integer, ForeignKey('users.id')))
user = session.query(User).filter_by(username='john_doe').first()
follower_count = len(user.followers) # Triggers a lazy-loaded query
followee_count = len(user.following) # Triggers a lazy-loaded query
print(f"User: {user.username}")
print(f"Follower Count: {follower_count}")
print(f"Following Count: {followee_count}")
์ด ์ฝ๋๋ ์ฌ์ฉ์ ๊ฒ์์ ์ํ ํ๋์ ์ฟผ๋ฆฌ์ ํ๋ก์ ๋ฐ ํ๋ก์ ๊ฒ์์ ์ํ ๋ ๊ฐ์ ์ถ๊ฐ ์ฟผ๋ฆฌ, ์ด ์ธ ๊ฐ์ ์ฟผ๋ฆฌ๋ฅผ ๋ฐ์์ํต๋๋ค. ์ด๋ N+1 ๋ฌธ์ ์ ํ ์์ ๋๋ค.
์ต์ ํ๋ (์ฆ์ ๋ก๋ฉ) ์ ๊ทผ ๋ฐฉ์:
user = session.query(User).options(selectinload(User.followers), selectinload(User.following)).filter_by(username='john_doe').first()
follower_count = len(user.followers)
followee_count = len(user.following)
print(f"User: {user.username}")
print(f"Follower Count: {follower_count}")
print(f"Following Count: {followee_count}")
`followers`์ `following` ๋ชจ๋์ `selectinload`๋ฅผ ์ฌ์ฉํจ์ผ๋ก์จ, ํ์ํ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ๋จ์ผ ์ฟผ๋ฆฌ(์ด๊ธฐ ์ฌ์ฉ์ ์ฟผ๋ฆฌ ํฌํจ, ์ด ๋ ๊ฐ)๋ก ๊ฐ์ ธ์ต๋๋ค. ์ด๋ ํนํ ๋ง์ ์์ ํ๋ก์์ ํ๋ก์์ ๊ฐ์ง ์ฌ์ฉ์์๊ฒ ์ฑ๋ฅ์ ํฌ๊ฒ ํฅ์์ํต๋๋ค.
์ถ๊ฐ ๋ชจ๋ฒ ์ฌ๋ก:
- ํน์ ์ปฌ๋ผ์ `with_entities` ์ฌ์ฉ: ํ ์ด๋ธ์์ ๋ช ๊ฐ์ ์ปฌ๋ผ๋ง ํ์ํ ๊ฒฝ์ฐ, `with_entities`๋ฅผ ์ฌ์ฉํ์ฌ ๋ถํ์ํ ๋ฐ์ดํฐ ๋ก๋ฉ์ ํผํ์ธ์. ์๋ฅผ ๋ค์ด, `session.query(User.id, User.username).all()`์ ID์ ์ฌ์ฉ์ ์ด๋ฆ๋ง ๊ฒ์ํฉ๋๋ค.
- ์ธ๋ฐํ ์ ์ด๋ฅผ ์ํด `defer` ๋ฐ `undefer` ์ฌ์ฉ: `defer` ์ต์ ์ ํน์ ์ปฌ๋ผ์ด ์ด๊ธฐ์ ๋ก๋๋๋ ๊ฒ์ ๋ฐฉ์งํ๊ณ , `undefer`๋ ํ์ํ ๋ ๋์ค์ ๋ก๋ํ ์ ์๋๋ก ํฉ๋๋ค. ์ด๋ ํญ์ ํ์ํ์ง ์์ ๋์ฉ๋ ๋ฐ์ดํฐ(์: ํฐ ํ ์คํธ ํ๋ ๋๋ ์ด๋ฏธ์ง)๋ฅผ ํฌํจํ๋ ์ปฌ๋ผ์ ์ ์ฉํฉ๋๋ค.
- ์ฟผ๋ฆฌ ํ๋กํ์ผ๋ง: SQLAlchemy์ ์ด๋ฒคํธ ์์คํ ๋๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ๋กํ์ผ๋ง ๋๊ตฌ๋ฅผ ์ฌ์ฉํ์ฌ ๋๋ฆฐ ์ฟผ๋ฆฌ ๋ฐ ์ต์ ํ ์์ญ์ ์๋ณํ์ธ์. `sqlalchemy-profiler`์ ๊ฐ์ ๋๊ตฌ๊ฐ ๋งค์ฐ ์ ์ฉํ ์ ์์ต๋๋ค.
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ธ๋ฑ์ค ์ฌ์ฉ: ์ฟผ๋ฆฌ ์คํ ์๋๋ฅผ ๋์ด๊ธฐ ์ํด ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ์ ์ ์ ํ ์ธ๋ฑ์ค๊ฐ ์๋์ง ํ์ธํ์ธ์. JOIN ๋ฐ WHERE ์ ์ ์ฌ์ฉ๋๋ ์ปฌ๋ผ์ ์ธ๋ฑ์ค์ ํนํ ์ฃผ์๋ฅผ ๊ธฐ์ธ์ด์ธ์.
- ์บ์ฑ ๊ณ ๋ ค: ์์ฃผ ์ ๊ทผํ๋ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ถํ๋ฅผ ์ค์ด๊ธฐ ์ํด ์บ์ฑ ๋ฉ์ปค๋์ฆ(์: Redis ๋๋ Memcached ์ฌ์ฉ)์ ๊ตฌํํ์ธ์. SQLAlchemy๋ ์บ์ฑ์ ์ํ ํตํฉ ์ต์ ์ ์ ๊ณตํฉ๋๋ค.
๊ฒฐ๋ก
์ง์ฐ ๋ก๋ฉ๊ณผ ์ฆ์ ๋ก๋ฉ์ ๋ง์คํฐํ๋ ๊ฒ์ ํจ์จ์ ์ด๊ณ ํ์ฅ ๊ฐ๋ฅํ SQLAlchemy ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ฑํ๋ ๋ฐ ํ์์ ์ ๋๋ค. ์ด๋ฌํ ์ ๋ต ๊ฐ์ ์ฅ๋จ์ ์ ์ดํดํ๊ณ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ์ ์ฉํจ์ผ๋ก์จ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ๋ฅผ ์ต์ ํํ๊ณ N+1 ๋ฌธ์ ๋ฅผ ์ค์ด๋ฉฐ ์ ๋ฐ์ ์ธ ์ ํ๋ฆฌ์ผ์ด์ ์ฑ๋ฅ์ ํฅ์์ํฌ ์ ์์ต๋๋ค. ์ต์ ์ ๊ฒฐ๊ณผ๋ฅผ ์ป์ผ๋ ค๋ฉด ์ฟผ๋ฆฌ๋ฅผ ํ๋กํ์ผ๋งํ๊ณ , ์ ์ ํ ์ฆ์ ๋ก๋ฉ ์ ๋ต์ ์ฌ์ฉํ๋ฉฐ, ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ธ๋ฑ์ค์ ์บ์ฑ์ ํ์ฉํด์ผ ํฉ๋๋ค. ํต์ฌ์ ํน์ ์๊ตฌ ์ฌํญ๊ณผ ๋ฐ์ดํฐ ์ ๊ทผ ํจํด์ ๋ฐ๋ผ ์ฌ๋ฐ๋ฅธ ์ ๋ต์ ์ ํํ๋ ๊ฒ์ ๋๋ค. ํนํ ๋ค๋ฅธ ์ง๋ฆฌ์ ์ง์ญ์ ๋ถ์ฐ๋ ์ฌ์ฉ์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๋ค๋ฃฐ ๋ ์ ํ์ด ๋ฏธ์น๋ ์ ์ญ์ ์ธ ์ํฅ์ ๊ณ ๋ คํ์ธ์. ์ผ๋ฐ์ ์ธ ๊ฒฝ์ฐ์ ์ต์ ํํ๋, ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋ฐ์ ํ๊ณ ๋ฐ์ดํฐ ์ ๊ทผ ํจํด์ด ๋ณ๊ฒฝ๋จ์ ๋ฐ๋ผ ๋ก๋ฉ ์ ๋ต์ ํญ์ ์กฐ์ ํ ์ค๋น๋ฅผ ํด์ผ ํฉ๋๋ค. ์ฟผ๋ฆฌ ์ฑ๋ฅ์ ์ ๊ธฐ์ ์ผ๋ก ๊ฒํ ํ๊ณ ์ด์ ๋ฐ๋ผ ๋ก๋ฉ ์ ๋ต์ ์กฐ์ ํ์ฌ ์๊ฐ์ด ์ง๋จ์ ๋ฐ๋ผ ์ต์ ์ ์ฑ๋ฅ์ ์ ์งํ์ธ์.