今更言うまでもないんですが、ORMってとっても便利ですよね。
pythonのフレームワークのDjangoにも実装されているんですが、便利がゆえに地雷になることもあったり。
備忘録もかねて対応策を
やけに負荷のかかっているSQLが発生
all_prefetch.py
Foo.objects.filter(
hoge=foo
).prefetch_related('bar')
上記のような一見、何の問題もなさそうなSQLを実行していたんですよ。
Fooテーブルから該当の条件のデータを取得して、1:N問題を避けるべく、relation関係にあるbarテーブルの内容をまとめて取得する……という。
ところが、何故かこの処理が呼び出されるAPIだけ処理が重いという事象が発生してみたので早速、調査を
大量のデータを取得しているッ!?
check_all_prefetch.py
# ↑↑ この前にログイン中のユーザのデータを取得している
Foo.objects.filter(
hoge=foo
).prefetch_related('bar') # <- barが1000件ぐらい存在していた
# ↓↓ そしてここより後の処理で、barから引っ張ってきたデータを、
# ユーザが持つ情報に応じてpython側でフィルタリングしている。
そう、SQL側でやるべきことをやっておらず、python側でやっていたッ!
データの転送量が多く、アクセスが殺到した際に問題になっていたという新事実が。
ありがちな問題……ではあるんですが、とはいえprefetch_relatedの部分に条件を設定できるような引数はなさそうです
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側でフィルタリングするという不毛なこともなく、データの転送量も抑えらるようになりました