はじめに
Rails のコントローラーで User
に紐づく全てのアソシエーションを preload
して、N+1 を解消しようとしました。
user = User.preload(:posts, :comments).find(params[:id])
SQL のログを確認すると、 preload
によって関連データがまとめて取得されていることを確認できました。
しかし、実際には後続でクエリが発行されており、無駄なクエリ発行を抑えられていませんでした。
原因
ログをよく見ると、意図しないタイミングで posts
や comments
に対する追加のクエリが発行されていました。
あるクラスのメソッド内で user.reload
が呼ばれていることをわかりました。
class SomeService def initialize(user) @user = user end def execute @user.reload # ← ここが問題 # ここで @user.posts や @user.comments を参照すると再クエリが発行される end end
reload
を実行すると、すでに preload
されていたデータがリセットされ、次回アクセス時に再度クエリが発行されてしまうことが判明しました。
reload
とは?
Rails の公式ドキュメントを調べてみると、reload
メソッドは以下のように説明されています。
Attributes are reloaded from the database, and caches busted, in particular the associations cache and the QueryCache.
一度取得したオブジェクトのキャッシュがクリアされ、データベースから最新の情報が再取得されるのです。
そのため、preload
していたデータもリセットされてしまい、再度 user.posts
や user.comments
にアクセスすると、新しくクエリが発行されることになります。
まとめ
今回のケースでは、単純に reload
を削除することで、不要なクエリを防ぐことが可能になります。
reload
は、データの整合性を担保したいときには有効ですが、N+1 の回避を目的としているときに不用意に使うと、逆にクエリが増えてしまうことがあります。
reloadメソッドを使用するときは、本当に必要なのかどうかを吟味することが必要と勉強になりました。