Ruby on Railsアプリケーションのパフォーマンスを向上させたい場合、キャッシュを効果的に導入することが方法の1つとして考えられます。 ここでは、Rails開発でよく用いられる2つのキャッシュについてまとめました。

キャッシュの種類

1. フラグメントキャッシュ

フラグメントキャッシュとは、ビューの構成要素をキャッシュブロック(cache do ...)でつつみ、キャッシュが有効な場合にそれを返す仕組みです。

一般的にビューはいくつかのパーシャル(フラグメント)によって構成されていますが、それぞれについてキャッシュやその有効期限を設定するにはフラグメントキャッシュを用いるとよいです。

2. 低レベルキャッシュ

ビューのフラグメントではなく、特定の値やクエリなど、より低レベルな要素もキャッシュできます。 複雑な計算やデータベースへのアクセスがある場合などにおいて、これが有効なケースもあります。

他にもページキャッシュやアクションキャッシュなどもありますが、これはRailsのコアから取り除かれたため言及しません。 基本的には前述の2つを考えればいいと思います。

キャッシュの有効期限

キャッシュを行なう方法について入る前に、まず有効期限の考え方について抑えておく必要があります。

たとえばPostモデルの1レコードを描画する場合、_post.html.erbというパーシャルを作成することになると思います。 このパーシャルが出力するHTMLは、レコードが変わらなければ常に同じHTMLになります。 つまり、キャッシュを参照した方がパフォーマンスがよくなります。

レコードが変わらなければキャッシュを利用し、変わったらキャッシュを更新する──という処理は、キャッシュブロックにActiveRecordのインスタンスを渡すことでよしなに行なってくれます。 次のように書くと、postidupdated_atの組み合わせを含んだ文字列をキーとしてキャッシュを保存します。

<% cache(post) do %>
  ...
<% end %>

レコードが変わるとupdated_atも変わるので、新しいキャッシュが生成される、という仕組みです。

ちなみにidupdated_atの組み合わせはActiveRecord::Base継承クラスから参照できる#cache_keyで定義されています。 キーを任意に決めたい場合は、このメソッドを活用するといいでしょう。

キャッシュの方法

フラグメントキャッシュ

フラグメントキャッシュは、cacheブロックに描画したいHTMLを記述します。 引数にはキーを渡します。

<% cache(post) do %>
  <div>
    <h2><%= post.title %></h2>
    <p><%= post.content %></p>
  </div>
<% end %>

低レベルキャッシュ

低レベルキャッシュは、Rails.cache.fetchにキーを渡し、ブロック内に処理を記述します。 キャッシュストアにキーがある場合はそれを返し、なければブロック内の処理を返します。

class Post < ActiveRecord::Base
  def some_content
    Rails.cache.fetch("#{cache_key}/some_content") do
      do_something(content)
    end
  end
end

キャッシュのバックエンド

キャッシュの保存先としては、大きく分けて次の3つがあります。

1. FileStore

キャッシュをファイルとして保存します。

ファイルなので、Unicornなどのプロセス間でキャッシュを共有できます。 RAMへのアクセスに比べると遅いですが、一般的にストレージはRAMより安いので、安く運用できます。

ただ、ホストをまたぐことはできません。 またLRU (Least Recently Used)キャッシュではないため、効率は悪くなってしまいます。

2. MemoryStore

キャッシュをRAMに保存します。

プロセス間で共有できず、またホストをまたぐこともできません。 一方でRAMに空きさえあれば導入でき、また速いという利点があります。

3. KVS (Memcache, Redis)

外部のKVSに保存します。 ホスト間で共有できます。

どのバックエンドを選択するかですが、サーバが1つの小さなアプリケーションの場合はFileStoreかMemoryStore、複数の場合はKVSを採用すればいいのかなと思います。

参考記事

おわりに

以上、キャッシュについて述べてきました。

キャッシュを導入すると確かに速くはなりますが、一方で「非効率な実装を隠蔽し、見かけ上だけ速くしている」という見方もできます。

キャッシュをオフにしても設計上問題ないように実装し、その上でキャッシュを導入することが大事だと思います。