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?
のようにしてコントローラーから抜けてしまえば、それ以降の処理は不要となります。