Django ORM (QuerySet)구조와 원리 그리고 최적화전략 - 김성렬 - PyCon Korea 2020를 보며 간단히 정리한 내용입니다.
QuerySet은 코드에 존재할 때 바로 불러오는 것이 아닌 필요한 시점에 불러온다. 그렇다면그 시점은 언제일까?
위의 Django 공식문서 내용을 보면 알 수 있다. 변수(q)에 저장되는 시점부터 호출되는 것이 아닌 실제로 호출되는 시점은 print(q) 한 번 이다. 한 마디로 변수에 담거나 하는 동작이 아닌 실제 DB에 접근하여 ORM이 동작하는 경우를 말한다.
Django ORM은 DB에서 데이터를 가져와 인스턴스화 하면 _result_Cache 라는 속성에 리스트 형태로 저장한다. 이 때, 이미 불러온 QuerySet을 다시 호출하면 DB에 접근하는 방식이 아닌, 이미 불러온 값을 저장하여 사용하는데, 이러한 방식을 Caching이라고 한다.
위와 같은 ORM의 편리함으로 인해 필요한 만큼의 QuerySet을 호출할 수 있지만, 문제가 존재한다.
QuerySet을 호출하는 방법에는 다양한 방법이 존재하는데, 아래와 같은 경우에서 N+1이라는 문제가 발생한다.
shirts = Shirt.objects.all()
for shirt in shirts:
print(shirt.brand.designer)
위의 for문이 동작할 때 첫 번째 시점에서는 shirt만 필요로 하므로 Shirt를 호출할 때 한 번, 지금 시점에서 필요한 designer를 호출하기 위해 shirts의 QuerySet만큼 N번 해서 총 1+N번 만큼 DB에 접근한다. 단순히 참조를 한 번 더 하는 것으로 인해 N번 만큼의 불필요한 DB호출이 생성된 것이다. 이 문제를 N+1 문제라고 한다.
N+1 문제를 해결하기 위해 나온 방식이 Eager Loading이다.
select_related | prefetch_related 두 가지 방식이 존재하며 간단한 정의는 아래와 같이 정리할 수 있다.
select_related는 정참조에서 사용 가능하며 ORM을 통해 불러온 테이블에서 어떤 테이블을 바라볼 지 정할 수 있다. SQL의 join과 같은 기능을 한다.
shirts = Shirt.objects.all().select_related("brand")
for brand in shirts:
print(brand.designer)
위와 같은 코드를 작성할 시 한 번의 QuerySet 호출로 Caching 처리하여 해결할 수 있다.
prefetch_related는 정|역참조에서 모두 사용 가능하며 Caching된 데이터에 존재하지 않는다면 추가 Query를 발생한다.
brand = Brand.Objects.all().prefetch_related("shirt_set")
위와 같은 형태로 사용할 수 있다. 또한, prefetch_related는 조건부를 사용할 수 있는데 이 내용은 실제 사용하면서 적용해보자.