Django Rest FrameworkのPaginationクラスが発行するCOUNTが遅い時の対処
はじめに
フロントVue.js、バックエンドDjangoRestFrameworkで作っていますが、一部の検索が遅い…。
具体的に言うと、全件検索で15〜20秒以上掛かってしまいます。
他のAPIと比べても検索件数が多いわけでも無いですし、
実際にQuerysetから発行される生SQLをDB上で叩いても、5秒以内には終わります。
何が悪いのだろうとDjangoのログを見ていると、
検索前に以下のCOUNT(*)
クエリが発行されていました。
[DEBUG] utils 71 140228644845312 (22.130) SELECT COUNT(*) FROM (SELECT ...
その後実行されるSELECT文と比べると、durationの値(22.130と0.067)が全然違います。
[DEBUG] utils 71 140228644845312 (0.067) SELECT ...
Countクエリを発行していたのは、Paginationクラスを指定していたから
該当のAPIを定義しているViewSetクラスは、HTMLでページングをしたい為、PageNumberPaginationを継承したクラスを指定していました。
こちらを、Noneと指定すると、事前にCOUNTクエリが発行されることはなくなり、Responseが返ってくる速度が早くなりました
pagination_class = None
とはいえ、Paginationは使いたいです。
なので、COUNTクエリを発行しないように、レコードの件数を返すようにしてみます。
やったこと
Paginationクラスでいくつかのメソッドをオーバーライドしました。
from collections import OrderedDict
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
class HogePagination(PageNumberPagination):
# ページサイズ
page_size = 500
# QuerySetで算出されるItem数
count_local = 0
def __init__(self):
super().__init__()
def paginate_queryset(self, queryset, request, view=None):
self.count_local = len(queryset)
return super().paginate_queryset(queryset, request, view)
def get_paginated_response(self, data):
return Response(OrderedDict([
('count', self.count_local),
('next', self.get_next_link()),
('previous', self.get_previous_link()),
('results', data)
]))
paginate_queryset
に渡ってくるquerysetのアイテム数をlen()
で取得し、
それをget_paginated_response
のcount
キーの値として返すことをしています。
これでCOUNTクエリを発行せず、全体の件数を取得することができました。
そもそもCOUNTクエリでこんなに遅くなるのは、いろんなテーブルをINNER JOIN(Queryset的には、select_related
)して、そもそものSELECT文自体が複雑になっている可能性があるので、
テーブル構成を見直したほうがいいんじゃない? という気もしてますが、とりあえずはこれで。
Discussion