본문 바로가기

Python

[SQLAlchemy] ORM 성능 최적화: Lazy Loading vs Eager Loading (feat. 로딩 전략)

반응형

 

SQLAlchemy ORM의 로딩 전략 옵션에 대해 알아보자.

이 개념은 단순한 옵션 차이를 넘어, 쿼리 성능, 데이터 처리 효율, N+1 문제 발생 여부에 직결된다.

 

-  Lazy Loading, Eager Loading 이란?

- 실무 예제

- 두 방식의 차이

- 언제 어떤 방식으로 써야하는가?

 

Lazy Loading이란? (필요할 때 실행)

Lazy Loading은 관계가 설정된 객체를 접근할 때마다 별도로 SELECT 쿼리를 실행하는 방식입니다.

예제 모델

class Author(Base):
    __tablename__ = 'author'
    id = Column(Integer, primary_key=True)
    books = relationship("Book", backref="author")

class Book(Base):
    __tablename__ = 'book'
    id = Column(Integer, primary_key=True)
    author_id = Column(Integer, ForeignKey("author.id"))

Lazy Loading 예시

authors = session.query(Author).all()
for author in authors:
    for book in author.books:
        print(book.title)

이 경우 SQLAlchemy는:

  • 첫 번째 쿼리로 모든 Author를 가져오고
  • author.books 접근 시마다 개별적으로 Book 쿼리를 실행합니다

문제: Author가 100명이라면, Book 쿼리는 100번 발생 → N+1 문제 발생

 

Eager Loading이란? (함께 조회)

Eager Loading은 관계 데이터를 사전에 함께 쿼리하여 불필요한 추가 쿼리를 막는 방식입니다.

SQLAlchemy는 대표적으로 두 가지 eager loading 방식을 지원합니다:

1. joinedload(): JOIN으로 한 번에 가져오기

from sqlalchemy.orm import joinedload

authors = session.query(Author).options(joinedload(Author.books)).all()
  • Author와 Book을 JOIN해서 한 번의 쿼리로 가져옴
  • SQL 예시:
  • SELECT * FROM author JOIN book ON author.id = book.author_id

2. selectinload(): 서브쿼리로 다 가져오기

from sqlalchemy.orm import selectinload

authors = session.query(Author).options(selectinload(Author.books)).all()
  • Author 먼저 가져오고,
  • Book은 WHERE author_id IN (...) 방식으로 가져옴 (두 번째 쿼리)
  • SQL 예시:
  • SELECT * FROM book WHERE author_id IN (1, 2, 3, ...);

Lazy vs Eager: 쿼리 비교

방식 쿼리 수 join 사용 중복 가능성 추천 상황
Lazy (default) 1 + N 없음 소규모 관계일 때
joinedload() 1 관계 수 많을 때 중복 로우 증가 1:N 관계가 적을 때
selectinload() 2 없음 대량 데이터 관계 처리 시
 

Eager Loading의 유의점

joinedload()

장점:

  • 한 번의 JOIN 쿼리로 전체 로딩
  • 루프 중 관계 접근 시 쿼리 추가 없음

주의점:

  • 카디널리티 폭발:
    • 관계가 1:N이면 부모 레코드가 N번 중복됨 (메모리 부하 증가)
    • pagination 시 LIMIT/OFFSET이 의도와 다르게 작동할 수 있음
  • JOIN 대상이 많을 경우 성능 저하
    • 여러 관계를 동시에 joinedload 시 복잡한 쿼리로 인해 느려질 수 있음

예시:

  • Author 100명, 각 Author가 Book 5권 보유
  • joinedload() 시 실제 SQL 결과는 500 rows 생성
  • ORM은 중복 제거를 하더라도, 네트워크, 메모리, 쿼리 파싱 비용 증가는 피할 수 없음

selectinload()

장점:

  • 관계 수 많을 때 더 안정적 (JOIN 없이 가져옴)
  • SQL이 간결하고 메모리 낭비 없음

주의점:

  • IN 절이 너무 길어질 수 있음 (수천 개의 ID 포함 시 일부 DBMS에서 문제)
  • 깊은 관계에서 중첩 selectinload() 사용 시 쿼리 수 증가
    • 성능이 떨어질 수 있음 (예: A → B → C 연쇄 관계)

언제 어떤 방식 써야 할까?

상황 추천 방식 이유
소규모 관계 또는 단일 조회 Lazy (기본값) 간단하고 빠름
리스트 루프 안에서 관계 접근 selectinload() N+1 제거, 메모리 안정적
적은 수의 관계를 즉시 다 조회해야 할 때 joinedload() 쿼리 1회로 처리 가능
pagination 사용 또는 중복 방지 필요 selectinload() 중복 없는 구조 유지
 

마무리

lazy loading으로 인한 N+1 문제는, 관계 데이터를 순회하는 루프 안에서 반드시 확인해야 할 중요한 포인트입니다.

반응형