TIL92. Django ORM : EagerLoading(Resolve N+1 Problem)

ID์งฑ์žฌยท2021๋…„ 11์›” 20์ผ
0

Django ORM

๋ชฉ๋ก ๋ณด๊ธฐ
6/7
post-thumbnail

๐Ÿ“Œ ์ด ํฌ์ŠคํŒ…์—์„œ๋Š” N+1 Problem์ด ๊ฐ€์ง€๋Š” ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด EagerLoadingํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.


๐ŸŒˆ EagerLoading(Resolve N+1 Problem)

๐Ÿ”ฅ N+1 Problem ์ด๋ž€?

๐Ÿ”ฅ EagerLoading ์ด๋ž€?


1. N+1 Problem ์ด๋ž€?

๐Ÿค” N+1 Problem

โœ”๏ธ N+1 ๋ฌธ์ œ๋ž€ Lazy-Loading์˜ ์„ฑ๋Šฅ ์ด์Šˆ๋กœ ์™ธ๋ž˜ํ‚ค๋ฅผ ์ฐธ์กฐํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฐธ์กฐํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ, ๋ฐœ์ƒํ•œ๋‹ค.

โœ”๏ธ Publish์™€ Book ํ…Œ์ด๋ธ”์ด ์žˆ๊ณ , Book ํ…Œ์ด๋ธ”์˜ user ํ•„๋“œ๊ฐ€ Publish ํ…Œ์ด๋ธ”์„ ์ •์ฐธ์กฐํ•˜๊ณ  ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณด์ž.

๐Ÿš€ Publish ํ…Œ์ด๋ธ”
class Publish(TimeStampModel):
    name                = models.CharField(max_length=30)
๐Ÿš€ Book ํ…Œ์ด๋ธ”    
class Board(TimeStampModel):
    name               = models.CharField(max_length=100)
    description        = models.TextField()
    publisher          = models.ForeignKey('Publish', on_delete=models.CASCADE)

โœ”๏ธ Book์— ๋Œ€ํ•œ list๋ฅผ ๋ฟŒ๋ ค์ฃผ๋Š” API๋ฅผ ๋งŒ๋“ ๋‹ค๊ณ  ๊ฐ€์ • ํ•  ๋•Œ, get ๋งค์„œ๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ๋กœ์ง์„ ๊ฐ€์ง„๋‹ค.

โœ”๏ธ ํ•˜๋‚˜์˜ book ๊ฐ์ฒด์˜ publish ํ•„๋“œ๋ฅผ ํ†ตํ•ด Publish ํ…Œ์ด๋ธ”๋กœ ์ด๋™ํ•ด name๊ฐ’์„ ๊ฐ€์ ธ์˜ค๋ ค๋Š” ์ˆœ๊ฐ„ N+1์˜ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒ๋œ๋‹ค.

โœ”๏ธ ์—ฌ๊ธฐ์„œ for๋ฌธ์ด ์ˆœํšŒํ•˜๋Š” ํšŸ์ˆ˜๊ฐ€ N์ด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŽ์„ ์ˆ˜๋ก ์—„์ฒญ๋‚œ ์–‘์˜ query๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

class BookListView(View):
    def get(self, request):
        result = []
        queryset = Book.objects.all()
        for book in queryset:
            books.append({
                'id':book.id,
                'name':book.name,
                'publish':book.publisher.name, # book.publisher์— ์ ‘๊ทผํ•˜๋ฉด, ์บ์‹ฑ๋˜์ง€ ์•Š์€ ๋ฐ์ดํ„ฐ ๋•Œ๋ฌธ์— query ๋ฐœ์ƒ(N+1 ๋ฌธ์ œ)
            })
        return JsonResponse({"message": result}, status=200)

โœ”๏ธ ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ORM์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋ฒ•์ด ์ฆ‰์‹œ ํ˜ธ์ถœ(EagerLoading) ์ธ๋ฐ, ์ง€๊ธˆ ๋‹น์žฅ ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ๋ฐ์ดํ„ฐ๋„ ํฌํ•จํ•˜์—ฌ ๋ฏธ๋ฆฌ Query๋ฌธ์„ ์‹คํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

  • ๐Ÿš€ ์ •์ฐธ์กฐ ์ผ ๋•Œ,, ๋ฐฉ๋ฒ•1: select_related
  • ๐Ÿš€ ์—ญ์ฐธ์กฐ ์ผ ๋•Œ,, ๋ฐฉ๋ฒ•2: prefetch_related

2. EagerLoading ์ด๋ž€?

โœ”๏ธ select_related์€ ์ •์ฐธ์กฐ์ผ ๋•Œ ์‚ฌ์šฉํ•˜๊ณ , JOIN์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ฆ‰์‹œ ๋กœ๋”ฉํ•œ๋‹ค. ๋˜ํ•œ ์ด ๊ฒฐ๊ณผ๋ฅผ ์บ์Š์— ์ €์žฅ์‹œํ‚จ๋‹ค.

 ๐Ÿš€ select_related
class BookListView(View):
    def get(self, request):
        result = []
        queryset = Book.objects.all().select_related("publisher") # ๐Ÿ‘ˆ ์ •์ฐธ์กฐ ์ผ ๋•Œ, select_related ํ™œ์šฉ
        for book in queryset:
        books.append({
                'id':book.id,
                'name':book.name,
                'publish':book.publisher.name, 
  	     })
        return JsonResponse({"message": result}, status=200)

โœ”๏ธ ์•„๋ž˜ ์ฝ”๋“œ๋Š” store.books.all()์—์„œ ์—ญ์ฐธ์กฐ๊ฐ€ ๋ฐœ์ƒ๋˜์–ด ์ด ๋•Œ๋งˆ๋‹ค query๊ฐ€ N๋ฒˆ๋งŒํผ ๋ฐœ์ƒํ•˜๋Š” N+1๋ฌธ์ œ๋ฅผ ๊ฐ–๊ณ  ์žˆ๋‹ค.

class BookListView(View):
    def get(self, request):
        result = []
        queryset = Store.objcts.all()
        for store in queryset:
            books = [book.name for book in store.books.all()] # ๐Ÿ‘ˆ ์—ญ์ฐธ์กฐ ๋ถ€๋ถ„์—์„œ N+1 ๋ฌธ์ œ ๋ฐœ์ƒ
            store.append({
                'id':store.id,
                'name':store.name,
                'book':books
         })
        return JsonResponse({"message": result}, status=200)  

โœ”๏ธ prefetch_related๋Š” ์—ญ์ฐธ์กฐ ์ผ ๋•Œ N+1 ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๊ณ , ์ถ”๊ฐ€ ์ฟผ๋ฆฌ ์…‹์„ ์ด์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‹จ์—์„œ ๋ชจ๋‘ ํ•ฉ์ณ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

class BookListView(View):
    def get(self, request):
        result = []
        queryset = Store.objcts.all().prefetch_related('books') # ๐Ÿ‘ˆ prefecth_related ์‚ฌ์šฉ
        for store in queryset:
            books = [book.name for book in store.books.all()] 
            store.append({
                'id':store.id,
                'name':store.name,
                'book':books
         })
        return JsonResponse({"message": result}, status=200)  

โœ”๏ธ prefetch_related๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, result_cash์— ์บ์‰ฌ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹ด๊ธฐ๋Š”๋ฐ, ๊ธฐ์กด query๋ฅผ ๋ณด๋‚ด๊ณ , ์ถ”๊ฐ€๋กœ ์—ญ์ฐธ์กฐ์— ๋Œ€ํ•œ prefetch_related๋ฅผ ๋ณด๋‚ด ์ด๋ฅผ ์ตœ์ข…์ ์œผ๋กœ ํ•ฉ์ณ์ค€๋‹ค.

โœ”๏ธ ์˜ˆ๋ฅผ ๋“ค์–ด, queryset = Store.objcts.all().prefetch_related('books', 'comment', 'whishlist')๋ฅผ ํ•˜๋ฉด, query๊ฐ€ ์ด 4๊ฐœ ์š”์ฒญ๋œ๋‹ค.

๐Ÿค” ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด ์ด๋Ÿฐ์‹์œผ๋กœ๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

โœ”๏ธ ์•„๋ž˜๋Š” prefetch_related๋ฅผ ํ™œ์šฉํ•ด ์—ญ์ฐธ์กฐ ์‹œ, EagerLoadingํ•œ ์˜ˆ์‹œ๋‹ค.

class StoreListView(View):
    def get(self, request):
        queryset = Store.objects.all().prefetch_related("books")
        result = []
        for stroe in queryset:
	        total_books = [book.name for book in store.books.all()]
	        filter_books = [book.name for book in store.books.filter(name='Book9991)]
            result.append({
    	        'id':store.id,
                'bame':store.name,
                'total_books':total_books,
                'filter_books':filter_books,
            })
        return JsonResponse({"message": result}, status=200)  

โœ”๏ธ queryset์ด ์žฌ์‚ฌ์šฉ๋œ๋‹ค๋ฉด, to_attr ์†์„ฑ์— ์บ์Šํ•ด๋‘๊ณ  ์žฌ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ํšจ์œจ์ ์ด๋‹ค.

class StoreListView(View):
    def get(self, request):
        queryset = Store.objects.all().prefetch_related(
            Prefetch('books', queryset=Book.objects.all(), to_attr='total_books'),
            Prefetch('books', queryset=Book.objects.filter(name='Book9991'), to_attr='filtered_books')
        )
        result = []
        for stroe in queryset:
	        total_books = [book.name for book in store.books.all()]
	        filter_books = [book.name for book in store.books.filter(name='Book9991)]
            result.append({
    	        'id':store.id,
                'bame':store.name,
                'total_books':total_books,
                'filter_books':filter_books,
            })
        return JsonResponse({"message": result}, status=200)  
profile
Keep Going, Keep Coding!

0๊ฐœ์˜ ๋Œ“๊ธ€