Django

ORM 최적화

traveler_JH 2022. 7. 5. 16:16
  • selected_related: 정참조 관계에서 사용
  • prefetch_related: 모든 관게에서 사용 가능
  • 성능확인

settings.py 안에 logging 설정 → api 통신이 있을 때 디비 호출하는 경우가 있다면 터미널에 로그로 남겨줌. 마찬가지로 쉘에서도 확인할 수 있음.

**from django.db import connection

  • Lazy Loading:

장고에만 있는 개념이 아니고 프로그래밍 전반적인 개념. python에서 generator 사용해서 구현하기도 함.

쿼리셋을 슬라이싱, 이터레이트, repr(), len(), list(), bool 등을 할 때 평가해서 디비에 접근함. → 해당 함수들이 실행되야 접근 → 지연 로딩

  • “query” 라는 속성이 있음, 지연 로딩이 일어날 때 쿼리가 어디에 저장되어 있을까?

쿼리셋에 쿼리라는 속성안에 sql문이 저장되어 있음

queryset = Publisher.objects.all()
queryset2 = queryset.exclude(id=2).annotate(count=Count('book))

# 총 4번 디비에 접근: 인덱스로 접근할 때마다 한 번씩 접근한다고 보면 됨
queryset2[0]
queryset2[0]
queryset2[0]
list(queryset[0])

# 총 1번 접근함 (caching 기능)
list(queryset[0])
queryset2[0]
queryset2[0]
queryset2[0]
  • caching 기능

쿼리셋 평가시 캐싱을 하는 경우와 안하는 경우가 있음

→ list, for문을 돌리는 경우는 캐싱을 함. 그래서 리스트를 먼저 선언하고 그 후에 인덱스로 요청을 하면 추가적인 디비 접근이 필요없고 처음 접근한 리스트에 접근해서 결과를 가져올 수 있음.

  • result_cache

디비에서 가져온 결과를 캐시해 놓는 기능.

# result_cache에 저장 x
queryset2[0]
queryset2[0]
queryset2[0]
list(queryset[0])

# result_cached에 저장
for publisher in queryset:
	a = publisher.id

→ for 문을 돌린 후에는 리절트 캐시에 저장되기 때문에 그 후에 인덱스로 접근해도 더이상 디비에 접근이 이루어지지 않음.

  • N+1 문제

참조하고 있는 다른 테이블의 내용을 가져올 때 디비를 히팅하는 횟수가 올라감

→ 해결방법: eager loading: 한 번에 모든 정보를 가져와서 캐싱해서 사용할 수 있게 함

  • select_related: 하나의 쿼리로 묶어서 보내는 것. join문과 비슷
  • prefetch_related: 이게 실행되기 전까지는 쿼리에 저장해두고 이게 실행된 후에는 prefetch_related_lookups이라는 필드에 튜플 형태로 어떤 테이블을 대치 로딩할지가 저장되어 있음. 즉 2번의 쿼리로 보냄
  • prefetch_related 사용 후에 filter를 사용해서 정보를 얻어올 때 다시 n+1 문제가 생길 수 있음

→ prefetch_related를 사용하면 캐싱이 되는데 filter를 사용하면 장고에서는 다른 것으로 이해해서 다시 where in 문을 사용해서 디비에 다시 접근하게 됨.

queryset = Store.objects.prefetch_related(
	Prefetch('books', queryset = Book.objects.all(), to_attr='total_books'),
	Prefetch('books', queryset = Book.objects.filter(name=~), to_attr='filtered_books')
)

# 위와 같이 쓰면 prefetch_related_lookups에 두 개가 저장됨

#####
queryset = Store.objects.prefetch_related('books')
= queryset = Store.objects.prefetch_related(
	Prefetch('books', queryset = Book.objects.all(), to_attr='books'),
	)

→ 위와 같이 사용해서 all()을 해서 가져온 정보는 total_books 라고 하는 속성에 캐싱하고, filter를 건 정보는 filtered_books 라고 하는 속성에 따로 저장하라고 해주면 문제가 해결 됨.

부하테스트

loadtest -n 3 http:~

: 해당 엔드포인트로 세 개의 엔드포인트를 보낼 때의 부하의 정도를 알 수 있음

'Django' 카테고리의 다른 글

Django QuerySet API  (0) 2022.07.05
정참조 역참조  (0) 2022.07.05
CRUD  (0) 2022.07.05
httpie  (0) 2022.07.05
Django views 함수형 vs 클래스형 & 제네릭  (0) 2022.07.05