問題
policies(規約)テーブル
field | type |
---|---|
id | int(11) |
version | int(11) |
最新の規約バージョンがほしい、
つまりpoliciesテーブルのレコードの中のversionカラムの最大値を取得したい場合を想定します。
以下のようにscopeを定義し実行することで取得できます。
class Policy < ApplicationRecord
scope :latest_version -> { maximum(:version) }
end
2022/9 追記です。記事の本筋とは関係ないですが、こちらはscopeの使い方として正しくない事に気づいたので注意してください。scopeはActiveRecord::Relation
が戻り値になるようにするべきです。参考
Policy.latest_version #=> 10
# もちろんscopeを定義せずに直接実行してもよいです
Policy.maximum(:version) # => 10
ここで懸念が発生しました。
もし頻繁にPolicy.latest_version
を使用したい場合、いちいちDBへ検索をかけてしまうことになります。
(ひとつのメソッド内であれば実行結果を変数に格納しておけばよいですが、いろんな場所で使うケースですね)
皆さんならこのような時どうしますか?
結論
このままで良い
RailsにはSQLキャッシュ
という機能があり、以前と同じクエリが発生すると、データベースへクエリを実行する代わりに、キャッシュされたデータを利用します。
参考: Railsガイド
例えばPolicy.latest_version
を5回実行した場合の、ログは以下のようになります。
(5.3ms) SELECT MAX("policies"."version") FROM "policies"
CACHE (0.1ms) SELECT MAX("policies"."version") FROM "policies"
CACHE (0.1ms) SELECT MAX("policies"."version") FROM "policies"
CACHE (0.1ms) SELECT MAX("policies"."version") FROM "policies"
CACHE (0.1ms) SELECT MAX("policies"."version") FROM "policies"
1回目だけDBに実際に検索をかけていますが、それ以降はキャッシュを利用していることがわかります。
行頭にCACHEとついている場合は、SQLキャッシュ機能が使用されDBに検索をかけずにキャッシュからデータを取得します。
実際に処理時間が減っていることが確認できるかと思います。(5.3ms => 0.1ms)
念の為、誤解のないように補足します。
キャッシュが利用されるのは同じクエリであることが条件で、scopeを使用することが条件ではありません。当然ですが以下でも問題なくキャッシュからデータを取得します。
Policy.maximum(:version)
# CACHE (0.1ms) SELECT MAX("policies"."version") FROM "policies"
ただし条件つき
SQLキャッシュが共有されるのは、あくまで1つのHTTPリクエストに対するアクションの範囲内です。
アクションの開始時に作成され、アクションの終了時に破棄されるので、キャッシュはアクションの実行中しか保持されないという点です。
Railsガイド
その他の手段
クラスインスタンス変数にキャッシュするという手段もあります。
class Policy < ApplicationRecord
def self.latest_version
@latest_version ||= maximum(:version)
end
end
ただし本番サーバでは、クラスはサーバ起動時に作られてそれっきりです。
したがって、一度@latest_version
変数にデータが格納されると、その後にDB側の規約最新バージョンが更新されても、クラスインスタンス変数は更新されません。
次にクラスインスタンス変数がリセットされるのは、サーバ再作成を伴うデプロイ処理時ということになるので注意が必要です。
お気づきの点がありましたら、ご気軽にご指摘ください。