[django] 쿼리문과 성능: select_related() 와 prefetch_related()

김기용·2020년 11월 15일
0
post-thumbnail

⚡️쿼리문과 성능


장고 기초 쿼리문에 조금 익숙해졌을 무렵 😎 데이터베이스로 날리는 쿼리의 개수가 웹 서비스 성능에 문제를 준다는 사실을 알게 되었다.🤭


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! id1인 모든 블로그와 관련된 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') //🎯🎯
profile
매일 새로운 배움을 통해 꾸준히 성장하는 것을 목표를 두고 있습니다. 논리적인 사고로 문제해결 하는것에 희열을 느끼고 언젠가 제가 만든 결과물들이 사람들에게 편이를 제공하며 사용되는 날을 간절히 소망하고 있습니다. 🙏

1개의 댓글

comment-user-thumbnail
2020년 11월 22일

정말 대단하시네요! 멋있습니다 -젬마

답글 달기