Zenn
❤️

Django ORMが勝手にLIMIT21を付けてくる なんで??

2024/12/11に公開

結論:Django ORMが負荷対策のために「LIMIT 21」を自動で付けてくれるっぽい

発行されたクエリを確認中、queryset側でLimit21の指定をしていないのにしれっとLIMIT21が付いてきました。
しかもpkをWHERE句で指定しているクエリでLIMITが付いてきていたので感覚としては結構気持ち悪かった

なんでや・・・なんで・・・・どうして・・・21・・・?

SELECT 
    "auth_user"."id", 
    "auth_user"."password", 
    "auth_user"."last_login", 
    "auth_user"."is_superuser", 
    "auth_user"."username", 
    "auth_user"."first_name", 
    "auth_user"."last_name", 
    "auth_user"."email", 
    "auth_user"."is_staff", 
    "auth_user"."is_active", 
    "auth_user"."date_joined" 
FROM 
    "auth_user" 
WHERE 
    "auth_user"."id" = 1 
LIMIT 
    21; ←😄

ちゃんと書いてあった

https://github.com/django/django/blob/f83b44075dafa429d59e8755aa47e15577cc49f9/django/db/models/query.py#L31

https://github.com/django/django/blob/f83b44075dafa429d59e8755aa47e15577cc49f9/django/db/models/query.py#L413-L424

議論がなされているIssue/PR
https://code.djangoproject.com/ticket/6785
https://github.com/django/django/pull/11215

get()で負荷対策いる?

get()は単一のオブジェクトを返すため、多くの場合、pkなどの一意なフィールドを指定して使うことが一般的です。その状況下において負荷対策は必要あるのか?と思っていたのですが、読み進めると成功ケースではあまり意味がないとのこと。それをわかった上でやっているよ!ってことも書いてありました。

このLIMITはget()を誤った形で使った場合にメリットがあるようです。
get()を誤った形で使った場合、例えばヒットするデータが0件だったり、複数ヒットしてしまうようなケースでは、全行に対して検索が走ってしまう可能性があります。そのため、Django側ではあらかじめデータベースに対して取得する行数を制限し、処理を効率化しているようです。

それはわかったけどなんで21?

https://code.djangoproject.com/ticket/6785

Issueを読む限りは以下理由のようです(英語が苦手なので間違っていたらご指摘ください🙇‍♀️)

  • 最大21行を取得することで「明らかにエラーとわかる状況」を作り出すため
  • 適切なパフォーマンスを維持するための妥協点であるため(極論、10でもいいし20でもいい)

たとえば・・・

LIMIT 2 の場合

「重複がある」とは分かるが、それが軽微な問題(2行だけ)なのか、大きな問題(20行以上)なのか判断できない。

LIMIT 21 の場合

結果が21行なら「さらにデータがある可能性」が分かり、異常の規模が把握しやすくなる。
DB周りだと、重複データが登録されてしまっている?キャッシュがおかしくなっている?フィルタ条件が不適切?など様々な要因が考えられるもんなあ。

結論

  • 負荷対策のために「LIMIT 21」を付けている
  • 21件にしているのはキメ。異常な状況を確実に確認できるから2件にしていない

こんなところでしょうか!
おわり!

さいごに

この記事は、クロスマート・テックアドカレ11日目の記事でした!
明日の記事もおたのしみに🎄
https://qiita.com/advent-calendar/2024/xmart

Discussion

ログインするとコメントできます