HTTPにはキャッシュの機能がありますが、Rails標準でも意外なところまで組み付けてあったりします。
記事の守備範囲
この記事では、Railsが動的に生成する箇所のキャッシュを中心に考えます。事前に生成して、送信にRailsが直接関与しないこともあるアセット類については、考慮の対象から除外します。
HTTPのキャッシュ
まず、HTTPのキャッシュには大きく分けて2通りの役割があります。
- キャッシュがある状態で全く通信を行わず、キャッシュした結果をそのまま利用する
- キャッシュのある状態で更新確認のための通信を行い、変化がなければ再利用する
そして、キャッシュされたものには2つの状態があります。
- fresh…キャッシュを信じて、通信せずに利用して問題ないことを示しています。
- stale…キャッシュが古くなっている、という状況です。
Cache-Controlヘッダ
HTTPキャッシュの指示のために、Cache-ControlというHTTPヘッダが存在します。詳細はMDNに譲りますが、ある程度まとめておきます。
-
no-store…一切のキャッシュ禁止 -
must-revalidate…staleなキャッシュを使う前に更新確認を行う -
max-age…キャッシュがfreshである期間の設定 -
no-cache…最初からstaleなキャッシュであるとの宣言(must-revalidate, max-age=0と同様)。キャッシュしないという意味ではない。 -
private…キャッシュが共有不可能であることを示す。 -
public‥キャッシュが共有可能であることを示す。 - なお、
privateもpublicも入れなかった場合、どちらで扱われるかは他のディレクティブの設定によります。ただし、Railsから使う場合は「publicを指定しなければprivateが自動で入る」という動作が組んであるので、デフォルトを気にする必要はあまりありません。
Rails標準の動作
Rails標準では、以下のように動作します。
- ビューの中身からETagを生成
- レスポンスは
must-revalidate, max-age=0, privateで送信 - 次回に
If-None-MatchでETagが正しかった場合、レスポンス本体は返さず304 Not Modifiedで応答
ということで、同じレスポンスを返したい場合、内容の転送負荷は削減されることになります。ビューを生成してからETagでチェックするため、不必要にキャッシュが残る心配はないのですが、ビュー処理の負荷は消えないことになります。
シナリオ別の設定
一定時間キャッシュを残したい
キャッシュを一定時間残して、その間はリクエストも行わないようにしたい、という場合、コントローラーでexpires_inというメソッドを使えます。
なお、その時間内はキャッシュの更新がされませんので、「残ってしまってもかまわない状況で使う」、「元データが変更になった際にはURLごと変更する」、「一定時間ごとにデータが変わる状況で、変わる時点までのキャッシュを行う」など、何かしらの戦略が必要となります。
一切キャッシュさせない
これも、Rails 7ではno_storeメソッドが登場していますが、それ以前のバージョンではrequest.cache_control.replace(no_store: true)のようにデータを書き換える必要があります。
Not Modified時の処理負荷を軽減したい
最終更新日時や特定の値などで、ビュー全体を描画しなくてもキャッシュの有効無効を判定できる場合、stale?というメソッドが有用です。
:etagや:last_modifiedなどに条件を設定して、それらの条件をリクエストと照合して、レスポンスボディ不要となれば、304 Not Modifiedのレスポンスを生成した上でfalseを返します。return if stale?のようにしてコントローラーから抜けてしまえば、それ以降の処理は不要となります。