caching strategies - herokaijp/devcenter GitHub Wiki
Herokuは、'automagic'のcache-moneyやcache_fuのようなライブラリを使わないことを推奨します。 我々は、容易なキャッシュ解決方法を模索中ですが、満足行くものを見つけ出せていません。 一般的にキャッシュは、アプリケーションに特化した試みです。以下は、Heroku上でRailsアプリを走らせる上で、キャッシュを使い パフォーマンス向上を計るためのロードマップとなります。
一度、memcachedを使うようアプリケーションを設定した場合、 Railsは、アクションキャッシュとフラグメントキャッシュの両方を自動的に使うようになります。
Railsに組み込まれたページキャッシュ機能は、 ファイルシステムへの書き込み権限を必要とします。そのようなものは、Heroku上では動作しません。 しかしながら、我々が提供するVarnish HTTP Cacheは、同等のことを満たしてくれます。 (しかも、驚くほど早く動作します。) HTTPキャッシュのサポートは、全てのHerokuアプリケーションに組み込まれており、memcachedを必要としません。
Varnishのページキャッシュは、before_filterのロジックを必要としないページ(典型的なのは、認証のページ)に適切です。 そして、それは全てのユーザー(すなわち、カスタマイズされたコンテンツを持たない)にとって同様です。 アプリケーションの所有者として、どのようにページを最新式(または旧式)にするかを決める必要があります。 設定方法のインストラクションは、 http cacheのドキュメントを参照して下さい。
あなたが作成しているページが、認証や、その他before/afterフィルターが必要な場合、 Railsに組み込まれたアクションキャッシュ機能を 使うことで、コンテンツをキャッシュし続けることが可能です。アクションキャッシュは、HerokuのMemcacheアドオンを 使います。(必須となります。)
特定のアクションへのキャッシュを有効にするため、シンプルにコントローラーへcaches_action :<action_name>
と追記して下さい。
もし、レイアウトに動的コンテンツ(ヘッダー内のユーザー名やemailアドレスのようなもの)を含む場合、
アクションのコンテンツをキャッシュし続けながら、動的にレイアウトをレンダーすることが可能です。
下記コントローラーのコードが、これらの概念を明確にしてくれます。:
:::ruby
# products_controller.rb
class ProductsController < ActionController
before_filter :authenticate
caches_action :index
caches_action :show, :layout => false
def index
@products = Product.all
end
def show
@product = Product.find(params[:id])
end
def create
expire_action :action => :index
end
end
(ソース: Rails Guides)
フラグメントキャッシュは、アプリ内のウィジェットやパーシャルをキャッシュするための素晴らしい仕組みです。 フラグメントキャッシュは、HerokuのMemcacheアドオンを使います。(必須となります。) 例えば、アプリがproductsをこのようにリスト化しているとします。:
:::ruby
# index.html.erb
<%= render :partial => "product", :collection => @products %>
# _product.html.erb
<div><%= link_to product, product.name %>: <%= product.price%></div>
<div><%= do_something_comlicated%></div>
それであれば、フラグメントキャッシュを使うことで、product毎のパーシャルを簡単にキャッシュすることが出来るでしょう。 パーシャルにActiveRecordオブジェクトを渡せば、Railsは、自動的にキャッシュキーを生成します。:
:::ruby
# _product.html.erb
<% cache(product) do %>
<div><%= link_to product, product.name %>: <%= product.price%></div>
<div><%= do_something_comlicated%></div>
<% end %>
別のフラグメントキャッシュ戦略として、新たなページロード毎に直接データストアからリフレッシュする必要の無い ウィジェットやその他ページの一部分をキャッシュするという方法があります。例えば、ウェブサイトのフロントページが、 売上高トップのproductsをリスト表示する場合、このフラグメントをキャッシュすることが出来るでしょう。 1時間毎に、情報をリフレッシュしたいと仮定します。:
:::ruby
# index.html.erb
<% cache("top_products", :expires_in => 1.hour) do %>
<div id="topSellingProducts">
<% @recent_product = Product.order("units_sold DESC").limit(20) %>
<%= render :partial => "product", :collection => @recent_products %>
</div>
<% end %>
低水準のキャッシュは、どの情報をもキャッシュするために、直接Rails.cache
オブジェクトを使うことを必要とします。
検索するのにコスト(金銭的ではない)が掛かり、いくぶん時代遅れとなるものの、いかなるデータをストアするために、これを使って下さい。
データベースクエリかAPIの呼び出しが、これを使う上での共通の方法となります。
低水準のキャッシュを実装する上で、最も効率的な方法は、Rails.cache.fetch
メソッドを使うこととなります。
このメソッドは、利用可能であれば、キャッシュから値を読み込みます。;
別な方法では、ブロック付きのメソッドを実行し、結果を返すことも出来ます。:
>> Rails.cache.fetch('answer')
==> "nil"
>> Rails.cache.fetch('answer') {1 + 1}
==> 2
Rails.cache.fetch('answer')
==> 2
以下の例を考えてみて下さい。在庫切れのproductを全て返すクラスメソッドを持ったProductモデルと、競争相手のwebサイトから 価格でproductを検索するインスタンスメソッドです。これらのメソッドにより返されるデータには、低水準のキャッシュが 適切でしょう。
:::ruby
# product.rb
def Product.out_of_stock
Rails.cache.fetch("out_of_stock_products", :expires_in => 5.minutes) do
Product.all.joins(:inventory).conditions.where("inventory.quantity = 0")
end
end
def competing_price
Rails.cache.fetch("/product/#{id}-#{updated_at}/comp_price", :expires_in => 12.hours) do
Competitor::API.find_price(id)
end
end
この最後の例では、モデルのidとupdate_at属性をベースとしたcache-keyを生成していることに気付いて下さい。 これは一般的な規約となり、productが更新された際、常にキャッシュを無効とするのに役立ちます。 一般的に、インスタンスレベルの情報へ低水準のキャッシュを使用する場合、cache-keyを生成する必要があります。
- Caching with Rails by RailsGuides
- Scaling Rails by New Relic