Django ORM 최적화

GreenBean·2021년 10월 7일
0
post-thumbnail

Django ORM 최적화

[PyCon Korea 2020] Django ORM (QuerySet) 구조와 원리 그리고 최적화 전략

QuerySet을 통해 알아보는 ORM의 특징

  • 쿼리셋1개의 메인 쿼리0~N개의 추가 쿼리셋으로 구성
  • Lazy Loading
    • 쿼리셋을 선언할 때는 쿼리셋 자체로만 존재하며 실제 SQL이 실행되는 시점은 그 쿼리셋이 사용되었을 때
    • 당장 필요하지 않으면 호출을 지연하는 특성으로 꼭 필요한 시점에만 필요한 만큼 SQL이 호출됨
    • 재사용하지 못함으로 불필요한 쿼리가 더 호출될 수 있음
    • 호출하는 순서만 바뀌는 것으로 쿼리셋 캐싱을 통해 개선 가능
  • Caching
    • 쿼리셋 캐싱을 재사용할 수 있음
      • 모든 user를 먼저 캐싱해두고, 필요한 특정 유저 정보를 가져올 때 앞의 변수를 활용하면 추가 쿼리를 호출하지 않음
  • Eager Loading (즉시로딩, N+1 Problem)
    • for문이 돌면서 조회할 때마다 SQL이 계속 호출되는 문제를 N+1 Problem이라고 함
      • user가 100명 있으면 for문으로 userinfo를 얻기 위해서는 총 100+1번 쿼리가 호출되는 현상
      • (유저를 호출하는 sql 한번) + (유저 개수 N) = (N+1개의 쿼리)
    • 즉시로딩을 하기위해, 즉, (N+1) 해결을 위해 Django에서는 select_realated()prefetched_related() 메서드를 사용 가능
    • 호출할 때 result cache에 원하는 데이터가 없으면 쿼리셋 호출
      • select_realated() : 조인을 통해 즉시로딩
      • prefetched_related() : 추가 쿼리를 사용하여 즉시로딩
        • prefetched_related에 선언한 속성 개수만큼 쿼리셋이 추가로 호출됨
      • 역참조는 select_related 옵션을 줄 수 없음 (Django에서 제약이 있는 부분)
  • 테스트 할 때 assertNumQueries()로 테스트 케이스를 작성하는 경우가 많지만, 매번 체크를 해줘야하고 N+1문제로 인한 크리티컬한 성능 이슈만 커버하기 때문에 captureQueriesContext를 활용하는 것도 도움됨

QuerySet 사용에서 실수하기 쉬운 점들

주요 변수

  • _result_cache_
    • 호출된 데이터를 저장하는 변수
    • 만약 찾는 데이터가 여기 없으면 추가 쿼리셋 수행
  • prefetch_related_lookups
    • 추가 쿼리셋의 타겟
  • iterable_class
    • 쿼리셋의 반환 타입 결정(default는 Model)

주의 사항

  • prefetch_related()filter()는 완전 별개
    • prefetched_related()는 추가 쿼리셋에서 제어
    • filter()는 새로운 쿼리셋이 아니라 한 개 쿼리셋 안에서 제어
    • filter의 조건절은 WHERE절을 붙히기 위해 메인쿼리에서 INNER JOIN을 사용하게 되고, 추가 쿼리에서 한번 더 조회하므로 비효율적
  • annotateselect_relatedfilterprefetch_related 순서가 실제 SQL 순서와 가장 유사하므로 이 순서로 쿼리셋을 작성하는 것을 추천
  • 쿼리셋 캐시를 재활용하지 못할 때가 있음
    • .all()로 질의하면 캐시를 재활용하지만, 특정 상품을 찾으려고 하면 캐시를 재사용하지 않고 SQL로 질의
    • 쿼리셋을 재호출하지 않으려면 .all()로 불러온 것에서 if절을 활용하여 리스트 컴프리헨션으로 빼오는 것을 추천
  • raw 쿼리셋은 쿼리셋의 또 다른 유형이기 때문에 prefetch_related(), Prefetch() 사용이 가능
profile
🌱 Backend-Dev | hwaya2828@gmail.com

0개의 댓글