Django : prefetch_related, select_related 맛보기 ver.

채록·2021년 2월 24일
0

Python & Django

목록 보기
27/34
post-thumbnail

너무 많이 맞는 내 db

원래 이 내용에 대한 세션이 오늘 예정되어 있었는데

"오.... 맞지맞지.. 준비됐을때 듣는게 더 좋지"
그치만 써보고 싶었기 때문에 이전 기수분들의 녹화세션을듣고 따라해봤다!! 그래서 이번 포스팅은 맛보기 버전!




너무 많이 맞다니??
아직 전문적인 표현법에 약해서..
DB에 접근한다 = DB를 hit 한다라고 표현하는 듯 하다.

"그렇다면 내 코드에서 posting의 instance가 42개일때 몇번 db를 hit 할까요?"

먼저 실행할 로직은 다음과 같다.

실행 결과 sql에 접근하는 형태는 아래와 같다.

위 형태 아니고 저런 뭉탱이 두개가 42개 있다. 너무길어서 사진 짜른거다.

보면 Posting 으로 접근하여 뽑아낼 정보들에는 PostingComment, User class에 존재하는 정보들도 필요해 그 경로로도 계속 접근하고 있음을 볼 수 있다.




shell로 비교 (select_related) 반복문 X

상황 : posting에는 그에 대한 comment가 달려있다. 따라서 PostingComment라는 class 에는 posting_id user_id content 이 세가지 field가 존재한다.
물론 posting과 user를 FK로 갖고 있다는 뜻!! 정참조!

comment 1번 instance로부터 해당 posting을 올린 유저의 이름을 뽑아내보자
c1 = 정참조, 정참조 사용
c2 = comment쓴 유저인 'user'를 select_related
c3 = comment가 달린 'posting'을 select_related
c4 = 'user'와 'posting'모두 select_related

>> c1 = PostingComment.objects.get(id=1) 🔥
(0.001) SELECT `posting_comments`.`id`, `posting_comments`.`user_id`, `posting_comments`.`posting_id`, `posting_comments`.`content`, `posting_comments`.`created_at` FROM `posting_comments` WHERE `posting_comments`.`id` = 1 LIMIT 21; args=(1,)
>>> c1.posting.user.name 🔥 🔥
(0.001) SELECT `postings`.`id`, `postings`.`user_id`, `postings`.`image_url`, `postings`.`content`, `postings`.`created_at`, `postings`.`updated_at`, `postings`.`size_id`, `postings`.`housing_id`, `postings`.`style_id`, `postings`.`space_id` FROM `postings` WHERE `postings`.`id` = 8 LIMIT 21; args=(8,)
(0.001) SELECT `users`.`id`, `users`.`email`, `users`.`password`, `users`.`name`, `users`.`created_at`, `users`.`updated_at`, `users`.`image_url`, `users`.`description` FROM `users` WHERE `users`.`id` = 9 LIMIT 21; args=(9,)
'보라돌이'


>>> c2 = PostingComment.objects.select_related('user').get(id=1) 🔥
(0.001) SELECT `posting_comments`.`id`, `posting_comments`.`user_id`, `posting_comments`.`posting_id`, `posting_comments`.`content`, `posting_comments`.`created_at`, `users`.`id`, `users`.`email`, `users`.`password`, `users`.`name`, `users`.`created_at`, `users`.`updated_at`, `users`.`image_url`, `users`.`description` FROM `posting_comments` INNER JOIN `users` ON (`posting_comments`.`user_id` = `users`.`id`) WHERE `posting_comments`.`id` = 1 LIMIT 21; args=(1,)
>>> c2.posting.user.name 🔥 🔥
(0.001) SELECT `postings`.`id`, `postings`.`user_id`, `postings`.`image_url`, `postings`.`content`, `postings`.`created_at`, `postings`.`updated_at`, `postings`.`size_id`, `postings`.`housing_id`, `postings`.`style_id`, `postings`.`space_id` FROM `postings` WHERE `postings`.`id` = 8 LIMIT 21; args=(8,)
(0.001) SELECT `users`.`id`, `users`.`email`, `users`.`password`, `users`.`name`, `users`.`created_at`, `users`.`updated_at`, `users`.`image_url`, `users`.`description` FROM `users` WHERE `users`.`id` = 9 LIMIT 21; args=(9,)
'보라돌이'


>>> c3 = PostingComment.objects.select_related('posting').get(id=1) 🔥
(0.001) SELECT `posting_comments`.`id`, `posting_comments`.`user_id`, `posting_comments`.`posting_id`, `posting_comments`.`content`, `posting_comments`.`created_at`, `postings`.`id`, `postings`.`user_id`, `postings`.`image_url`, `postings`.`content`, `postings`.`created_at`, `postings`.`updated_at`, `postings`.`size_id`, `postings`.`housing_id`, `postings`.`style_id`, `postings`.`space_id` FROM `posting_comments` INNER JOIN `postings` ON (`posting_comments`.`posting_id` = `postings`.`id`) WHERE `posting_comments`.`id` = 1 LIMIT 21; args=(1,)
>>> c3.posting.user.name 🔥
(0.001) SELECT `users`.`id`, `users`.`email`, `users`.`password`, `users`.`name`, `users`.`created_at`, `users`.`updated_at`, `users`.`image_url`, `users`.`description` FROM `users` WHERE `users`.`id` = 9 LIMIT 21; args=(9,)
'보라돌이'


>>> c4 = PostingComment.objects.select_related('posting', 'user').get(id=1) 🔥
(0.001) SELECT `posting_comments`.`id`, `posting_comments`.`user_id`, `posting_comments`.`posting_id`, `posting_comments`.`content`, `posting_comments`.`created_at`, `users`.`id`, `users`.`email`, `users`.`password`, `users`.`name`, `users`.`created_at`, `users`.`updated_at`, `users`.`image_url`, `users`.`description`, `postings`.`id`, `postings`.`user_id`, `postings`.`image_url`, `postings`.`content`, `postings`.`created_at`, `postings`.`updated_at`, `postings`.`size_id`, `postings`.`housing_id`, `postings`.`style_id`, `postings`.`space_id` FROM `posting_comments` INNER JOIN `users` ON (`posting_comments`.`user_id` = `users`.`id`) INNER JOIN `postings` ON (`posting_comments`.`posting_id` = `postings`.`id`) WHERE `posting_comments`.`id` = 1 LIMIT 21; args=(1,)
>>> c4.posting.user.name 🔥
(0.001) SELECT `users`.`id`, `users`.`email`, `users`.`password`, `users`.`name`, `users`.`created_at`, `users`.`updated_at`, `users`.`image_url`, `users`.`description` FROM `users` WHERE `users`.`id` = 9 LIMIT 21; args=(9,)
'보라돌이'

애초에 찾고자 한 값이 댓글로부터 시작해 댓글이 달린 게시글을 쓴 유저의 이름을 찾는것이기때문에 select_related('posting')을 해주어 posting에 관한 값을 미리 불러오는것! 그래서 🔥가 하나 적어질 수 있었다. (user는 해주나 마나였다. 애초에 여기서 user는 comment를 단 사람이기 때문에)

🔥 표시가 곧 db를 사용할 때! db hit!
"저중에 많아봤자 3번인데 3번이 많은가?" 라고 생각했다. 나도.
근데 이게 타고 들어갈게 더 많고.. 이런 작업을 동시에 수백명이 한다면? 통신상태에서?


shell로 비교 (prefetch_related) 반복문X

prefetch_related는 역참조 관계에서 사용한다. 아까 comment -> posting.user 예시를 했으니 이번엔 반대로 user가 단 게시글 -> comment로!!

>>> u1 = User.objects.get(id=1) 🔥
(0.001) SELECT `users`.`id`, `users`.`email`, `users`.`password`, `users`.`name`, `users`.`created_at`, `users`.`updated_at`, `users`.`image_url`, `users`.`description` FROM `users` WHERE `users`.`id` = 1 LIMIT 21; args=(1,)
>>> u1.posting_set.all()[0].comment.all()[0].content 🔥 🔥
(0.001) SELECT `postings`.`id`, `postings`.`user_id`, `postings`.`image_url`, `postings`.`content`, `postings`.`created_at`, `postings`.`updated_at`, `postings`.`size_id`, `postings`.`housing_id`, `postings`.`style_id`, `postings`.`space_id` FROM `postings` WHERE `postings`.`user_id` = 1 LIMIT 1; args=(1,)
(0.001) SELECT `posting_comments`.`id`, `posting_comments`.`user_id`, `posting_comments`.`posting_id`, `posting_comments`.`content`, `posting_comments`.`created_at` FROM `posting_comments` WHERE `posting_comments`.`posting_id` = 12 LIMIT 1; args=(12,)
'가구 배치가 너무 맘에 드네요~~'


>>> u2 = User.objects.prefetch_related('posting_set').get(id=1) 🔥 🔥
(0.001) SELECT `users`.`id`, `users`.`email`, `users`.`password`, `users`.`name`, `users`.`created_at`, `users`.`updated_at`, `users`.`image_url`, `users`.`description` FROM `users` WHERE `users`.`id` = 1 LIMIT 21; args=(1,)
(0.001) SELECT `postings`.`id`, `postings`.`user_id`, `postings`.`image_url`, `postings`.`content`, `postings`.`created_at`, `postings`.`updated_at`, `postings`.`size_id`, `postings`.`housing_id`, `postings`.`style_id`, `postings`.`space_id` FROM `postings` WHERE `postings`.`user_id` IN (1); args=(1,)
>>> u2.posting_set.all()[0].comment.all()[0].content 🔥
(0.001) SELECT `posting_comments`.`id`, `posting_comments`.`user_id`, `posting_comments`.`posting_id`, `posting_comments`.`content`, `posting_comments`.`created_at` FROM `posting_comments` WHERE `posting_comments`.`posting_id` = 12 LIMIT 1; args=(12,)
'가구 배치가 너무 맘에 드네요~~'

"왜... 🔥갯수가 같지?"
라고 생각했다.

근데 이건 반복문이 아니라 한번만 호출했을때고, 만약 이런게 반복문 속에 있다면???

반복문을 돌면서 값을 호출할 때마다 db를 hit할수도 있다!!!




🔥 내 코드 (반복문!! 42개)

내 코드에서는 조건에 맞는 posting을 불러온 후 해당 포스팅의 작성자에 대한 정보와 해당 게시물에 달린 댓글의 정보(유저 프로필사진, 유저 이름, 댓글내용) 를 함께 넣어준다. 여기서 user(작성자)는 posting의 FK로 걸려있고, comment는 역참조 관계이다.

참고로 PostingComment의 posting은 FK로 걸려있으면서 related_name='comment'라고 정의되어 있다. 때문에 아래 prefetch_related로 사용할때 postingcomment_set이 아니라 comment라고 사용했다.


- 적용 전

처음 prefetch_relatedselect_related를 모두 설정해 주지 않았을때 실행 결과 db기록을 저장한 메모장의 스크롤바는 약.. 3.6cm이다. (몇줄인지셀수 없어서 스크롤바 길이로 판단했다;;)

posting = Posting.objects.all()

- 적용 후

그다음 역참조인 comment는 prefetch_related로, 정참조인 user는 select_related로 설정해 준 결과 스크롤 길이는 약.. 7.5cm이다!!

posting = Posting.objects.prefetch_related('comment').select_related('user').all()

스크롤바가 길어졌단건 내용이 그만큼 짧아졌다는 것!!!!




지금은 내 데이터자체가 방대한 양이 아니고, 나 혼자 통신해보기 때문에 그 차이를 실감하기 어렵겠지만 어쨋든 db접근 기록이 줄었다는것!!! wow..

정말 중요하고도 좋은 기능이다!

참고 사이트
hit 장면 참고 사이트

profile
🍎 🍊 🍋 🍏 🍇

2개의 댓글

comment-user-thumbnail
2021년 2월 27일

계장 순회중입니다..
으악 저도 지난주에 함 적용해보려다 포기했는데 ㅜㅜ 잘 읽고 갑니다 💕💕💕
채현님 정말 고생 많으셧어요❤️❤️

1개의 답글