プログラマでありたい

おっさんになっても、プログラマでありつづけたい

Railsのaction cache(アクション キャッシュ)を使ってみる

 とあるRailsで構築しているサイトでパフォーマンス改善をしたくて、キャッシュ機構の導入を検討しました。キャッシュ機構は色々なポイントがありますが、ざっくり分類すれば言って下記の3点でしょう。
1.Webサーバ側によるコンテンツキャッシュ
2.アプリケーションサーバ側による処理のキャッシュ
3.DB側のデータのキャッシュ

 1のWebサーバのコンテンツキャッシュについては、Apacheのキャッシュであったりリバースプロキシによるキャッシュも含めてもよいでしょう。3のDBのキャッシュ機能については、クエリキャッシュだったりその他の色々なキャッシュを想定して頂ければと思います。最近すっかり定着したMemcached等のKVSのキャッシュは、2と3の中間になるんですかね。
 そして、2のアプリケーションのキャッシュ機能についてです。今回のケースでは認証等が絡むので、1のWebサーバ側でのキャッシュの採用は無理でした。となると、2か3が候補となります。ただ、今回キャッシュを求められる処理については、"DBのレスポンス自体は元々非常に高速、でも更なる高速化が求められている。"かつ、"キャッシュを求められる処理が1箇所しかない"ということで、アプリケーションサーバにキャッシュさせるのが一番かと判断しました。


 そこで、Railsのキャッシュ機構を調べてみたところ、デフォルトでは次の3種類があるようです。
Page caching(ページ単位のキャッシュ)
Action caching(アクション単位のキャッシュ)
Fragment caching(部品単位のキャッシュ)

 ページ単位のキャッシュは、文字通りページを丸ごとキャッシュします。アクションの結果を静的ページとしてキャッシュし、次回以降はそのまま出力します。最速な反面、使いどころとしてはWikiやブログなど万人向けのページにしか使えません。つまり最初に定義した1のキャッシュと同じです。Apacheやリバースプロキシとのキャッシュの違いは何かと言われれば、プログラム側でキャッシュの破棄等の制御が出来ることかと思います。それをしないのであれば、Webサーバやリバースプロキシで行った方が効率は良いでしょう。
 Fragment Cachingについては、ページの一部分についてのキャッシュです。主な用途としてはテンプレートの一部分についてキャッシュするといった利用の仕方のようです。例えばセッション中のユーザの名前を表示するときに使うとかいった方法が主な用途のようです。
 最後にAction cachingですが、ページを丸ごとキャッシュするという点ではページキャッシュと同じです。ただし、キャッシュを参照する前に、ActionPackを経由します。つまり、認証等の前処理を入れることが出来ます。まぁ割とかゆいところに手が届くキャッシュです。


 今回の場合、認証こみでページ単位のキャッシュがしたかったのでAction Cacheを使いました。悩んだ点は下記の3点。
・出力形式(HTML,JSON,XML)が違ったら、別のキャッシュとして扱われる
・細かい制御の書式のサンプルが少ない
・キャッシュ対象はGetメソッドのみ(Postメソッドは対象外)

 一つ目の出力についてですが、考えてみたら当たり前でした。アクションの結果自体をキャッシュするので、結果が違うので別々に保存しないとだめですね。フォーマットのしていは、request.formatで出来ます。
 二つ目ですが、そもそも単純な仕組みなのでソース見ろという事なのかもしれません。まぁとりあえず使うのは次の3つくらいかなと思います。
・対象アクションの指定。(これはごろごろサンプルがあります。)
・フォーマットの指定。request.format(html,json,xml)
・キャッシュ期限の指定。expires_in => 1.hour
 ちなみに、キャッシュ破棄については何も、処理のソースを載せておくだけの男前さです。
例を一つ載せておくので、参考になればと思います。

expire_action(:controller => "samples", :action => "show", :id => id, :format => "xml")


 最後にキャッシュ対象のメソッドについてですが、基本的にキャッシュされるのはGetメソッドのみです。キャッシュを制御しているaction_controllerのファイル(/caching/actions.rb)を見てみると次のようになっていました。

          def caching_allowed(controller)
            controller.request.get? && controller.response.status.to_i == 200
          end

 キャッシュの対象はgetかつ応答ステータスが200(正常)のみのようです。ただややこしいことに、キャッシュする対象であって、既に同一パラメータのGETのリクエストがありキャッシュされていた場合は、POSTでもキャッシュを返すようになっていました。何となくバグじゃねーのと思いますが、V2.3.8しか見ていないので気にしないでおきましょう。


 最後に公式ページのサンプルを載せておきます。キャッシュする方については下記のサンプルで充分です。ただキャッシュ破棄についてもう少し書いてほしいなと思います。

  class ListsController < ApplicationController
    before_filter :authenticate, :except => :public
    caches_page   :public
    caches_action :index, :if => proc do |c|
      !c.request.format.json?  # cache if is not a JSON request
    end

    caches_action :show, :cache_path => { :project => 1 },
      :expires_in => 1.hour

    caches_action :feed, :cache_path => proc do |controller|
      if controller.params[:user_id]
        controller.send(:user_list_url,
          controller.params[:user_id], controller.params[:id])
      else
        controller.send(:list_url, controller.params[:id])
      end
    end
  end


 まぁ色々書きましたが、中々便利です。是非お試しアレ!!


参考にしたサイト:
Module: ActionController::Caching::Actions
優しいRailsの育て方
rails の action_cache (アクションキャッシュ)を使う、その一。
rails の action_cache (アクションキャッシュ)を使う、その二。