장고 기초 쿼리문에 조금 익숙해졌을 무렵 😎 데이터베이스로 날리는 쿼리의 개수가 웹 서비스 성능에 문제를 준다는 사실을 알게 되었다.🤭
class Blog(models.Model):
author = models.ForeignKey('User', on_delete=models.CASCADE)
title = models.CharField(max_length=200)
class User(models.Model):
username = models.CharField(max_length=200)
위와 같이 Blog 와 User 모델이 있다고 가정하고 쿼리문이 데이터베이스를 때리는🎯 과정을 살펴본다면..
(나는..데이터베이스가 많이 맞은 만큼 성능저하 발생한다고 이해했다..)
Bad!
blog = Blog.objects.get(id=1) // 🎯 Hit! Blog 오브젝트를 가져온다
author_name = blog.author.username // 🎯 Hit! Blog 오브젝트와 연결된 User 모델을 참조한다.
위 코드에서는 2번의 쿼리문이 발생한다. 데이터 베이스가 2대 맞은 것이다. 하지만 ⚡️select_related()문을 사용한다면 데이터베이스가 한번 맞는걸로 이과정을 퉁칠 수 있다.
Good!
blog = Blog.objects.select_related('author').get(id=1) //🎯 Hit! id가 1인 모든 블로그와 관련된 author를 가져오는 쿼리문
author_name = blog.author.username // Blog 모델과 관련된 user 모델은 이미 blog라는 변수 안에 caching 되었기 때문에 데이터 베이스에 쿼리문을 날릴 필요가 없다!
2번때리나 1번때리나 거기서 거기일것 같다는 생각이 들겠지만 코드를 짜다보면 for문을 돌리는 코드를 사용해야할 때가 있는데...
blogs = Blog.objects.all() // 🎯 Hit!
context = [
{
'title': blog.title,
'author': blog.author.username //🎯 Hit!: 한번 loop를 돌때마다 발생
}
for blog in blogs
]
블로그 객체가 1개 일때는 문제가 없지만 1만개라고 가정 했을때 위 코드와 같이 작성한다면 블로그의 모든 객체를 ❶ blogs라는 변수에 담아 둘때 🎯Hit! ❷for loop를 한번 돌때마다 🎯Hit 하기 때문 총 10001번의 쿼리가 발생하게된다!!
아래와 같이 select_related()를 사용한다면 총 1번의 쿼리문으로 Blog객체의 수에 상관없이 User 모델의 username을 참조시 데이터베이스를 추가로 때리지 않아도 된다.
blogs = Blog.objects.select_realted('author') // 🎯 Hit!
context = [
{
'title': blog.title,
'author': blog.author.username
}
for blog in blogs
]
select_related()가 정참조시 사용되었다면, prefetch_related()는 역참조시 사용된는 옵션이다.
위에서 사용한 Blog 모델의 경우 User 모델의 username을 author라는 field로 정 참조 하고 있기 때문에 select_related()를 사용한것이고,
ManyToMany(다대다, N:M) 관계이거나 역참조(정참조의반대)시 사용되는 것이 prefetch_related()이다.
User 모델이 Blog 모델을 정참조 하고 있지 않기 때문에 Blog 모델을 참조하려면
users = User.objects.prefetch_related('blog_set')
이렇게 작성해 주어야한다! 역참조시 모델명이름뒤에 _set 붙이는것을 잊지말자!
꼭 집고 넘어가야하는 개념이 있다.
select_related()는 데이터 베이스에서 JOIN을 수행하고 오기 때문에 데이터 베이스를 한번🎯 때리지만
blogs = Blog.objects.select_related('author') // 🎯
prefetch_related() 는 두 모델을 데이터베이스에서 가져와서 파이썬 내에서 JOIN을 수행하기 때문에 2번🎯🎯의 Hit이 발생한다
users = User.objects.prefetch_related('blog_set') //🎯🎯
정말 대단하시네요! 멋있습니다 -젬마