1. 혼자 만들어보는 Zara - Q & ORM최적화
혼자 만들어보는 Zara - Q & ORM최적화
1. 구현 부분
Zara에서 여성-코트|트렌치코트를 눌렀을 때 조회되는 리스트에 대한 API입니다.
필터링 부분의 경우 사이즈, 가격, 아이템, 색상에 대해 구현했습니다.
패션쪽을 잘 몰라서 컬렉션과 신발/악세사리는 왜 있는지 아직도 이해가 안 가서
4가지 필터링에 대해서 우선적으로 했습니다.
2. models.py
class Item(TimeStampModel) :
category = models.ForeignKey(Category, on_delete=models.CASCADE)
name = models.CharField(max_length=30)
class Product(TimeStampModel) :
item = models.ForeignKey(Item, on_delete=models.CASCADE)
name = models.CharField(max_length=30)
price = models.PositiveIntegerField()
class Size(TimeStampModel) :
size = models.CharField(max_length=20)
class Color(TimeStampModel) :
color = models.CharField(max_length=20)
class DetailProduct(TimeStampModel) :
product = models.ForeignKey(Product, on_delete=models.CASCADE)
size = models.ForeignKey(Size, on_delete=models.CASCADE)
color = models.ForeignKey(Color, on_delete=models.CASCADE)
코트를 눌렀을 때 나오는 대표 상품은 Product
에 저장이 되고, 상품에 대한 세부정보는
DetailProduct
에 저장되는 구조입니다.
참조관계
Product는 Item을 정참조합니다.
DetailProduct는 Size, Color, Product를 정참조합니다.
Product는 DetailProduct를 역참조합니다.
2. views.py
def get(self, request) :
offset = int(request.GET.get('offset', 0))
limit = int(request.GET.get('limit', 15))
category_id = int(request.GET['category_id'])
item_id = request.GET.getlist('item_id', None)
color_id = request.GET.getlist('color_id', None)
size_id = request.GET.getlist('size_id', None)
min_price = request.GET.get('min_price', None)
max_price = request.GET.get('max_price', None)
if limit > 20 :
return JsonResponse({'message' : 'TOO_MUCH_LIST'}, status=400)
product_filter = Q(item__category_id=category_id)
if item_id :
product_filter.add(Q(item__id__in = item_id), Q.AND)
if color_id :
product_filter.add(Q(detailproduct__color_id__in = color_id), Q.AND)
if size_id :
product_filter.add(Q(detailproduct__size_id__in = size_id), Q.AND)
if min_price and max_price :
product_filter.add(Q(price__gte=min_price)&Q(price__lte=max_price), Q.AND)
product_list = [{
'id' : product.id,
'name' : product.name,
'price' : product.price,
'item_id' : product.item.id,
'item_name' : product.item.name,
'thumbnail' : [
{
'id' : thumbnail.id,
'url' : thumbnail.url
} for thumbnail in product.thumbnail_set.all()],
'detail_set' : [
{
'color_id' : detail.color_id,
'color_name' : detail.color.color,
'size_id' : detail.size_id,
'size_name' : detail.size.size
}
for detail in product.detailproduct_set.all()]
} for product in Product.objects.select_related('item').\
prefetch_related('detailproduct_set', 'thumbnail_set').\
filter(product_filter)[offset:offset+limit]
]
return JsonResponse({'message' : product_list}, status=200)
2-1. Query Parameter 필터링
우선, 아래는 Query Parameter
로 받아올 변수들입니다.
offset, limit, category_id, item_id, color_id, size_id, min_price, max_price
-
offset
과limit
은Pagination
을 위해서 설정했고,
아이템, 색상, 사이즈, 가격범위내 조회를 위해 나머지 변수를 설정했습니다.
limit
제한의 경우, 프론트엔드에서 필요 이상의 데이터 요청을 막기 위해
제한을 뒀습니다. -
그리고 최초에는 코트라는 카테고리를 눌러야 하므로 제일 먼저 카테고리를 이용한
필터링을 위해product_filter
에 담았습니다. -
그리고 아이템, 색상, 사이즈, 가격은 선택할 수도 있고 안 할수도 있기 때문에
조건문을 설정하여 선택했으면 넣어줄 수 있도록 했습니다. -
그리고 아이템, 색상, 사이즈의 경우 다중선택이 가능합니다.
그래서 상황에 따라 URL 뒤에Query Parameter
가 복잡해질 수 있습니다.
예를 들면 아래와 같습니다.
http://localhost:8000/products?category_id=2&item_id=1&item_id=2&color_id=1
이렇게되면 아이템의 ID가 1, 2로 두 개를 선택하여 요청을 보내기 때문에
리스트로 받아야 합니다. 그래서 다중선택 가능한 건 리스트로 받아야 한다고
생각해서request.GET.getlist
를 사용하게 되었습니다. -
그래서
Query Parameter
로 받은 변수들이 있으면 필터조건에 계속 추가합니다. -
필수 조건인
category_id
의 경우, 카테고리를 눌러야 리스트가 나오기 때문에
request.GET
을 사용했습니다.
필수값이기 때문에 카테고리ID를 URL에Query Parameter
로 입력하지 않을 시,
KeyError
가 발생합니다.
2-2. ORM 최적화와 N+1 Problem
이렇게 조건들을 다 선택하고 조건에 맞는 상품들을 조회해야 합니다.
Product.objects.select_related('item').\
prefetch_related('detailproduct_set', 'thumbnail_set').\
filter(product_filter)[offset:offset+limit]
Product
는 Item
을 정참조하기 때문에 select_related
를 사용했으며
DetailProduct
과 Thumbnail
을 역참조하기 때문에 prefetch_related
를 사용했습니다.
다만, 고민해봐야 할 부분이 생겼습니다.
'detail_set' : [
{
'color_id' : detail.color_id,
'color_name' : detail.color.color,
'size_id' : detail.size_id,
'size_name' : detail.size.size
}
예를 들어 여기서 prefetch_related
를 이용해 색상과 사이즈의 ID를 가져왔지만,
그것의 이름을 가져오려면 한 번 더 타고 들어가야해서 어쩔 수 없는 N+1 Problem
이 발생합니다.
쿼리 로그를 찍어보고 이 부분에 대해 어떻게 해야 할지 고민하게 되었습니다.
내일은 데이터 셋을 불러오는 걸 모듈로 나눠서 views.py
가 간단해질 수 있게,
어떻게보면 객체지향적인(?) 코드로 바꿀 예정입니다.
Author And Source
이 문제에 관하여(1. 혼자 만들어보는 Zara - Q & ORM최적화), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@kyleee/1.-혼자-만들어보는-Zara-Q-ORM최적화저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)