DotFriends

⚫️Dotfriends 페이징 기능 쿼리 성능 향상 시키기

King of Silicon Valley 2021. 10. 16. 19:48
728x90

Dotfriends의 페이징 기능 성능 테스트를 하던 중 데이터가 100만개를 넘어서면 호출 시간이 3초나 걸리는 문제가 생겼다. 

 

api 호출시간이 4.27초다

 

쿼리문 한개에 4초의 시간이 걸린다. 

 

이런 문제를 자각하고 페이징 기능을 쿼리 성능 튜닝에 대해서 알아보았다. 

 

해결 방법은 크게 3가지가 있었다. 

 

1. NO OFFSET으로 페이징 기능 변경 

 

1-1. NO OFFSET 기능으로 변경 후 총 데이터 카운트 쿼리 삭제 

 

2. 서브 쿼리로 변경

 

NO OFFSET 기능은 상용하기가 어려운게 dotfriends사이트 특성상 원하는 지점으로 빠르게 이동하는 방식이 유저에게 더 편할 것 같기 때문에 NO OFFSET기능으로 변경하기에는 무리가 있었다. 

 

데이터가 1억건이 넘아가게 되면 카운트 쿼리에서도 성능이 저하 되므로 카운트 쿼리를 안사용하는 방식을 사용해야겠지만 

 

우선 100만건이라는 가정하에서는 카운트 쿼리가 큰 시간을 잡아먹지 않기 때문에 서브 쿼리를 사용하시로 했다. 

 

난관 

장고 ORM쿼리가 이미 너무 복잡한 상태에서 서브 쿼리문 문법을 사용하기가 어려웠고 

로우 쿼리로 해야겠단 생각하고 쿼리문을 계속 짜봤지만 

로우 쿼리문이 익숙하지 않아서 SQL공부를 따로 했다. 

 

 

해결

기존에 ORM코드를 보면 

 

 products = Product.objects.filter(**q).prefetch_related('image_set')\
                 .annotate(avg_rate=Avg('comment__rate'),popular=Count("userproductlike", distinct=True),review_count=Count('comment',distinct=True))\
                 .order_by(order)[offset:offset+limit]

쿼리를 조회한 후에 offset:limit 이 붙게 된다. 

그래서 조회하는 쿼리가 offset 값이 커질수록 호출 시간이 늘어나게 된다. 

 

이걸 로우 쿼리로 바꾸면 

products = Product.objects.raw('SELECT 
p.id, p.name, p.price, p.discount_percent, p.is_new, p.category_id, p.created_at, p.updated_at,
AVG(comments.rate) AS avg_rate, COUNT(DISTINCT userproductlikes.id) 
AS popular, COUNT(DISTINCT comments.id) AS review_count from products as p 
LEFT OUTER JOIN comments 
ON
(p.id = comments.product_id)
LEFT OUTER JOIN userproductlikes 
ON
(p.id=userproductlikes.product_id) 
JOIN(SELECT id 
     FROM products 
     ORDER BY {order} 
     LIMIT {offset}, {limit}) as temp 
     ON temp.id = p.id 
 GROUP BY p.id;'.format(offset= offset, limit= limit, order=order))

상당히 복잡한 쿼리문이 되었다. 

 

그래도 성능은 상당히 좋아졌다. 

 

API호출에 144ms
쿼리 호출에 101ms

 

기존 4초(4000ms)에서 101ms로 40배나 빨라졌다.