๐ ์ด ํฌ์คํ ์์๋ N+1 Problem์ด ๊ฐ์ง๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด EagerLoadingํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์ดํด๋ณด๊ฒ ์ต๋๋ค.
๐ฅ N+1 Problem ์ด๋?
๐ฅ EagerLoading ์ด๋?
โ๏ธ 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๋ฌธ์ ์คํํ๋ ๋ฐฉ๋ฒ์ด๋ค.
โ๏ธ 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)