Help us understand the problem. What is going on with this article?

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

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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away