3월 26, 2022

[리팩토링] SQL문과 쿼리 개수를 확인하여 prefetch와 select_related의 성능 확인하기

 



해당 erd의 applicants에서 application_status까지 타고 들어가려 합니다.




Get 요청 보냈을 시 


저는 SQL문을 알지 못합니다. 쿼리문을 보고 직관적으로 해석해보았습니다. 틀린 해석일 가능성이 큽니다.

1. id가 8 인 유저를 user테이블로부터 불러옵니다

2. applicants 중에서 user_id가 8인 것을 찾습니다. -> 7번 applicant

3. applications에서 applicant_id가 7 인것을 찾습니다

4. inner join을 이용하여 application_status 중 applications.status_id 와 같은 id를 가진 행을 엮은 뒤, applicant_id가 7 인 것을 찾습니다 



이 부분에서 prefetch_related를 빼주었더니 


오히려 쿼리의 숫자가 하나 줄어들었습니다




이 부분이 없어졌군요

그니까 위의 3번 부분이 없어진 것입니다.


반면에 select_related를 주석처리 해주었더니

쿼리가 21개로 급증했습니다

inner join으로 미리 데이터를 엮어오지 않아서 매번 for문이 돌때마다 데이터를 db에서 가져오는 것을 볼 수 있습니다.


왜 prefetch를 썼더니 쿼리 개수가 하나 더 늘어났을까요??

 https://stackoverflow.com/questions/31237042/whats-the-difference-between-select-related-and-prefetch-related-in-django-orm

이 링크의 답변을 보시면, select_related는 SQL join을 하여 데이터를 SQL서버의 테이블 일부분으로 받는 반면, prefetch_related는 또 다른 쿼리를 발생시켜서 기존 object의 불필요한 컬럼을 줄여준다(inner join을 하여 테이블의 컬럼을 늘리지 않는다는 뜻으로 받아들였습니다) 고 합니다. 

다시 한번 코드를 보면,

applicant에서 status로 가는 과정에서 역참조로 application_set을 사용하는 것은 맞습니다. 다만 prefetch를 사용하지 않아도, 그 밑의 코드에서 application_set.all() 로 역참조한 application의 쿼리셋을 가져오고, 거기에 select_related로 status를 가져오고 있습니다 

저는 prefetch를 사용하면 미리 application을 db에서 join으로 긁어와서 추후에 application_set을 사용할때 db를 다시한번 hit할 일이 없다고 생각했습니다. 그러나 prefetch_related는 "파이썬" 상에서 join을 실시하며(db에서 미리 join을 일으키지 않으며), join될 각 테이블에 대한 쿼리를 발생시킵니다.

따라서 prefecth_related를 사용했을 시 이 부분이 추가되지 않았나 생각이 듭니다


 


한번 더 실행해보니 


쿼리 숫자가 5개로 또 한번 줄어들었습니다.
그런데 화면상에 뜨는 쿼리문은 그대로이네요.. 이 부분은 알게되면 추후 포스팅하겠습니다


밑의 CompanyView를 봅시다.

이 코드의 경우 

applicant에서 application으로 가서 job_position으로 한번 더 이동 한 뒤 , 거기서 각각 company의 name / job_category의 name / 해당 테이블의 created_at을 뽑아와야 하는 구조입니다


쿼리가 무려 31개나 날아갑니다.

자세히보면 job_category, company, application_status를 가져오는 쿼리가 중복됩니다.

job_position만 select_related로 join할 것이 아니라 job_category와 company는 select_related로,  status는 prefetch로 가져옵니다. 

쿼리의 개수가 획기적으로 줄어들었습니다.




application 쿼리셋에서 시작해서, 어떻게 두다리 너머의 company와 category 까지 가져올 수 있을지 고민했는데, 그냥 job_position에서 __ 로 넘어가면 되는거였습니다.

다시한번 prefetch와 select의 차이를 알기 위하여 

prefetch 를 select_related로 바꿔주었습니다.

쿼리가 하나 줄어들었습니다.

마지막의 이 부분이 없어졌네요 



앞서 첨부한 스택오버플로우 답변글에 이런 답변이 있습니다. select_related와 prefetch_related의 쿼리 개수가 딱 하나 차이나는것이 이 답변을 증명하네요.

"prefetch_related run the query on the first model , collects all the IDs it needs to prefetch and then runs a query with an IN clause in the WHERE with all the IDs that it needs. "

이 답변에 해당하는 쿼리가 이것인듯 하네요