Open4

django rest frameworkで個人プロジェクトを作る過程

sh0sh0

queryの最適化?

djangoではormを扱うときselect_relatedprefetch_relatedでjoinとかqueryのcustomizeができる

sh0sh0

threadを呼び出すときqueryのcustom

  • 最初
    def get_queryset(self):
        user = self.kwargs.get('username')
        thread_id = self.kwargs.get('pk')
        qs = self.queryset.filter(id=thread_id)
        return qs

結果

queryの量も多いしchild objectが増えるたびにqueryの数が多くなってしまう

  • 改善
    ここでselect_relatedprefetch_relatedを使えば
    def get_queryset(self):
        user = self.kwargs.get('username')
        thread_id = self.kwargs.get('pk')
        prefetches = [
            Prefetch('comments', queryset=Comment.objects.select_related('user').prefetch_related('replies').order_by(
                '-updated_at')),
            Prefetch('likes')
        ]
        qs = self.queryset.filter(id=thread_id).select_related('owner').prefetch_related(*prefetches)
        return qs

結果

送るqueryの数も少なくなってchild objectの量が多くなってもqueryの数は変わらない。
特に、prefetch_relatedを使うとき、django.db.modelsからPrefetchをimportして使えばもっと効率良くqueryの改善ができる。

sh0sh0

django orm order_byを条件付きorder_byへ

model structure
Thread > Comment > Reply

ThreadをソートするときThreadに属しているCommentのcreated_atを元にソートしようと思っていたが
したのコードだとThreadにCommentを何も追加してなかったらnullを返してしまいorder_byが動作しない事に気づいた。

        query_filter = None
        query_order = ['-updated']
        query_annotate = {
            'updated': Max('comments__updated_at'),
            'count_likes': Count('likes', distinct=True),
            'count_comments': Count('comments', distinct=True)
        }
        if order == 'latest':
            pass
        else:
            if order == 'daily':
                query_filter = Q(updated__range=[now - timedelta(days=1), now])
            elif order == 'weekly':
                query_filter = Q(updated__range=[now - timedelta(days=7), now])
            elif order == 'alltime':
                pass
            query_order.insert(0, '-count_likes')
        if query_filter:
            query = qs.annotate(**query_annotate).filter(query_filter).distinct().order_by(*query_order)
        else:
            query = qs.annotate(**query_annotate).distinct().order_by(*query_order)
        return query

ここでquery_annotate変数を下記のように書き換えてみた。

query_annotate = {
            'updated': Case(
                When(Exists(Comment.objects.filter(thread_id=OuterRef('id'))), then=Max('comments__updated_at')),
                default=F('created_at'),
                output_field=DateTimeField(),
            ),
            'count_likes': Count('likes', distinct=True),
            'count_comments': Count('comments', distinct=True)
        }

query_annotateupdatedfieldを上のように書き換えてthreadにcommentsがあるときは一番最近追加したcommentのupdated_atを返して、commentsがなかったらthread自身のcreated_atを返すことにしてcommentがなくてもorder_byが動作するように修正した。
上記のupdatedに書いたコードをquery文にしたら次のようなqueryになる。

CASE
                    WHEN EXISTS
                           (SELECT (1) AS "a"
                            FROM "thlog_comment" U0
                            WHERE U0."thread_id" = "thlog_thread"."id"
                            LIMIT 1) THEN "thlog_thread"."created_at"
                    ELSE MAX("thlog_comment"."updated_at")
                END AS "updated",