Edited at

rails 4 の fragment cache(フラグメントキャッシュ)まとめ

More than 3 years have passed since last update.


fragment cache とは

rails で利用できる view の一部分をキャッシュする機能。rails 3以上ならデフォルトで利用できる。railsは view のレンダリングが遅いので、アクセスの多いサイトはキャッシュの利用が必要になってくる。

<% cache {some_key} do %>

cache したい HTML
<% end %>

上記のように、{some_key} を指定すると、その中身をキャッシュしてくれる。{some_key} の部分は、省略もできるし、Stringやオブジェクトも指定できる。


基本は skip_digest: true かつ expires_in: オプションをつける

<% cache "data", expires_in: 1.minutes, skip_digest: true do %>

<%= @data.text %>
<% end %>

リアルタイムに更新が反映されなくていい箇所は、上記のように時間によってキャッシュを expire させるのがややこしいことを何も考えなくて良いので楽である。expires_in: 1.seconds でもキャッシュするHTML内でSQLを発行している場合、十分早くなる。

コントローラ側も特に fragment_exist? のようなことはやらず下記のように普通にロードすれば良い。ActiveRecordは遅延評価なので、viewで表示に必要な場合のみSQLが発行される。

def index

@data = Data.where.order("id DESC")
end


キーにStringを指定した場合

<% cache "key" do %>

cache したい HTML
<% end %>

この場合は、Stringのキーに対応した形でシンプルにHTMLがキャッシュされる。内部的に実際には使われるキーにはデフォルトだと中身のHTMLを元に作ったdigestが付与されるため、cacheしたいHTML自体が変更されれば、digestが変更され、自動でキャッシュが更新される。

しかし下記のような場合

<% cache "key" do %>

<%= @some_db_data %>
<% end %>

@some_db_dataの中身が変更されても、キャッシュには変更が反映されない。キャッシュのキーに含まれるdigestはあくまでHTMLを元に作られるため、データの変更があってもdigestは変更されないからである。

また、自動で付与されるdigestを利用しないようにするには

<% cache "key", skip_digest: true do %>

<%= @some_db_data %>
<% end %>

とする。この場合、HTMLに変更があったとしても、キャッシュには反映されず変更前のものが表示される。


キーを省略した場合

<% cache do %>

cache したい HTML
<% end %>

上記のように、キーを省略した場合は、ページのurlからキーが自動で作られる。url依存のキーなので、同じページでこの形を複数記述すると、キーのバッティングがおきて、どちらかのキャッシュが上書きされて大変なことになる。


キーにオブジェクトを指定した場合

<% cache @object do %>

<%= @object.text %>
<% end %>

オブジェクトを指定した場合は、@object.cache_keyがキャッシュのキーとなる。ActiveRecordのオブジェクトの場合、IDや、updated_at を元にキーが作成されるので、オブジェクトのupdated_atが変更されると自動でキャッシュも更新される。

<% cache @objects do %>

<% @objects.each do |object| %>
<%= object.text %>
<% end %>
<% end %>

上記のように配列にした場合は、各オブジェクトに対して cache_key が呼ばれてそれを連結したものがキャッシュのキーになる。よって配列の順番や中身が変更されればキャッシュは更新される。


つまづいた点

コントローラ側で

def index

@objects = SomeModel.findAll
end

として、view側で

<% cache "object#{@objects.first.id}" do %>

<% @objects.each do |object| %>
<%= object.text %>
<% end %>
<% end %>

と書いた場合と

<% cache "objects" do %>

<% @objects.each do |object| %>
<%= object.text %>
<% end %>
<% end %>

と書いた場合で負荷テストの結果が大幅に変わった。

後者の方が大きく早かったのだけど、単純に後者は@objectsへのアクセスががないので、SQLがそもそも発行されていないというのが原因だった。気づいてみればなるほどという感じだけど。。。


russian doll caching(ロシアンドールキャッシング)とは

ネストされたフラグメントキャッシュの内側のキャッシュが更新されると、外側のキャッシュも更新してくれる機能。rails 4ならデフォルトで利用可能。

<% cache @company do %>

<h1><%= @company.name %></h1>
<ul>
<% cache @company.members %>
<% @company.members.each do |member|
<li><%= member.name %></li>
<% end %>
<% end %>
</ul>
<% end %>

上記のようなネストされた構造のとき、membersに変更があったとしても、companyに変更がなければcompanyのキーが変更されず、キャッシュは更新されない。

しかし、memberのmodelに

class Member < ActiveRecord::Base

belongs_to :company, touch: true
end

と書いておくと、ネストされた親のキーにも更新が伝搬してくれるようになる。