99클럽 코테 스터디 15일차 TIL DB N+1 문제
728x90
  • DB
    • Index
      • Index 작동 원리
      • Index 종류
        • B-Tree 인덱스
        • Hash Indexes
        • Bitmap Index
        • GIST(Generalized Search Tree)
        • R-Tree
        • Full Text Index
        • Spatial Index
        • Trie(Prefix Tree) Index
        • Covering Index
  • 영속성
  • 트랜잭션
  • ORM
  • ACID
  • N+1 문제
  • DB 정규화
  • Data Replication
  • sharding 전략
  • CAP 이론

란?

  • DB 쿼리의 성능 최적화와 관련된 일반적인 문제.
  • 특히, ORM 프레임워크를 사용할 때 자주 발생함. 주로 데이터베이스에 불필요하게 많은 쿼리를 발생시키기 때문에 성능 저하를 유발함.

원인

  1. N 개의 부모 엔티티와 각각의 부모 엔티티에 관련된 자식 엔티티들
    • 예시상황
      • 예를 들어, 블로그 시스템에서 여러 블로그 글(부모 엔티티)을 가져오고, 각 글마다 여러 댓글(자식 엔티티)을 가져오는 상황.
    • 문제 근원
      • 초기 쿼리 : 모든 부모 엔티티를 가져오는 단일 쿼리가 실행됨. 예를 들어, SELECT * FROM posts
      • 추가 쿼리 : 각 부모 엔티티에 대해 관련된 자식 엔티티를 가져오기 위해 추가 쿼리가 실행됨. 예를 들어, 각 post 에 대해 SELECT * FROM comments WHERE post_id = ?
        이 때, 만약 10개의 포스트를 가져오면, 총 11개의 쿼리가 실행됨.(1개의 부모 쿼리 + 10개의 자식 쿼리).
        이 문제가 발생하는 이유는 ORM이 각 부모 엔티티에 대해 자식 엔티티를 개별적으로 가져오기 때문임.

발생 후 대처법

확인 방법

  1. 로그 분석
    • DB log 나 ORM 쿼리 로그를 분석하여 동일한 유형의 쿼리가 반복적으로 실행되는지 확인함.
  2. 프로파일링 도구 사용
    • SQLAlchemy, Django ORM 등에서 제공하는 프로파일링 도구를 사용하여 쿼리 실행 패턴을 분석함.

대처

  1. 모니터링
    • 응답 시간이 긴 쿼리나 성능 병목을 일으키는 부분을 모니터링함.
  2. 캐싱 사용
    • 반복적으로 동일한 데이터를 조회하는 경우, 캐싱을 통해 데이터베이스 접근을 줄일 수 있음.

해결방법

  1. Eager Loading
    • 처음부터 필요한 모든 데이터를 한꺼번에 가져오는 방법
  • SQLAlchemy
    joinedload 또는 subqueryload 를 사용하여 관련 데이터를 미리 로드할 수 있음.
from sqlalchemy.orm import joinedload
posts = session.query(Post).options(joinedload(Post.comments)).all()
  • Django ORM
    select_relate 또는 prefetch_related 를 사용하여 관련 데이터를 미리 로드할 수 있음.
posts = Post.objects.select_related('author').all()  # ForeignKey 관계
posts = Post.objects.prefetch_related('comments').all()  # Many-to-Many 관계
  1. Batch Processing
    • 한 번에 여러 개의 엔티티를 처리하도록 쿼리를 조정하는 방법. 이를 통해 반복되는 쿼리 호출을 줄일 수 있음.
  • In-Clause 사용
    • 여러 개의 ID를 한 번에 가져오는 쿼리를 작성하여 필요한 데이터를 한꺼번에 가져옴.
SELECT * FROM comments WHERE post_id IN (1,2,3,4,5);
  1. 데이터 모델링 최적화
    • 데이터 모델링을 최적화하여 불필요한 쿼리 발생을 줄임. 예를 들어, 데이터 정규화나 역정규화를 통해 자주 사용하는 데이터를 함께 저장함.
  2. 캐싱 도입
    • 자주 조회되는 데이터는 캐시에 저장하여 데이터베이스 접근을 최소화함.
    • Redis, Memcached 같은 캐시 서버를 사용할 수 있음.
  • Django 캐시 프레임워크
from django.core.cache import cache
posts = cache.get('all_posts')
if not posts:
    posts = Post.objects.all()
    cache.set('all_post', posts)
728x90