Python
Django
ORM
python2.7

Djangoのmodelでの、eager loading機能であるprefetch_relatedに検索条件を追加する

今更言うまでもないんですが、ORMってとっても便利ですよね。

pythonのフレームワークのDjangoにも実装されているんですが、便利がゆえに地雷になることもあったり。:volcano:

備忘録もかねて対応策を


やけに負荷のかかっているSQLが発生


all_prefetch.py

Foo.objects.filter(

hoge=foo
).prefetch_related('bar')

上記のような一見、何の問題もなさそうなSQLを実行していたんですよ。

Fooテーブルから該当の条件のデータを取得して、1:N問題を避けるべく、relation関係にあるbarテーブルの内容をまとめて取得する……という。

ところが、何故かこの処理が呼び出されるAPIだけ処理が重いという事象が発生してみたので早速、調査を:thinking:


大量のデータを取得しているッ!?


check_all_prefetch.py

# ↑↑ この前にログイン中のユーザのデータを取得している

Foo.objects.filter(
hoge=foo
).prefetch_related('bar') # <- barが1000件ぐらい存在していた

# ↓↓ そしてここより後の処理で、barから引っ張ってきたデータを、
# ユーザが持つ情報に応じてpython側でフィルタリングしている。


そう、SQL側でやるべきことをやっておらず、python側でやっていたッ!

データの転送量が多く、アクセスが殺到した際に問題になっていたという新事実が。

ありがちな問題……ではあるんですが、とはいえprefetch_relatedの部分に条件を設定できるような引数はなさそうです:sob:


prefetch_relatedに検索条件を追加するッ!


add_filter_prefetch.py

from django.db.models import Prefetch

# barのprefetch内容を設定
p = Prefetch('bar', Bar.objects.filter(id__in=user.hoge))

# prefetchに上記で作成したオブジェクトを渡す
Foo.objects.filter(
hoge=foo
).prefetch_related(p)


こんな感じの修正になりました!

Prefetchを使用することでprefetch_relatedで生成されるSQLにも正しく検索条件を追加できています

これでpython側でフィルタリングするという不毛なこともなく、データの転送量も抑えらるようになりました:blush: